Convert test scripts to pytest; extend the equivalence harness
- pyproject.toml replaces sras_viewer_requirements.txt (same pins) and adds a dev extra with pytest. - tools/test_refactor.py, test_alignment.py, test_gui.py become tests/test_compute.py, tests/test_alignment.py, tests/test_gui.py with assertions preserved verbatim. test_gui.py stays one ordered integration sequence over a shared module-scoped window. - check_equivalence.py: drop the dead pre-refactor monolith shim (and the _compute_angle_alignment alias it consumed), extend the pad sweep to (1, 2, 4, 8, 40), add legacy-v4 and big-endian int16 legs (new bps=2 option in make_test_sras) so the padded FFT path and the >i2 memmap path are in the baseline before the FFT rewrite. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
+24
-27
@@ -1,13 +1,9 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Golden-output equivalence harness for the sras-viewer refactor.
|
||||
"""Golden-output equivalence harness for compute-path refactors.
|
||||
|
||||
Computes a battery of DC / FFT / alignment outputs and prints a stable hash
|
||||
for each. Run it on the pre-refactor commit to capture a baseline, then again
|
||||
after the refactor and diff the two reports — every line must match.
|
||||
|
||||
Imports work against both the pre-refactor monolith (`sras_viewer`) and the
|
||||
post-refactor split (`sras_format` + `sras_compute`), so the *same* script
|
||||
produces both sides of the comparison.
|
||||
for each. Run it before a refactor to capture a baseline, then again after
|
||||
and diff the two reports — every line must match.
|
||||
|
||||
Hashes canonicalise to native little-endian float64 before hashing, so a
|
||||
deliberate dtype/byte-order change that preserves values does not show up as
|
||||
@@ -29,23 +25,11 @@ import numpy as np
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
|
||||
|
||||
# --- Import shim: split modules if present, else the monolith --------------
|
||||
try:
|
||||
from sras_format import SrasFile, CH1_IDX, CH3_IDX, CH4_IDX, adc_to_mv
|
||||
import sras_compute as C
|
||||
_LAYOUT = "split"
|
||||
except ImportError:
|
||||
import sras_viewer as _V
|
||||
from sras_viewer import SrasFile, CH1_IDX, CH3_IDX, CH4_IDX, adc_to_mv
|
||||
C = _V
|
||||
_LAYOUT = "monolith"
|
||||
|
||||
compute_dc_image = C.compute_dc_image
|
||||
compute_rf_image = C.compute_rf_image
|
||||
compute_alignment = C._compute_angle_alignment
|
||||
apply_alignment = C.apply_alignment
|
||||
|
||||
import tools.make_test_sras as gen # noqa: E402
|
||||
from sras_format import SrasFile, CH3_IDX, CH4_IDX, adc_to_mv # noqa: E402
|
||||
from sras_compute import ( # noqa: E402
|
||||
apply_alignment, compute_angle_alignment, compute_dc_image, compute_rf_image,
|
||||
)
|
||||
import tools.make_test_sras as gen # noqa: E402
|
||||
|
||||
|
||||
def h(arr) -> str:
|
||||
@@ -109,7 +93,7 @@ def check_file(path: Path, lines: list[str], tag: str,
|
||||
for bg in (False, True):
|
||||
if bg and s.background is None:
|
||||
continue
|
||||
for pad in (1, 2):
|
||||
for pad in (1, 2, 4, 8, 40):
|
||||
n_fft = s.samples_per_frame * pad if pad > 1 else None
|
||||
for ti, thr in enumerate(thresholds):
|
||||
img = compute_rf_image(s, a, dc_threshold_mv=thr,
|
||||
@@ -135,7 +119,7 @@ def check_alignment(path: Path, lines: list[str], tag: str):
|
||||
sras.ch_ymult_mv[CH4_IDX], sras.ch_yoff_adc[CH4_IDX],
|
||||
sras.ch_yzero_mv[CH4_IDX])
|
||||
thr = float(np.median(dc4))
|
||||
res = compute_alignment(sras, 0, thr)
|
||||
res = compute_angle_alignment(sras, 0, thr)
|
||||
report(lines, f"[{tag}] align canvas_shape", str(res.canvas_shape))
|
||||
report(lines, f"[{tag}] align canvas_origin",
|
||||
f"{res.canvas_origin_mm[0]:.9g},{res.canvas_origin_mm[1]:.9g}")
|
||||
@@ -165,7 +149,7 @@ def main():
|
||||
help="directory for generated synthetic files")
|
||||
args = p.parse_args()
|
||||
|
||||
lines = [f"# layout: {_LAYOUT}", f"# numpy: {np.__version__}"]
|
||||
lines = [f"# numpy: {np.__version__}"]
|
||||
|
||||
scratch = Path(args.scratch)
|
||||
synth = scratch / "equiv_synth.sras"
|
||||
@@ -179,6 +163,19 @@ def main():
|
||||
gen.write(synth_odd, n_angles=2, seed=7, samples_per_frame=37)
|
||||
check_file(synth_odd, lines, "odd", angles=[0, 1], n_rows=None)
|
||||
|
||||
# A legacy v4 file exercises the uniform-geometry legacy layout through
|
||||
# the same DC/FFT battery.
|
||||
synth_v4 = scratch / "equiv_synth_v4.sras"
|
||||
gen.write_legacy(synth_v4, version=4, n_angles=2, n_rows=6,
|
||||
n_frames=14, samples_per_frame=48, seed=5)
|
||||
check_file(synth_v4, lines, "v4", angles=[0, 1], n_rows=None)
|
||||
|
||||
# A big-endian int16 v6 file (real acquisitions are >i2; the other
|
||||
# synthetics are int8).
|
||||
synth_i16 = scratch / "equiv_synth_i16.sras"
|
||||
gen.write(synth_i16, n_angles=2, seed=9, samples_per_frame=64, bps=2)
|
||||
check_file(synth_i16, lines, "int16", angles=[0, 1], n_rows=None)
|
||||
|
||||
if args.real:
|
||||
real = Path(args.real)
|
||||
if real.exists():
|
||||
|
||||
Reference in New Issue
Block a user