Add batch export of DC/RF/Velocity map images
Add Export -> Batch Export Images..., which renders and saves one PNG per angle for whichever of CH1 (RF), CH3 (DC-A), CH4 (DC-B), and Velocity the user selects, for the currently open file. Each channel gets its own fixed min/max colorbar range, entered once in the dialog and held constant across every exported angle, so the resulting images are directly comparable to each other instead of each auto-scaling to its own data (today's live-view default). BatchExportDialog (sras_viewer.py) collects the output folder, file prefix, and per-channel checkbox + range, seeded from whatever's already cached (session DC/FFT caches, or the file's own v7 cache blocks) so opening the dialog never triggers a fresh compute. Export runs on a background thread via a new BatchExportWorker (sras_workers.py), which computes each angle's channel image with the existing dc_image_mv/compute_rf_image helpers (reusing one FFT per angle for both RF and Velocity) and renders it with a headless matplotlib Agg canvas. Also includes several small correctness/robustness fixes that were already staged in the working tree: an int16-vs-float32 accumulator mismatch in sras_average.py's row averaging, a DC4-mask cache reuse and atomic sidecar write in sras_compute.py, a dc3/dc4 pairing guard in sras_format.py's v7 cache writer, and matching updates to the tools/ test fixtures. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
+15
-3
@@ -266,13 +266,19 @@ def compute_rf_image(sras: SrasFile, angle_idx: int,
|
||||
budget=budget)
|
||||
background = sras.background if (apply_bg_sub and sras.background is not None) else None
|
||||
cal4 = sras.cal(CH4_IDX)
|
||||
# DC4 mask source, in the same priority order as the fast path above:
|
||||
# already-cached DC block (skips reading CH4 raw waveforms entirely),
|
||||
# then the caller-supplied image, else recompute per chunk below.
|
||||
dc4_full = sras.cached_dc_mv(angle_idx, CH4_IDX) if dc_threshold_mv is not None else None
|
||||
if dc4_full is None:
|
||||
dc4_full = dc4_mv
|
||||
|
||||
def chunk(r0: int, r1: int):
|
||||
if dc_threshold_mv is None:
|
||||
valid = None
|
||||
else:
|
||||
if dc4_mv is not None:
|
||||
dc4_chunk = dc4_mv[r0:r1]
|
||||
if dc4_full is not None:
|
||||
dc4_chunk = dc4_full[r0:r1]
|
||||
else:
|
||||
dc4_chunk = adc_to_mv(
|
||||
data[r0:r1, CH4_IDX, :, :].astype(np.float32).mean(axis=-1), *cal4)
|
||||
@@ -1007,7 +1013,13 @@ def save_manual_alignment(sras: SrasFile, ref_angle_idx: int,
|
||||
for a, p in per_angle.items()
|
||||
},
|
||||
}
|
||||
path.write_text(json.dumps(payload, indent=2))
|
||||
# Write to a temp file in the same directory then rename over the target
|
||||
# — os.replace() is atomic on both POSIX and Windows, so a crash mid-write
|
||||
# leaves either the old sidecar or the new one, never a truncated/corrupt
|
||||
# file that load_manual_alignment would silently treat as "no alignment".
|
||||
tmp_path = path.with_name(path.name + f".tmp{os.getpid()}")
|
||||
tmp_path.write_text(json.dumps(payload, indent=2))
|
||||
os.replace(tmp_path, path)
|
||||
return path
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user