Dedup format layer; public accessors replace private reach-throughs
- SrasFile: _parse_v6 now retains per-angle x_delta and the verbatim preamble/background byte spans, and gains public data_offset, y_pos_per_angle, and iter_angle_blocks() (which now owns the ragged block-offset walk used three separate places before). - sras_edit_scans: the 70-line re-parse of the v6 header sections (_read_v6_sections/_reread_span/_v6_angle_offsets) collapses into a _write_v6 that consumes SrasFile directly — verified byte-identical round-trip on v6 int8/int16 and legacy files. Eight print-and-exit pairs become _die(). - tools/make_test_sras imports the struct layouts from sras_format and the rotation matrix from sras_compute instead of re-declaring them (byte assembly stays independent of the reader). - sras_compute: block_mean_2d, pixel_pitch_mm, nominal_delta_deg made public (they were GUI-facing); registration_workers() and default_max_workers() wrap the remaining private reach-throughs from sras_workers. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
+52
-9
@@ -375,21 +375,34 @@ class SrasFile:
|
||||
angles = np.frombuffer(f.read(n_angles * 4), dtype=">f4").astype(np.float32)
|
||||
|
||||
x_start = np.empty(n_angles, dtype=np.float64)
|
||||
x_delta = np.empty(n_angles, dtype=np.float64)
|
||||
n_frames = np.empty(n_angles, dtype=np.int64)
|
||||
n_rows = np.empty(n_angles, dtype=np.int64)
|
||||
for a in range(n_angles):
|
||||
xs, xd, nf, nr = _read_struct(f, GEO_FMT_V6)
|
||||
x_start[a], n_frames[a], n_rows[a] = xs, nf, nr
|
||||
x_start[a], x_delta[a], n_frames[a], n_rows[a] = xs, xd, nf, nr
|
||||
|
||||
y_pos_per_angle = [
|
||||
np.frombuffer(f.read(int(n_rows[a]) * 4), dtype=">f4").astype(np.float32)
|
||||
for a in range(n_angles)
|
||||
]
|
||||
|
||||
# Verbatim on-disk spans of the preamble and background sections,
|
||||
# kept so file-rewriting tools (sras_edit_scans) can carry them
|
||||
# over byte-for-byte without re-parsing.
|
||||
span_start = f.tell()
|
||||
self._set_calibration(_read_preambles(f, n_ch), n_ch)
|
||||
self.background = _read_background(f)
|
||||
span_end = f.tell()
|
||||
f.seek(span_start)
|
||||
self.preambles_raw = f.read(span_end - span_start)
|
||||
|
||||
data_offset = f.tell()
|
||||
span_start = span_end
|
||||
self.background = _read_background(f)
|
||||
span_end = f.tell()
|
||||
f.seek(span_start)
|
||||
self.background_raw = f.read(span_end - span_start)
|
||||
|
||||
data_offset = span_end
|
||||
|
||||
self._data_offset = data_offset
|
||||
|
||||
@@ -428,6 +441,7 @@ class SrasFile:
|
||||
self.scan_aborted = n_complete < n_angles_declared
|
||||
self.angles_deg = angles[:n_complete]
|
||||
self.x_start_mm = x_start[:n_complete]
|
||||
self.x_delta_mm_per_angle = x_delta[:n_complete]
|
||||
self.n_frames = n_frames[:n_complete]
|
||||
self.n_rows = n_rows[:n_complete]
|
||||
self._y_pos_per_angle = y_pos_per_angle[:n_complete]
|
||||
@@ -436,6 +450,37 @@ class SrasFile:
|
||||
if self.version == 7 and offset < file_size:
|
||||
self._parse_cach_section(offset)
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Byte-layout accessors (public: used by file-rewriting tools)
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
@property
|
||||
def data_offset(self) -> int:
|
||||
"""File offset where the waveform data begins (headers end)."""
|
||||
return self._data_offset
|
||||
|
||||
@property
|
||||
def y_pos_per_angle(self) -> list[np.ndarray]:
|
||||
"""Per-angle Y row positions (mm). The list and its arrays are the
|
||||
live parsed state — tools that reproject may replace entries."""
|
||||
return self._y_pos_per_angle
|
||||
|
||||
@y_pos_per_angle.setter
|
||||
def y_pos_per_angle(self, value: list[np.ndarray]):
|
||||
self._y_pos_per_angle = value
|
||||
|
||||
def iter_angle_blocks(self):
|
||||
"""Yields (angle_idx, byte_offset, byte_count) for each complete
|
||||
angle's waveform block. Works for every version: legacy files have
|
||||
uniform per-angle geometry, so the same walk applies."""
|
||||
offset = self._data_offset
|
||||
for a in range(self.n_angles):
|
||||
nbytes = (int(self.n_rows[a]) * self.n_channels
|
||||
* int(self.n_frames[a]) * self.samples_per_frame
|
||||
* self.bytes_per_sample)
|
||||
yield a, offset, nbytes
|
||||
offset += nbytes
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# v7 cache tail (CACH section: precomputed DC / FFT images)
|
||||
# ------------------------------------------------------------------
|
||||
@@ -445,12 +490,10 @@ class SrasFile:
|
||||
start), derived purely from the header + Per-Angle Geometry Table —
|
||||
independent of whether a cache tail is actually present. Used by
|
||||
both the parser and the in-place writer."""
|
||||
waveform_bytes = sum(
|
||||
int(self.n_rows[a]) * self.n_channels * int(self.n_frames[a])
|
||||
* self.samples_per_frame * self.bytes_per_sample
|
||||
for a in range(self.n_angles)
|
||||
)
|
||||
return self._data_offset + int(waveform_bytes)
|
||||
end = self._data_offset
|
||||
for _, offset, nbytes in self.iter_angle_blocks():
|
||||
end = offset + nbytes
|
||||
return end
|
||||
|
||||
def _read_cache_block(self, f, hdr_fmt: str, magic: bytes,
|
||||
stores: list[list]) -> int | None:
|
||||
|
||||
Reference in New Issue
Block a user