Skip to content

Commit 8899bc7

Browse files
committed
RF: Add units parameter to set_zooms
- Switch 'canonical' to 'norm' for brevity
1 parent 27234c3 commit 8899bc7

File tree

8 files changed

+203
-114
lines changed

8 files changed

+203
-114
lines changed

nibabel/analyze.py

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -399,7 +399,7 @@ def from_header(klass, header=None, check=True):
399399
f"but output header {klass} does not support it")
400400
obj.set_data_dtype(header.get_data_dtype())
401401
obj.set_data_shape(header.get_data_shape())
402-
obj.set_zooms(header.get_zooms(units='raw'))
402+
obj.set_zooms(header.get_zooms(units='raw'), units='raw')
403403
if check:
404404
obj.check_fix()
405405
return obj
@@ -661,16 +661,16 @@ def get_base_affine(self):
661661

662662
get_best_affine = get_base_affine
663663

664-
def get_zooms(self, units='canonical', raise_unknown=False):
664+
def get_zooms(self, units='norm', raise_unknown=False):
665665
""" Get zooms (spacing between voxels along each axis) from header
666666
667667
Parameters
668668
----------
669-
units : {'canonical', 'raw'}, optional
670-
Return zooms in "canonical" units of mm/sec for spatial/temporal or
669+
units : {'norm', 'raw'}, optional
670+
Return zooms in normalized units of mm/sec for spatial/temporal or
671671
as raw values stored in header.
672672
raise_unkown : bool, optional
673-
If canonical units are requested and the units are ambiguous, raise
673+
If normalized units are requested and the units are ambiguous, raise
674674
a ``ValueError``
675675
676676
Returns
@@ -698,10 +698,19 @@ def get_zooms(self, units='canonical', raise_unknown=False):
698698
pixdims = hdr['pixdim']
699699
return tuple(pixdims[1:ndim + 1])
700700

701-
def set_zooms(self, zooms):
701+
def set_zooms(self, zooms, units='norm'):
702702
""" Set zooms into header fields
703703
704704
See docstring for ``get_zooms`` for examples
705+
706+
Parameters
707+
----------
708+
zooms : sequence of floats
709+
Zoom values to set in header
710+
units : {'norm', 'raw'}, optional
711+
Zooms are specified in normalized units of mm/sec for
712+
spatial/temporal or as raw values to be interpreted according to
713+
format specification.
705714
"""
706715
hdr = self._structarr
707716
dims = hdr['dim']
@@ -715,10 +724,6 @@ def set_zooms(self, zooms):
715724
pixdims = hdr['pixdim']
716725
pixdims[1:ndim + 1] = zooms[:]
717726

718-
def set_norm_zooms(self, zooms):
719-
''' Set zooms in mm/s units '''
720-
return self.set_zooms(zooms)
721-
722727
def as_analyze_map(self):
723728
""" Return header as mapping for conversion to Analyze types
724729

nibabel/freesurfer/mghformat.py

