Refactor cache validation and optimize alignment wizard incremental updates
- Extract cache mismatch logic into reusable cache_mismatch_reasons() function for cleaner validation and consistent miss reporting - Expose memory_budget_bytes() for external callers (aligned exporter) - Optimize rebuild_stack() with incremental layer updates when only one angle parameters change, avoiding full reprojection overhead - Remove unused seed_per_angle parameter from alignment wizard - Simplify cached_rf_image() DC masking with cleaner early exit logic - Clean up internal state tracking with explicit origin caching Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -254,6 +254,48 @@ class SrasFile:
|
||||
# be served from it — see sras_compute.cached_rf_image.
|
||||
self.precomputed_pad_factor: int = 1
|
||||
|
||||
def encoded_preambles(self) -> bytes:
|
||||
"""This file's Preamble Blocks section, as bytes a writer can emit.
|
||||
|
||||
v6/v7 files kept the on-disk span verbatim, which is both cheaper and
|
||||
lossless; legacy files did not keep it, and a v2 file has no preambles
|
||||
at all, so those are re-encoded from the parsed strings (empty ones for
|
||||
v2). Empty is not a silent downgrade: _parse_preamble("") returns {} and
|
||||
_set_calibration falls back to the hardcoded scope constants, which is
|
||||
exactly the calibration a v2 file already gets, so mV values round-trip
|
||||
unchanged.
|
||||
|
||||
Lives here rather than at each writer so the version fan-out sits next
|
||||
to the parser that creates it, and no writer has to probe the object
|
||||
to find out which shape it got.
|
||||
"""
|
||||
raw = getattr(self, "preambles_raw", None)
|
||||
if raw is not None:
|
||||
return raw
|
||||
out = bytearray()
|
||||
for s in self.preambles or [""] * self.n_channels:
|
||||
encoded = s.encode("utf-8")
|
||||
out += struct.pack(">H", len(encoded)) + encoded
|
||||
return bytes(out)
|
||||
|
||||
def encoded_background(self) -> bytes:
|
||||
"""This file's Background Block, as bytes a writer can emit.
|
||||
|
||||
When there is none (v2/v3), this is samples_per_frame zeros rather than
|
||||
a zero-length block. Every consumer guards on `background is not None`
|
||||
and then subtracts it from a (spf,)-shaped row, so a length-0 array
|
||||
would broadcast-fail at the first background-subtracted FFT; zeros make
|
||||
the subtraction a correct no-op instead.
|
||||
"""
|
||||
raw = getattr(self, "background_raw", None)
|
||||
if raw is not None:
|
||||
return raw
|
||||
if self.background is None:
|
||||
samples = np.zeros(self.samples_per_frame, dtype=np.int8)
|
||||
else:
|
||||
samples = np.rint(self.background).astype(np.int8)
|
||||
return struct.pack(">I", samples.size) + samples.tobytes()
|
||||
|
||||
def cached_dc_mv(self, angle_idx: int, ch_idx: int) -> np.ndarray | None:
|
||||
"""A stored DC image (already in mV) for (angle, channel), or None."""
|
||||
store = self.precomputed_dc3_mv if ch_idx == CH3_IDX else self.precomputed_dc4_mv
|
||||
@@ -284,6 +326,15 @@ class SrasFile:
|
||||
self.scan_aborted = False
|
||||
self.n_angles_declared = n_angles
|
||||
|
||||
# Pre-v6 files carry no nominal ROI. Defined as None rather than
|
||||
# left absent so the object's shape does not depend on its version
|
||||
# and writers can ask instead of probing with hasattr.
|
||||
self.x_start_nominal_mm = None
|
||||
self.y_start_nominal_mm = None
|
||||
self.x_delta_nominal_mm = None
|
||||
self.y_delta_nominal_mm = None
|
||||
self.row_spacing_mm = None
|
||||
|
||||
angles = np.frombuffer(f.read(n_angles * 4), dtype=">f4").astype(np.float32)
|
||||
y_pos = np.frombuffer(f.read(n_rows * 4), dtype=">f4").astype(np.float32)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user