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:
+20
-10
@@ -768,7 +768,7 @@ def _skimage_phase_cross_correlation():
|
||||
|
||||
# ---- Geometry: local mm, ref mm, and the one affine builder ---------------
|
||||
|
||||
def _pixel_pitch_mm(sras: SrasFile, angle_idx: int) -> tuple[float, float]:
|
||||
def pixel_pitch_mm(sras: SrasFile, angle_idx: int) -> tuple[float, float]:
|
||||
"""(dx, dy) mm/pixel for one angle: dx is the file-wide constant
|
||||
pixel_x_mm; dy is this angle's own row spacing (assumed uniform, the same
|
||||
assumption _redraw_image makes when it builds the display extent). dy keeps
|
||||
@@ -803,7 +803,7 @@ def _local_half_extent_mm(sras: SrasFile, angle_idx: int) -> tuple[float, float]
|
||||
"""(half width, half height) in mm from this angle's array center to the
|
||||
center of its outermost pixel."""
|
||||
n_rows, n_frames = sras.image_shape(angle_idx)
|
||||
dx, dy = _pixel_pitch_mm(sras, angle_idx)
|
||||
dx, dy = pixel_pitch_mm(sras, angle_idx)
|
||||
return (n_frames - 1) / 2.0 * abs(dx), (n_rows - 1) / 2.0 * abs(dy)
|
||||
|
||||
|
||||
@@ -813,7 +813,7 @@ def _rotation_matrix(theta_deg: float) -> np.ndarray:
|
||||
return np.array([[c, -s], [s, c]]) # CCW rotation acting on (x, y)
|
||||
|
||||
|
||||
def _nominal_delta_deg(sras: SrasFile, angle_idx: int, ref_idx: int) -> float:
|
||||
def nominal_delta_deg(sras: SrasFile, angle_idx: int, ref_idx: int) -> float:
|
||||
"""The rotation-stage's own reported angle change between two angles.
|
||||
|
||||
Used only to *seed* the rotation search, never as the answer: the stage's
|
||||
@@ -888,7 +888,7 @@ def apply_alignment(result: AlignmentResult, angle_idx: int, img: np.ndarray,
|
||||
mode="constant", cval=0.0)
|
||||
|
||||
|
||||
def _block_mean_2d(img: np.ndarray, fy: int, fx: int) -> np.ndarray:
|
||||
def block_mean_2d(img: np.ndarray, fy: int, fx: int) -> np.ndarray:
|
||||
"""Block-mean by independent row/column factors. Independent factors matter
|
||||
because the raw grid is strongly anisotropic (5 µm along x, 50 µm along y):
|
||||
a single square factor would either alias along x or throw away rows."""
|
||||
@@ -944,10 +944,10 @@ def _prepare_reg_image(sras: SrasFile, angle_idx: int, img: np.ndarray,
|
||||
resampled onto the registration grid. Pre-averaging matters: the raw grid
|
||||
is 10x finer along x than along y, so sampling it directly at the (much
|
||||
coarser) isotropic registration pitch would alias badly along x."""
|
||||
dx, dy = _pixel_pitch_mm(sras, angle_idx)
|
||||
dx, dy = pixel_pitch_mm(sras, angle_idx)
|
||||
fx = max(1, int(pitch_mm / abs(dx)))
|
||||
fy = max(1, int(pitch_mm / abs(dy)))
|
||||
small = _block_mean_2d(np.asarray(img, dtype=np.float32), fy, fx)
|
||||
small = block_mean_2d(np.asarray(img, dtype=np.float32), fy, fx)
|
||||
n_rows, n_frames = img.shape
|
||||
return _RegImage(
|
||||
small, dx * fx, dy * fy,
|
||||
@@ -1087,7 +1087,7 @@ def _reg_pitch_and_size(sras: SrasFile, max_dim: int,
|
||||
span = diag * margin
|
||||
native = float(np.sqrt(
|
||||
abs(sras.pixel_x_mm)
|
||||
* max(abs(_pixel_pitch_mm(sras, a)[1]) for a in range(sras.n_angles))))
|
||||
* max(abs(pixel_pitch_mm(sras, a)[1]) for a in range(sras.n_angles))))
|
||||
pitch = max(span / max_dim, native)
|
||||
n = int(scipy_fft.next_fast_len(max(16, int(np.ceil(span / pitch)))))
|
||||
return pitch, n
|
||||
@@ -1111,6 +1111,16 @@ def _registration_workers(sras: SrasFile, fine_dim: int) -> int:
|
||||
_TOTAL_BYTES_BUDGET // max(1, per_worker))))
|
||||
|
||||
|
||||
def registration_workers(sras: SrasFile) -> int:
|
||||
"""Public: concurrent-registration cap at the default fine grid."""
|
||||
return _registration_workers(sras, _DEFAULT_FINE_DIM)
|
||||
|
||||
|
||||
def default_max_workers() -> int:
|
||||
"""Public: the module-wide worker cap (SRAS_MAX_WORKERS or cpu count)."""
|
||||
return _MAX_WORKERS
|
||||
|
||||
|
||||
def _rotation_candidates(nominal_deg: float, search_deg: float,
|
||||
step_deg: float) -> list[float]:
|
||||
"""Coarse rotation candidates: a window around *both* signs of the stage's
|
||||
@@ -1254,7 +1264,7 @@ def register_angle_to_reference(
|
||||
if not candidates:
|
||||
return RigidFit(0.0, (0.0, 0.0), -1.0, "none")
|
||||
|
||||
nominal = _nominal_delta_deg(sras, angle_idx, ref_angle_idx)
|
||||
nominal = nominal_delta_deg(sras, angle_idx, ref_angle_idx)
|
||||
thetas = _rotation_candidates(nominal, search_deg, coarse_step_deg)
|
||||
|
||||
# ---- Stage 1: coarse sweep, every source ------------------------------
|
||||
@@ -1363,7 +1373,7 @@ def build_canvas_affine(sras: SrasFile, angle_idx: int, ref_angle_idx: int,
|
||||
(rows, cols) block-mean factor already applied to the image the caller will
|
||||
resample — 1:1 for the raw image, coarser for the manual-alignment
|
||||
preview's downsampled masks."""
|
||||
dx_a, dy_a = _pixel_pitch_mm(sras, angle_idx)
|
||||
dx_a, dy_a = pixel_pitch_mm(sras, angle_idx)
|
||||
n_rows, n_frames = sras.image_shape(angle_idx)
|
||||
fy, fx = (max(1, int(v)) for v in src_downsample)
|
||||
if (fy, fx) != (1, 1):
|
||||
@@ -1398,7 +1408,7 @@ def _result_from_params(sras: SrasFile, ref_angle_idx: int,
|
||||
the shared canvas, then build each angle's canvas->raw affine. Pure matrix
|
||||
and bbox math, so it is cheap enough to call synchronously on the GUI
|
||||
thread on every manual edit."""
|
||||
pitch = _pixel_pitch_mm(sras, ref_angle_idx)
|
||||
pitch = pixel_pitch_mm(sras, ref_angle_idx)
|
||||
canvas_origin_mm, canvas_shape = canvas_for_params(
|
||||
sras, ref_angle_idx, pitch, params, snap=True)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user