Skip to content

Commit 3f80565

Browse files
committed
RF+TEST: fix / test saving with output dtype
Test output dtype can come from data, header, or specified dtype.
1 parent fb6457b commit 3f80565

File tree

2 files changed

+90
-26
lines changed

2 files changed

+90
-26
lines changed

nipy/io/files.py

Lines changed: 29 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,10 @@
1313

1414
import os
1515

16+
import numpy as np
17+
1618
import nibabel as nib
19+
from nibabel.spatialimages import HeaderDataError
1720

1821
from ..core.image.image import is_image
1922

@@ -60,14 +63,17 @@ def save(img, filename, dtype_from='data'):
6063
filename : string
6164
Should be a valid filename.
6265
dtype_from : {'data', 'header'} or dtype specifier, optional
63-
dtype to save data to disk. Set into header before save as hint for
64-
output format to chose dtype to save to. Not every format supports every
65-
dtype, so some values of this parameter will raise errors. The default
66-
(`io_dtype` == None) is to use the dtype of the image data.
66+
Method of setting dtype to save data to disk. Value of 'data' (default),
67+
means use data dtype to save. 'header' means use data dtype specified
68+
in header, if available, otherwise use data dtype. Can also be any
69+
valid specifier for a numpy dtype, e.g. 'i4', ``np.float32``. Not every
70+
format supports every dtype, so some values of this parameter or data
71+
dtypes will raise errors.
6772
6873
Returns
6974
-------
7075
image : An `Image` object
76+
Possibly modified by saving.
7177
7278
See Also
7379
--------
@@ -102,7 +108,7 @@ def save(img, filename, dtype_from='data'):
102108
>>> saved_image3 = save_image(img, fname)
103109
Traceback (most recent call last):
104110
...
105-
ValueError: Cannot save file type "minc"
111+
ValueError: Sorry, we cannot yet save as format "minc"
106112
107113
Finally, we clear up our temporary files:
108114
@@ -119,27 +125,28 @@ def save(img, filename, dtype_from='data'):
119125
* SPM Analyze : ['.img', '.img.gz']
120126
"""
121127
# Try and get nifti
122-
ni_img = nipy2nifti(img)
128+
dt_from_is_str = isinstance(dtype_from, basestring)
129+
if dt_from_is_str and dtype_from == 'header':
130+
# All done
131+
io_dtype = None
132+
elif dt_from_is_str and dtype_from == 'data':
133+
io_dtype = img.get_data().dtype
134+
else:
135+
io_dtype = np.dtype(dtype_from)
136+
# make new image
137+
ni_img = nipy2nifti(img, data_dtype = io_dtype)
123138
ftype = _type_from_filename(filename)
124139
if ftype.startswith('nifti1'):
125-
saver = nib.nifti1.save
140+
ni_img.to_filename(filename)
126141
elif ftype == 'analyze':
127-
saver = nib.spm2analyze.save
142+
try:
143+
ana_img = nib.Spm2AnalyzeImage.from_image(ni_img)
144+
except HeaderDataError:
145+
raise HeaderDataError('SPM analyze does not support datatype %s' %
146+
ni_img.get_header().get_data_dtype())
147+
ana_img.to_filename(filename)
128148
else:
129-
raise ValueError('Cannot save file type "%s"' % ftype)
130-
dtype_from_str = isinstance(dtype_from, basestring)
131-
if dtype_from_str and dtype_from == 'header':
132-
saver(ni_img, filename)
133-
return img
134-
# Set output dtype if possible
135-
hdr = ni_img.get_header()
136-
if dtype_from_str and dtype_from == 'data':
137-
io_dtype = img.get_data().dtype
138-
else:
139-
io_dtype = dtype_from
140-
hdr.set_data_dtype(io_dtype)
141-
# make new image
142-
saver(ni_img, filename)
149+
raise ValueError('Sorry, we cannot yet save as format "%s"' % ftype)
143150
return img
144151

145152

nipy/io/tests/test_image_io.py

Lines changed: 61 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33

44
import numpy as np
55

6-
from nibabel.spatialimages import ImageFileError
6+
from nibabel.spatialimages import ImageFileError, HeaderDataError
7+
from nibabel import Nifti1Header
78

89
from ..api import load_image, save_image, as_image
910
from nipy.core.api import AffineTransform as AfT, Image, vox2mni
@@ -125,9 +126,65 @@ def test_scaling_io_dtype():
125126
del img
126127

127128

128-
def test_from_data():
129-
# Default data dtype comes from data
130-
pass
129+
def assert_dt_no_end_equal(a, b):
130+
""" Assert two numpy dtype specifiers are equal apart from byte order
131+
132+
Avoids failed comparison between int32 / int64 and intp
133+
"""
134+
a = np.dtype(a).newbyteorder('=')
135+
b = np.dtype(b).newbyteorder('=')
136+
assert_equal(a.str, b.str)
137+
138+
139+
def test_output_dtypes():
140+
shape = (4, 2, 3)
141+
rng = np.random.RandomState(19441217) # IN-S BD
142+
data = rng.normal(4, 20, size=shape)
143+
aff = np.diag([2.2, 3.3, 4.1, 1])
144+
cmap = vox2mni(aff)
145+
img = Image(data, cmap)
146+
fname_root = 'my_file'
147+
with InTemporaryDirectory():
148+
for ext in 'img', 'nii':
149+
out_fname = fname_root + '.' + ext
150+
# Default is for data to come from data dtype
151+
save_image(img, out_fname)
152+
img_back = load_image(out_fname)
153+
hdr = img_back.metadata['header']
154+
assert_dt_no_end_equal(hdr.get_data_dtype(), np.float)
155+
# All these types are OK for both output formats
156+
for out_dt in 'i2', 'i4', np.int16, '<f4', '>f8':
157+
# Specified output dtype
158+
save_image(img, out_fname, out_dt)
159+
img_back = load_image(out_fname)
160+
hdr = img_back.metadata['header']
161+
assert_dt_no_end_equal(hdr.get_data_dtype(), out_dt)
162+
# Output comes from data by default
163+
data_typed = data.astype(out_dt)
164+
img_again = Image(data_typed, cmap)
165+
save_image(img_again, out_fname)
166+
img_back = load_image(out_fname)
167+
hdr = img_back.metadata['header']
168+
assert_dt_no_end_equal(hdr.get_data_dtype(), out_dt)
169+
# Even if header specifies otherwise
170+
in_hdr = Nifti1Header()
171+
in_hdr.set_data_dtype(np.dtype('c8'))
172+
img_more = Image(data_typed, cmap, metadata={'header': in_hdr})
173+
save_image(img_more, out_fname)
174+
img_back = load_image(out_fname)
175+
hdr = img_back.metadata['header']
176+
assert_dt_no_end_equal(hdr.get_data_dtype(), out_dt)
177+
# But can come from header if specified
178+
save_image(img_more, out_fname, dtype_from='header')
179+
img_back = load_image(out_fname)
180+
hdr = img_back.metadata['header']
181+
assert_dt_no_end_equal(hdr.get_data_dtype(), 'c8')
182+
# u2 only OK for nifti
183+
save_image(img, 'my_file.nii', 'u2')
184+
img_back = load_image('my_file.nii')
185+
hdr = img_back.metadata['header']
186+
assert_dt_no_end_equal(hdr.get_data_dtype(), 'u2')
187+
assert_raises(HeaderDataError, save_image, img, 'my_file.img', 'u2')
131188

132189

133190
def test_header_roundtrip():

0 commit comments

Comments
 (0)