BUG: Use manager to set title
1 |
# emacs: -*- mode: python-mode; py-indent-offset: 4; indent-tabs-mode: nil -*-
|
|
2 |
# vi: set ft=python sts=4 ts=4 sw=4 et:
|
|
3 |
### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##
|
|
4 |
#
|
|
5 |
# See COPYING file distributed along with the NiBabel package for the
|
|
6 |
# copyright and license terms.
|
|
7 |
#
|
|
8 |
### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##
|
|
9 |
# module imports
|
|
10 | 33 |
""" Utilities to load and save image objects """
|
11 |
|
|
12 | 33 |
import os |
13 | 33 |
import numpy as np |
14 |
|
|
15 | 33 |
from .filename_parser import splitext_addext, _stringify_path |
16 | 33 |
from .openers import ImageOpener |
17 | 33 |
from .filebasedimages import ImageFileError |
18 | 33 |
from .imageclasses import all_image_classes |
19 | 33 |
from .arrayproxy import is_proxy |
20 | 33 |
from .deprecated import deprecate_with_version |
21 |
|
|
22 |
|
|
23 | 33 |
def load(filename, **kwargs): |
24 |
r""" Load file given filename, guessing at file type |
|
25 |
|
|
26 |
Parameters
|
|
27 |
----------
|
|
28 |
filename : str or os.PathLike
|
|
29 |
specification of file to load
|
|
30 |
\*\*kwargs : keyword arguments
|
|
31 |
Keyword arguments to format-specific load
|
|
32 |
|
|
33 |
Returns
|
|
34 |
-------
|
|
35 |
img : ``SpatialImage``
|
|
36 |
Image of guessed type
|
|
37 |
"""
|
|
38 | 33 |
filename = _stringify_path(filename) |
39 |
|
|
40 |
# Check file exists and is not empty
|
|
41 | 33 |
try: |
42 | 33 |
stat_result = os.stat(filename) |
43 | 33 |
except OSError: |
44 | 33 |
raise FileNotFoundError(f"No such file or no access: '{filename}'") |
45 |
if stat_result.st_size <= 0: |
|
46 | 33 |
raise ImageFileError(f"Empty file: '{filename}'") |
47 |
|
|
48 | 33 |
sniff = None |
49 |
for image_klass in all_image_classes: |
|
50 | 33 |
is_valid, sniff = image_klass.path_maybe_image(filename, sniff) |
51 |
if is_valid: |
|
52 | 33 |
img = image_klass.from_filename(filename, **kwargs) |
53 | 33 |
return img |
54 |
|
|
55 | 33 |
raise ImageFileError(f'Cannot work out file type of "{filename}"') |
56 |
|
|
57 |
|
|
58 | 33 |
@deprecate_with_version('guessed_image_type deprecated.', '3.2', '5.0') |
59 | 5 |
def guessed_image_type(filename): |
60 |
""" Guess image type from file `filename`
|
|
61 |
|
|
62 |
Parameters
|
|
63 |
----------
|
|
64 |
filename : str
|
|
65 |
File name containing an image
|
|
66 |
|
|
67 |
Returns
|
|
68 |
-------
|
|
69 |
image_class : class
|
|
70 |
Class corresponding to guessed image type
|
|
71 |
"""
|
|
72 | 33 |
sniff = None |
73 |
for image_klass in all_image_classes: |
|
74 | 33 |
is_valid, sniff = image_klass.path_maybe_image(filename, sniff) |
75 |
if is_valid: |
|
76 | 33 |
return image_klass |
77 |
|
|
78 |
raise ImageFileError(f'Cannot work out file type of "{filename}"') |
|
79 |
|
|
80 |
|
|
81 | 33 |
def save(img, filename): |
82 |
""" Save an image to file adapting format to `filename`
|
|
83 |
|
|
84 |
Parameters
|
|
85 |
----------
|
|
86 |
img : ``SpatialImage``
|
|
87 |
image to save
|
|
88 |
filename : str or os.PathLike
|
|
89 |
filename (often implying filenames) to which to save `img`.
|
|
90 |
|
|
91 |
Returns
|
|
92 |
-------
|
|
93 |
None
|
|
94 |
"""
|
|
95 | 33 |
filename = _stringify_path(filename) |
96 |
|
|
97 |
# Save the type as expected
|
|
98 | 33 |
try: |
99 | 33 |
img.to_filename(filename) |
100 | 33 |
except ImageFileError: |
101 | 33 |
pass
|
102 |
else: |
|
103 | 33 |
return
|
104 |
|
|
105 |
# Be nice to users by making common implicit conversions
|
|
106 | 33 |
froot, ext, trailing = splitext_addext(filename, ('.gz', '.bz2')) |
107 | 33 |
lext = ext.lower() |
108 |
|
|
109 |
# Special-case Nifti singles and Pairs
|
|
110 |
# Inline imports, as this module really shouldn't reference any image type
|
|
111 | 33 |
from .nifti1 import Nifti1Image, Nifti1Pair |
112 | 33 |
from .nifti2 import Nifti2Image, Nifti2Pair |
113 |
|
|
114 | 33 |
klass = None |
115 | 33 |
converted = None |
116 |
|
|
117 |
if type(img) == Nifti1Image and lext in ('.img', '.hdr'): |
|
118 | 33 |
klass = Nifti1Pair |
119 |
elif type(img) == Nifti2Image and lext in ('.img', '.hdr'): |
|
120 | 33 |
klass = Nifti2Pair |
121 |
elif type(img) == Nifti1Pair and lext == '.nii': |
|
122 | 33 |
klass = Nifti1Image |
123 |
elif type(img) == Nifti2Pair and lext == '.nii': |
|
124 | 33 |
klass = Nifti2Image |
125 |
else: # arbitrary conversion |
|
126 |
valid_klasses = [klass for klass in all_image_classes |
|
127 |
if ext in klass.valid_exts] |
|
128 |
if not valid_klasses: # if list is empty |
|
129 |
raise ImageFileError(f'Cannot work out file type of "{filename}"') |
|
130 |
|
|
131 |
# Got a list of valid extensions, but that's no guarantee
|
|
132 |
# the file conversion will work. So, try each image
|
|
133 |
# in order...
|
|
134 |
for klass in valid_klasses: |
|
135 | 33 |
try: |
136 | 33 |
converted = klass.from_image(img) |
137 | 33 |
break
|
138 | 33 |
except Exception as e: |
139 | 33 |
err = e |
140 |
# ... and if none of them work, raise an error.
|
|
141 |
if converted is None: |
|
142 | 33 |
raise err |
143 |
|
|
144 |
# Here, we either have a klass or a converted image.
|
|
145 |
if converted is None: |
|
146 | 33 |
converted = klass.from_image(img) |
147 | 33 |
converted.to_filename(filename) |
148 |
|
|
149 |
|
|
150 | 33 |
@deprecate_with_version('read_img_data deprecated. ' |
151 |
'Please use ``img.dataobj.get_unscaled()`` instead.', |
|
152 |
'3.2', |
|
153 |
'5.0') |
|
154 | 33 |
def read_img_data(img, prefer='scaled'): |
155 |
""" Read data from image associated with files
|
|
156 |
|
|
157 |
If you want unscaled data, please use ``img.dataobj.get_unscaled()``
|
|
158 |
instead. If you want scaled data, use ``img.get_fdata()`` (which will cache
|
|
159 |
the loaded array) or ``np.array(img.dataobj)`` (which won't cache the
|
|
160 |
array). If you want to load the data as for a modified header, save the
|
|
161 |
image with the modified header, and reload.
|
|
162 |
|
|
163 |
Parameters
|
|
164 |
----------
|
|
165 |
img : ``SpatialImage``
|
|
166 |
Image with valid image file in ``img.file_map``. Unlike the
|
|
167 |
``img.get_fdata()`` method, this function returns the data read
|
|
168 |
from the image file, as specified by the *current* image header
|
|
169 |
and *current* image files.
|
|
170 |
prefer : str, optional
|
|
171 |
Can be 'scaled' - in which case we return the data with the
|
|
172 |
scaling suggested by the format, or 'unscaled', in which case we
|
|
173 |
return, if we can, the raw data from the image file, without the
|
|
174 |
scaling applied.
|
|
175 |
|
|
176 |
Returns
|
|
177 |
-------
|
|
178 |
arr : ndarray
|
|
179 |
array as read from file, given parameters in header
|
|
180 |
|
|
181 |
Notes
|
|
182 |
-----
|
|
183 |
Summary: please use the ``get_data`` method of `img` instead of this
|
|
184 |
function unless you are sure what you are doing.
|
|
185 |
|
|
186 |
In general, you will probably prefer ``prefer='scaled'``, because
|
|
187 |
this gives the data as the image format expects to return it.
|
|
188 |
|
|
189 |
Use `prefer` == 'unscaled' with care; the modified Analyze-type
|
|
190 |
formats such as SPM formats, and nifti1, specify that the image data
|
|
191 |
array is given by the raw data on disk, multiplied by a scalefactor
|
|
192 |
and maybe with the addition of a constant. This function, with
|
|
193 |
``unscaled`` returns the data on the disk, without these
|
|
194 |
format-specific scalings applied. Please use this funciton only if
|
|
195 |
you absolutely need the unscaled data, and the magnitude of the
|
|
196 |
data, as given by the scalefactor, is not relevant to your
|
|
197 |
application. The Analyze-type formats have a single scalefactor +/-
|
|
198 |
offset per image on disk. If you do not care about the absolute
|
|
199 |
values, and will be removing the mean from the data, then the
|
|
200 |
unscaled values will have preserved intensity ratios compared to the
|
|
201 |
mean-centered scaled data. However, this is not necessarily true of
|
|
202 |
other formats with more complicated scaling - such as MINC.
|
|
203 |
"""
|
|
204 |
if prefer not in ('scaled', 'unscaled'): |
|
205 |
raise ValueError(f'Invalid string "{prefer}" for "prefer"') |
|
206 | 33 |
hdr = img.header |
207 |
if not hasattr(hdr, 'raw_data_from_fileobj'): |
|
208 |
# We can only do scaled
|
|
209 |
if prefer == 'unscaled': |
|
210 |
raise ValueError("Can only do unscaled for Analyze types") |
|
211 | 33 |
return np.array(img.dataobj) |
212 |
# Analyze types
|
|
213 | 33 |
img_fh = img.file_map['image'] |
214 | 33 |
img_file_like = (img_fh.filename if img_fh.fileobj is None |
215 |
else img_fh.fileobj) |
|
216 |
if img_file_like is None: |
|
217 | 33 |
raise ImageFileError('No image file specified for this image') |
218 |
# Check the consumable values in the header
|
|
219 | 33 |
hdr = img.header |
220 | 33 |
dao = img.dataobj |
221 | 33 |
default_offset = hdr.get_data_offset() == 0 |
222 | 33 |
default_scaling = hdr.get_slope_inter() == (None, None) |
223 |
# If we have a proxy object and the header has any consumed fields, we load
|
|
224 |
# the consumed values back from the proxy
|
|
225 |
if is_proxy(dao) and (default_offset or default_scaling): |
|
226 | 33 |
hdr = hdr.copy() |
227 |
if default_offset and dao.offset != 0: |
|
228 | 33 |
hdr.set_data_offset(dao.offset) |
229 |
if default_scaling and (dao.slope, dao.inter) != (1, 0): |
|
230 | 33 |
hdr.set_slope_inter(dao.slope, dao.inter) |
231 | 33 |
with ImageOpener(img_file_like) as fileobj: |
232 |
if prefer == 'scaled': |
|
233 | 33 |
return hdr.data_from_fileobj(fileobj) |
234 | 33 |
return hdr.raw_data_from_fileobj(fileobj) |
235 |
|
|
236 |
|
|
237 | 33 |
@deprecate_with_version('which_analyze_type deprecated.', '3.2', '4.0') |
238 | 5 |
def which_analyze_type(binaryblock): |
239 |
""" Is `binaryblock` from NIfTI1, NIfTI2 or Analyze header?
|
|
240 |
|
|
241 |
Parameters
|
|
242 |
----------
|
|
243 |
binaryblock : bytes
|
|
244 |
The `binaryblock` is 348 bytes that might be NIfTI1, NIfTI2, Analyze,
|
|
245 |
or None of the the above.
|
|
246 |
|
|
247 |
Returns
|
|
248 |
-------
|
|
249 |
hdr_type : str
|
|
250 |
* a nifti1 header (pair or single) -> return 'nifti1'
|
|
251 |
* a nifti2 header (pair or single) -> return 'nifti2'
|
|
252 |
* an Analyze header -> return 'analyze'
|
|
253 |
* None of the above -> return None
|
|
254 |
|
|
255 |
Notes
|
|
256 |
-----
|
|
257 |
Algorithm:
|
|
258 |
|
|
259 |
* read in the first 4 bytes from the file as 32-bit int ``sizeof_hdr``
|
|
260 |
* if ``sizeof_hdr`` is 540 or byteswapped 540 -> assume nifti2
|
|
261 |
* Check for 'ni1', 'n+1' magic -> assume nifti1
|
|
262 |
* if ``sizeof_hdr`` is 348 or byteswapped 348 assume Analyze
|
|
263 |
* Return None
|
|
264 |
"""
|
|
265 | 33 |
from .nifti1 import header_dtype |
266 | 33 |
hdr_struct = np.ndarray(shape=(), dtype=header_dtype, buffer=binaryblock) |
267 | 33 |
bs_hdr_struct = hdr_struct.byteswap() |
268 | 33 |
sizeof_hdr = hdr_struct['sizeof_hdr'] |
269 | 33 |
bs_sizeof_hdr = bs_hdr_struct['sizeof_hdr'] |
270 |
if 540 in (sizeof_hdr, bs_sizeof_hdr): |
|
271 | 33 |
return 'nifti2' |
272 |
if hdr_struct['magic'] in (b'ni1', b'n+1'): |
|
273 | 33 |
return 'nifti1' |
274 |
if 348 in (sizeof_hdr, bs_sizeof_hdr): |
|
275 | 33 |
return 'analyze' |
276 | 33 |
return None |
Read our documentation on viewing source code .