Skip to content

Commit 8825973

Browse files
committed
fix(affine): handle variable slice-pack orientation entries
1 parent fc62796 commit 8825973

2 files changed

Lines changed: 88 additions & 32 deletions

File tree

‎src/brkraw/resolver/affine.py‎

Lines changed: 56 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -328,42 +328,66 @@ def resolve_matvec_and_shape(visu_pars,
328328
shape = np.asarray(visu_pars.get("VisuCoreSize"), dtype=float)
329329

330330
if dim == 2:
331-
num_rotates = rotate.shape[0]
332-
num_origins = origin.shape[0]
333331
num_slicepack = len(num_slices)
334-
if num_rotates != num_origins:
335-
raise ValueError("num_rotates != num_origins")
336-
337-
expected = int(num_slicepack * num_slices[spack_idx])
338-
if rotate.ndim == 2 and rotate.shape[1] == 9 and rotate.shape[0] > expected:
339-
if not np.allclose(rotate, rotate[0], atol=0, rtol=0):
340-
logger.warning(
341-
"VisuCoreOrientation has %s entries but expected %s; "
342-
"using the first %s entry/entries.",
343-
rotate.shape[0],
344-
expected,
345-
expected,
346-
)
347-
if origin.ndim == 2 and origin.shape[1] == 3 and origin.shape[0] > expected:
348-
if not np.allclose(origin, origin[0], atol=0, rtol=0):
349-
logger.warning(
350-
"VisuCorePosition has %s entries but expected %s; "
351-
"using the first %s entry/entries.",
352-
origin.shape[0],
353-
expected,
354-
expected,
355-
)
356-
if rotate.ndim == 2 and rotate.shape[1] == 9 and rotate.shape[0] >= expected:
357-
rotate = rotate[:expected, :]
358-
if origin.ndim == 2 and origin.shape[1] == 3 and origin.shape[0] >= expected:
359-
origin = origin[:expected, :]
360-
rotate = rotate.reshape((num_slicepack, num_slices[spack_idx], 9))
361-
origin = origin.reshape((num_slicepack, num_slices[spack_idx], 3))
362-
_rotate = rotate[spack_idx]
363-
_origin = origin[spack_idx]
332+
333+
if spack_idx < 0 or spack_idx >= num_slicepack:
334+
raise IndexError(f"spack_idx out of range: {spack_idx} (num packs: {num_slicepack})")
335+
336+
total_slices = int(np.sum(np.asarray(num_slices, dtype=int)))
337+
spack_slice_start = int(np.sum(np.asarray(num_slices[:spack_idx], dtype=int)))
338+
spack_slice_end = spack_slice_start + int(num_slices[spack_idx])
339+
340+
def _select_slice_entries(arr: np.ndarray, *, width: int, name: str) -> np.ndarray:
341+
arr = np.asarray(arr, dtype=float)
342+
if arr.ndim == 1:
343+
if arr.size == width:
344+
arr = arr.reshape((1, width))
345+
else:
346+
raise ValueError(f"{name} has shape {arr.shape}, expected (*, {width})")
347+
if arr.ndim != 2 or arr.shape[1] != width:
348+
raise ValueError(f"{name} has shape {arr.shape}, expected (*, {width})")
349+
350+
# Prefer per-slice entries (concatenated across slice packs).
351+
if arr.shape[0] > total_slices:
352+
if not np.allclose(arr[:total_slices], arr[0], atol=0, rtol=0):
353+
logger.warning(
354+
"%s has %s entries but expected %s; using the first %s entries.",
355+
name,
356+
arr.shape[0],
357+
total_slices,
358+
total_slices,
359+
)
360+
arr = arr[:total_slices, :]
361+
362+
if arr.shape[0] == total_slices:
363+
return arr[spack_slice_start:spack_slice_end, :]
364+
365+
# Fallback: per-pack entries (one entry per slice pack).
366+
if arr.shape[0] == num_slicepack:
367+
if int(num_slices[spack_idx]) != 1:
368+
raise ValueError(
369+
f"{name} provides one entry per slice pack ({num_slicepack}) "
370+
f"but pack {spack_idx} has {num_slices[spack_idx]} slices; "
371+
"per-slice entries are required to resolve slice positions."
372+
)
373+
return arr[spack_idx:spack_idx + 1, :]
374+
375+
raise ValueError(
376+
f"{name} has {arr.shape[0]} entries, expected {total_slices} (per-slice) "
377+
f"or {num_slicepack} (per-pack); method num_slices={num_slices}."
378+
)
379+
380+
_rotate = _select_slice_entries(rotate, width=9, name="VisuCoreOrientation")
381+
_origin = _select_slice_entries(origin, width=3, name="VisuCorePosition")
364382
_num_slices = num_slices[spack_idx]
365383
_slice_thickness = slice_thickness[spack_idx]
366384

385+
if _rotate.shape[0] > 1 and not np.allclose(_rotate, _rotate[0], atol=0, rtol=0):
386+
logger.warning(
387+
"VisuCoreOrientation varies across slices in pack %s; using the first slice orientation.",
388+
spack_idx,
389+
)
390+
367391
row = _rotate[0, 0:3]
368392
col = _rotate[0, 3:6]
369393
slc = _rotate[0, 6:9]

‎tests/test_resolver/test_affine.py‎

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import numpy as np
2+
3+
from brkraw.resolver.affine import resolve_matvec_and_shape
4+
5+
6+
def test_resolve_matvec_and_shape_variable_slices_per_pack() -> None:
7+
num_slices = [2, 1, 3]
8+
slice_thickness = [1.0, 1.0, 1.0]
9+
10+
total_slices = sum(num_slices)
11+
base_rotate = np.array([1, 0, 0, 0, 1, 0, 0, 0, 1], dtype=float)
12+
rotate = np.tile(base_rotate, (total_slices, 1))
13+
14+
z = np.array([10, 11, 20, 30, 31, 32], dtype=float)
15+
origin = np.column_stack([np.zeros(total_slices), np.zeros(total_slices), z])
16+
17+
visu_pars = {
18+
"VisuCoreDim": 2,
19+
"VisuCoreOrientation": rotate,
20+
"VisuCorePosition": origin,
21+
"VisuCoreExtent": np.array([40.0, 40.0], dtype=float),
22+
"VisuCoreSize": np.array([256, 256], dtype=float),
23+
}
24+
25+
for spack_idx, expected_min_z in enumerate([10.0, 20.0, 30.0]):
26+
mat, vec, shape = resolve_matvec_and_shape(
27+
visu_pars, spack_idx, num_slices, slice_thickness
28+
)
29+
assert shape == (256, 256, num_slices[spack_idx])
30+
assert np.allclose(vec, [0.0, 0.0, expected_min_z])
31+
assert np.allclose(np.diag(mat), [40.0 / 256.0, 40.0 / 256.0, 1.0])
32+

0 commit comments

Comments
 (0)