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 33
    if stat_result.st_size <= 0:
46 33
        raise ImageFileError(f"Empty file: '{filename}'")
47

48 33
    sniff = None
49 33
    for image_klass in all_image_classes:
50 33
        is_valid, sniff = image_klass.path_maybe_image(filename, sniff)
51 33
        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 33
    for image_klass in all_image_classes:
74 33
        is_valid, sniff = image_klass.path_maybe_image(filename, sniff)
75 33
        if is_valid:
76 33
            return image_klass
77

78 0
    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 33
    if type(img) == Nifti1Image and lext in ('.img', '.hdr'):
118 33
        klass = Nifti1Pair
119 33
    elif type(img) == Nifti2Image and lext in ('.img', '.hdr'):
120 33
        klass = Nifti2Pair
121 33
    elif type(img) == Nifti1Pair and lext == '.nii':
122 33
        klass = Nifti1Image
123 33
    elif type(img) == Nifti2Pair and lext == '.nii':
124 33
        klass = Nifti2Image
125
    else:  # arbitrary conversion
126 33
        valid_klasses = [klass for klass in all_image_classes
127
                         if ext in klass.valid_exts]
128 33
        if not valid_klasses:  # if list is empty
129 0
            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 33
        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 33
        if converted is None:
142 33
            raise err
143

144
    # Here, we either have a klass or a converted image.
145 33
    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 33
    if prefer not in ('scaled', 'unscaled'):
205 0
        raise ValueError(f'Invalid string "{prefer}" for "prefer"')
206 33
    hdr = img.header
207 33
    if not hasattr(hdr, 'raw_data_from_fileobj'):
208
        # We can only do scaled
209 33
        if prefer == 'unscaled':
210 0
            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 33
    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 33
    if is_proxy(dao) and (default_offset or default_scaling):
226 33
        hdr = hdr.copy()
227 33
        if default_offset and dao.offset != 0:
228 33
            hdr.set_data_offset(dao.offset)
229 33
        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 33
        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 33
    if 540 in (sizeof_hdr, bs_sizeof_hdr):
271 33
        return 'nifti2'
272 33
    if hdr_struct['magic'] in (b'ni1', b'n+1'):
273 33
        return 'nifti1'
274 33
    if 348 in (sizeof_hdr, bs_sizeof_hdr):
275 33
        return 'analyze'
276 33
    return None

Read our documentation on viewing source code .

Loading