Skip to content

Commit b4cc677

Browse files
authored
Merge pull request nipreps#543 from oesteban/fix/mosaic-cuts-calculation
FIX: Use ``numpy.linspace`` to calculate mosaic plots' cutting planes
2 parents 8256530 + 9142224 commit b4cc677

File tree

1 file changed

+16
-22
lines changed

1 file changed

+16
-22
lines changed

niworkflows/viz/utils.py

Lines changed: 16 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -143,8 +143,6 @@ def extract_svg(display_object, dpi=300, compress="auto"):
143143

144144
def cuts_from_bbox(mask_nii, cuts=3):
145145
"""Find equi-spaced cuts for presenting images."""
146-
from nibabel.affines import apply_affine
147-
148146
mask_data = np.asanyarray(mask_nii.dataobj) > 0.0
149147

150148
# First, project the number of masked voxels on each axes
@@ -163,32 +161,28 @@ def cuts_from_bbox(mask_nii, cuts=3):
163161
# I have manually found that for the axial view requiring 30%
164162
# of the slice elements to be masked drops almost empty boxes
165163
# in the mosaic of axial planes (and also addresses #281)
166-
ijk_th = [
167-
int((mask_data.shape[1] * mask_data.shape[2]) * 0.2), # sagittal
168-
int((mask_data.shape[0] * mask_data.shape[2]) * 0.0), # coronal
169-
int((mask_data.shape[0] * mask_data.shape[1]) * 0.3), # axial
170-
]
171-
172-
vox_coords = []
164+
ijk_th = np.ceil([
165+
(mask_data.shape[1] * mask_data.shape[2]) * 0.2, # sagittal
166+
(mask_data.shape[0] * mask_data.shape[2]) * 0.1, # coronal
167+
(mask_data.shape[0] * mask_data.shape[1]) * 0.3, # axial
168+
]).astype(int)
169+
170+
vox_coords = np.zeros((4, cuts), dtype=np.float32)
171+
vox_coords[-1, :] = 1.0
173172
for ax, (c, th) in enumerate(zip(ijk_counts, ijk_th)):
173+
# Start with full plane if mask is seemingly empty
174+
smin, smax = (0, mask_data.shape[ax] - 1)
175+
174176
B = np.argwhere(c > th)
177+
if not B.size: # Threshold too high
178+
B = np.argwhere(c > 0)
175179
if B.size:
176180
smin, smax = B.min(), B.max()
177181

178-
# Avoid too narrow selections of cuts (very small masks)
179-
if not B.size or (th > 0 and (smin + cuts + 1) >= smax):
180-
B = np.argwhere(c > 0)
181-
182-
# Resort to full plane if mask is seemingly empty
183-
smin, smax = B.min(), B.max() if B.size else (0, mask_data.shape[ax])
184-
inc = (smax - smin) / (cuts + 1)
185-
vox_coords.append([smin + (i + 1) * inc for i in range(cuts)])
182+
vox_coords[ax, :] = np.linspace(smin, smax, num=cuts + 2)[1:-1]
186183

187-
ras_coords = []
188-
for cross in np.array(vox_coords).T:
189-
ras_coords.append(apply_affine(mask_nii.affine, cross).tolist())
190-
ras_cuts = [list(coords) for coords in np.transpose(ras_coords)]
191-
return {k: v for k, v in zip(["x", "y", "z"], ras_cuts)}
184+
ras_coords = mask_nii.affine.dot(vox_coords)[:3, ...]
185+
return {k: list(v) for k, v in zip(["x", "y", "z"], np.around(ras_coords, 3))}
192186

193187

194188
def _3d_in_file(in_file):

0 commit comments

Comments
 (0)