Skip to content

Commit c8e9c34

Browse files
committed
BF+TST - update header from affines when needed
The update routine updates the header information from the affine only if the affine differs from the current estimate of the affine stored in the header. Add tests for header updating on save
1 parent d5357cb commit c8e9c34

File tree

5 files changed

+95
-15
lines changed

5 files changed

+95
-15
lines changed

nibabel/analyze.py

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1053,12 +1053,20 @@ def update_header(self):
10531053
(1.0, 2.0, 3.0)
10541054
'''
10551055
hdr = self._header
1056+
# We need to update the header if the data shape has changed. It's a
1057+
# bit difficult to change the data shape using the standard API, but
1058+
# maybe it happened
10561059
if not self._data is None:
10571060
hdr.set_data_shape(self._data.shape)
1058-
if not self._affine is None:
1059-
RZS = self._affine[:3, :3]
1060-
vox = np.sqrt(np.sum(RZS * RZS, axis=0))
1061-
hdr['pixdim'][1:4] = vox
1061+
# If the affine is not None, and it is different from the main affine in
1062+
# the header, update the heaader
1063+
if self._affine is None:
1064+
return
1065+
if np.all(self._affine == hdr.get_best_affine()):
1066+
return
1067+
RZS = self._affine[:3, :3]
1068+
vox = np.sqrt(np.sum(RZS * RZS, axis=0))
1069+
hdr['pixdim'][1:4] = vox
10621070

10631071

10641072
load = AnalyzeImage.load

nibabel/nifti1.py

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1428,14 +1428,16 @@ def update_header(self):
14281428
super(Nifti1Pair, self).update_header()
14291429
hdr = self._header
14301430
hdr['magic'] = 'ni1'
1431-
if not self._affine is None:
1432-
# Set affine into sform
1433-
hdr.set_sform(self._affine, code='aligned')
1434-
# Make qform 'unknown', set voxel sizes from affine
1435-
hdr['qform_code'] = 0
1436-
RZS = self._affine[:3, :3]
1437-
zooms = np.sqrt(np.sum(RZS * RZS, axis=0))
1438-
hdr['pixdim'][1:4] = zooms
1431+
# If the affine is not None, and it is different from the main affine in
1432+
# the header, update the heaader
1433+
if self._affine is None:
1434+
return
1435+
if np.all(self._affine == hdr.get_best_affine()):
1436+
return
1437+
# Set affine into sform with default code
1438+
hdr.set_sform(self._affine, code='aligned')
1439+
# Make qform 'unknown'
1440+
hdr.set_qform(self._affine, code='unknown')
14391441

14401442

14411443
class Nifti1Image(Nifti1Pair):

nibabel/tests/test_analyze.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -532,6 +532,37 @@ def test_affine_44(self):
532532
# Not OK - affine wrong shape
533533
assert_raises(ValueError, IC, data, np.diag([2, 3, 4]))
534534

535+
def test_header_updating(self):
536+
# Only update on changes
537+
img_klass = self.image_class
538+
# With a None affine - don't overwrite zooms
539+
img = img_klass(np.zeros((2,3,4)), None)
540+
hdr = img.get_header()
541+
hdr.set_zooms((4,5,6))
542+
# Save / reload using bytes IO objects
543+
for key, value in img.file_map.items():
544+
value.fileobj = BytesIO()
545+
img.to_file_map()
546+
hdr_back = img.from_file_map(img.file_map).get_header()
547+
assert_array_equal(hdr_back.get_zooms(), (4,5,6))
548+
# With a real affine, update zooms
549+
img = img_klass(np.zeros((2,3,4)), np.diag([2,3,4,1]), hdr)
550+
hdr = img.get_header()
551+
assert_array_equal(hdr.get_zooms(), (2, 3, 4))
552+
# Modify affine in-place? Update on save.
553+
img.get_affine()[0,0] = 9
554+
for key, value in img.file_map.items():
555+
value.fileobj = BytesIO()
556+
img.to_file_map()
557+
hdr_back = img.from_file_map(img.file_map).get_header()
558+
assert_array_equal(hdr.get_zooms(), (9, 3, 4))
559+
# Modify data in-place? Update on save
560+
data = img.get_data()
561+
data.shape = (3, 2, 4)
562+
img.to_file_map()
563+
img_back = img.from_file_map(img.file_map)
564+
assert_array_equal(img_back.shape, (3, 2, 4))
565+
535566

536567
def test_unsupported():
537568
# analyze does not support uint32

nibabel/tests/test_nifti1.py

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -174,11 +174,46 @@ def test_binblock_is_file(self):
174174

175175

176176
class TestNifti1Image(tana.TestAnalyzeImage):
177-
# class for testing images
177+
# Run analyze-flavor spatialimage tests
178178
image_class = Nifti1Image
179179

180-
181-
class TestNifti1Pair(tana.TestAnalyzeImage):
180+
def _qform_rt(self, img):
181+
# Round trip image after setting qform, sform codes
182+
hdr = img.get_header()
183+
hdr['qform_code'] = 3
184+
hdr['sform_code'] = 4
185+
# Save / reload using bytes IO objects
186+
for key, value in img.file_map.items():
187+
value.fileobj = BytesIO()
188+
img.to_file_map()
189+
return img.from_file_map(img.file_map)
190+
191+
def test_qform_cycle(self):
192+
# Qform load save cycle
193+
img_klass = self.image_class
194+
# None affine
195+
img = img_klass(np.zeros((2,3,4)), None)
196+
hdr_back = self._qform_rt(img).get_header()
197+
assert_equal(hdr_back['qform_code'], 3)
198+
assert_equal(hdr_back['sform_code'], 4)
199+
# Try non-None affine
200+
img = img_klass(np.zeros((2,3,4)), np.eye(4))
201+
hdr_back = self._qform_rt(img).get_header()
202+
assert_equal(hdr_back['qform_code'], 3)
203+
assert_equal(hdr_back['sform_code'], 4)
204+
# Modify affine in-place - does it hold?
205+
img.get_affine()[0,0] = 9
206+
img.to_file_map()
207+
img_back = img.from_file_map(img.file_map)
208+
exp_aff = np.diag([9,1,1,1])
209+
assert_array_equal(img_back.get_affine(), exp_aff)
210+
hdr_back = img.get_header()
211+
assert_array_equal(hdr_back.get_sform(), exp_aff)
212+
assert_array_equal(hdr_back.get_qform(), exp_aff)
213+
214+
215+
class TestNifti1Pair(TestNifti1Image):
216+
# Run analyze-flavor spatialimage tests
182217
image_class = Nifti1Pair
183218

184219

nibabel/tests/test_spm99analyze.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,10 @@ class TestSpm99AnalyzeImage(test_analyze.TestAnalyzeImage):
105105
test_analyze.TestAnalyzeImage.test_data_hdr_cache
106106
))
107107

108+
test_header_updating = (scipy_skip(
109+
test_analyze.TestAnalyzeImage.test_header_updating
110+
))
111+
108112
@scipy_skip
109113
def test_mat_read(self):
110114
# Test mat file reading and writing for the SPM analyze types

0 commit comments

Comments
 (0)