Lines changed: 28 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -237,7 +237,7 @@ def _ndims(self):
237237
"""
238238
return 3 + (self._structarr['dims'][3] > 1)
239239

240-
def get_zooms(self, units='canonical', raise_unknown=False):
240+
def get_zooms(self, units='norm', raise_unknown=False):
241241
""" Get zooms from header
242242
243243
Returns the spacing of voxels in the x, y, and z dimensions.
@@ -246,18 +246,18 @@ def get_zooms(self, units='canonical', raise_unknown=False):
246246
TR is stored in milliseconds (see `The MGH/MGZ Volume Format
247247
<mghformat>`_),
248248
so if ``units == 'raw'``, the fourth zoom will be in ms.
249-
If ``units == 'canonical'`` (default), the fourth zoom will be in
249+
If ``units == 'norm'`` (default), the fourth zoom will be in
250250
seconds.
251251
252252
To access only the spatial zooms, use `hdr['delta']`.
253253
254254
Parameters
255255
----------
256-
units : {'canonical', 'raw'}, optional
257-
Return zooms in "canonical" units of mm/sec for spatial/temporal or
256+
units : {'norm', 'raw'}, optional
257+
Return zooms in normalized units of mm/sec for spatial/temporal or
258258
as raw values stored in header.
259259
raise_unkown : bool, optional
260-
If canonical units are requested and the units are ambiguous, raise
260+
If normalized units are requested and the units are ambiguous, raise
261261
a ``ValueError``
262262
263263
Returns
@@ -267,29 +267,40 @@ def get_zooms(self, units='canonical', raise_unknown=False):
267267
268268
.. _mghformat: https://surfer.nmr.mgh.harvard.edu/fswiki/FsTutorial/MghFormat#line-82
269269
"""
270-
if units == 'canonical':
270+
if units == 'norm':
271271
tfactor = 0.001
272272
elif units == 'raw':
273273
tfactor = 1
274274
else:
275-
raise ValueError("`units` parameter must be 'canonical' or 'raw'")
275+
raise ValueError("`units` parameter must be 'norm' or 'raw'")
276276

277277
# Do not return time zoom (TR) if 3D image
278278
tzoom = (self['tr'] * tfactor,) if self._ndims() > 3 else ()
279279
return tuple(self._structarr['delta']) + tzoom
280280

281-
def set_zooms(self, zooms):
281+
def set_zooms(self, zooms, units='norm'):
282282
""" Set zooms into header fields
283283
284284
Sets the spacing of voxels in the x, y, and z dimensions.
285-
For four-dimensional files, a temporal zoom (repetition time, or TR, in
286-
ms) may be provided as a fourth sequence element.
285+
For four-dimensional files, a temporal zoom (repetition time, or TR)
286+
may be provided as a fourth sequence element.
287+
288+
TR is stored in milliseconds (see `The MGH/MGZ Volume Format
289+
<mghformat>`_),
290+
so if ``units == 'raw'``, the fourth zoom will be interpreted as ms
291+
and stored unmodified.
292+
If ``units == 'norm'`` (default), the fourth zoom will be interpreted
293+
as seconds, and converted to ms before storing in the header.
287294
288295
Parameters
289296
----------
290297
zooms : sequence
291298
sequence of floats specifying spatial and (optionally) temporal
292299
zooms
300+
units : {'norm', 'raw'}, optional
301+
Zooms are specified in normalized units of mm/sec for
302+
spatial/temporal dimensions or as raw values to be stored in
303+
header.
293304
"""
294305
hdr = self._structarr
295306
zooms = np.asarray(zooms)
@@ -303,13 +314,13 @@ def set_zooms(self, zooms):
303314
if len(zooms) == 4:
304315
if zooms[3] < 0:
305316
raise HeaderDataError(f'TR must be non-negative; got {zooms[3]}')
306-
hdr['tr'] = zooms[3]
307-
308-
def set_norm_zooms(self, zooms):
309-
if len(zooms) == 4:
310-
zooms = zooms[:3] + (zooms[3] * 1000,)
311-
312-
self.set_zooms(zooms)
317+
if units == 'norm':
318+
tfactor = 1000
319+
elif units == 'raw':
320+
tfactor = 1
321+
else:
322+
raise ValueError("`units` parameter must be 'norm' or 'raw'")
323+
hdr['tr'] = zooms[3] * tfactor
313324

314325
def get_data_shape(self):
315326
""" Get shape of data

nibabel/freesurfer/tests/test_mghformat.py

Lines changed: 18 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -70,11 +70,12 @@ def test_read_mgh():
7070
assert h['dof'] == 0
7171
assert h['goodRASFlag'] == 1
7272
assert_array_equal(h['dims'], [3, 4, 5, 2])
73-
assert_almost_equal(h['tr'], 2.0)
73+
assert_almost_equal(h['tr'], 2)
7474
assert_almost_equal(h['flip_angle'], 0.0)
7575
assert_almost_equal(h['te'], 0.0)
7676
assert_almost_equal(h['ti'], 0.0)
77-
assert_array_almost_equal(h.get_zooms(), [1, 1, 1, 2])
77+
assert_array_almost_equal(h.get_zooms(units='raw'), [1, 1, 1, 2])
78+
assert_array_almost_equal(h.get_zooms(units='norm'), [1, 1, 1, 0.002])
7879
assert_array_almost_equal(h.get_vox2ras(), v2r)
7980
assert_array_almost_equal(h.get_vox2ras_tkr(), v2rtkr)
8081

@@ -147,7 +148,7 @@ def test_write_noaffine_mgh():
147148
def test_set_zooms():
148149
mgz = load(MGZ_FNAME)
149150
h = mgz.header
150-
assert_array_almost_equal(h.get_zooms(), [1, 1, 1, 2])
151+
assert_array_almost_equal(h.get_zooms(), [1, 1, 1, 0.002])
151152
h.set_zooms([1, 1, 1, 3])
152153
assert_array_almost_equal(h.get_zooms(), [1, 1, 1, 3])
153154
for zooms in ((-1, 1, 1, 1),
@@ -359,28 +360,28 @@ def test_zooms_edge_cases(self):
359360

360361
assert_array_almost_equal(img.header.get_zooms(units='raw'),
361362
(1, 1, 1, 0))
362-
assert_array_almost_equal(img.header.get_zooms(units='canonical'),
363+
assert_array_almost_equal(img.header.get_zooms(units='norm'),
363364
(1, 1, 1, 0))
364365

365-
img.header.set_zooms((1, 1, 1, 2000))
366+
img.header.set_zooms((1, 1, 1, 2000), units='raw')
366367
assert_array_almost_equal(img.header.get_zooms(units='raw'),
367368
(1, 1, 1, 2000))
368-
assert_array_almost_equal(img.header.get_zooms(units='canonical'),
369+
assert_array_almost_equal(img.header.get_zooms(units='norm'),
369370
(1, 1, 1, 2))
370371
assert_array_almost_equal(img.header.get_zooms(), (1, 1, 1, 2))
371372

372-
img.header.set_norm_zooms((2, 2, 2, 3))
373+
img.header.set_zooms((2, 2, 2, 3), units='norm')
373374
assert_array_almost_equal(img.header.get_zooms(units='raw'),
374375
(2, 2, 2, 3000))
375-
assert_array_almost_equal(img.header.get_zooms(units='canonical'),
376+
assert_array_almost_equal(img.header.get_zooms(units='norm'),
376377
(2, 2, 2, 3))
377378
assert_array_almost_equal(img.header.get_zooms(), (2, 2, 2, 3))
378379

379380
# It's legal to set zooms for spatial dimensions only
380-
img.header.set_norm_zooms((3, 3, 3))
381+
img.header.set_zooms((3, 3, 3), units='norm')
381382
assert_array_almost_equal(img.header.get_zooms(units='raw'),
382383
(3, 3, 3, 3000))
383-
assert_array_almost_equal(img.header.get_zooms(units='canonical'),
384+
assert_array_almost_equal(img.header.get_zooms(units='norm'),
384385
(3, 3, 3, 3))
385386
assert_array_almost_equal(img.header.get_zooms(), (3, 3, 3, 3))
386387

@@ -389,24 +390,24 @@ def test_zooms_edge_cases(self):
389390

390391
assert_array_almost_equal(img.header.get_zooms(units='raw'),
391392
(1, 1, 1))
392-
assert_array_almost_equal(img.header.get_zooms(units='canonical'),
393+
assert_array_almost_equal(img.header.get_zooms(units='norm'),
393394
(1, 1, 1))
394395

395-
img.header.set_zooms((2, 2, 2))
396+
img.header.set_zooms((2, 2, 2), units='raw')
396397
assert_array_almost_equal(img.header.get_zooms(units='raw'),
397398
(2, 2, 2))
398-
assert_array_almost_equal(img.header.get_zooms(units='canonical'),
399+
assert_array_almost_equal(img.header.get_zooms(units='norm'),
399400
(2, 2, 2))
400401

401-
img.header.set_norm_zooms((3, 3, 3))
402+
img.header.set_zooms((3, 3, 3), units='norm')
402403
assert_array_almost_equal(img.header.get_zooms(units='raw'),
403404
(3, 3, 3))
404-
assert_array_almost_equal(img.header.get_zooms(units='canonical'),
405+
assert_array_almost_equal(img.header.get_zooms(units='norm'),
405406
(3, 3, 3))
406407

407408
# Cannot set TR as zoom for 3D image
408-
assert_raises(HeaderDataError, img.header.set_zooms, (4, 4, 4, 5))
409-
assert_raises(HeaderDataError, img.header.set_norm_zooms, (4, 4, 4, 5))
409+
assert_raises(HeaderDataError, img.header.set_zooms, (4, 4, 4, 5), 'raw')
410+
assert_raises(HeaderDataError, img.header.set_zooms, (4, 4, 4, 5), 'norm')
410411

411412

412413
class TestMGHHeader(tws._TestLabeledWrapStruct):

0 commit comments

Comments
 (0)