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 33
""" Preliminary MINC2 support
10

11
Use with care; I haven't tested this against a wide range of MINC files.
12

13
If you have a file that isn't read correctly, please send an example.
14

15
Test reading with something like::
16

17
    import nibabel as nib
18
    img = nib.load('my_funny.mnc')
19
    data = img.get_fdata()
20
    print(data.mean())
21
    print(data.max())
22
    print(data.min())
23

24
and compare against command line output of::
25

26
    mincstats my_funny.mnc
27
"""
28 33
import numpy as np
29

30 33
from .minc1 import Minc1File, MincHeader, Minc1Image, MincError
31

32

33 33
class Hdf5Bunch(object):
34
    """ Make object for accessing attributes of variable
35
    """
36

37 33
    def __init__(self, var):
38 33
        for name, value in var.attrs.items():
39 21
            setattr(self, name, value)
40

41

42 33
class Minc2File(Minc1File):
43
    """ Class to wrap MINC2 format file
44

45
    Although it has some of the same methods as a ``Header``, we use
46
    this only when reading a MINC2 file, to pull out useful header
47
    information, and for the method of reading the data out
48
    """
49

50 33
    def __init__(self, mincfile):
51 21
        self._mincfile = mincfile
52 21
        minc_part = mincfile['minc-2.0']
53
        # The whole image is the first of the entries in 'image'
54 21
        image = minc_part['image']['0']
55 21
        self._image = image['image']
56 21
        self._dim_names = self._get_dimensions(self._image)
57 21
        dimensions = minc_part['dimensions']
58 33
        self._dims = [Hdf5Bunch(dimensions[s]) for s in self._dim_names]
59
        # We don't currently support irregular spacing
60
        # https://en.wikibooks.org/wiki/MINC/Reference/MINC2.0_File_Format_Reference#Dimension_variable_attributes
61 33
        for dim in self._dims:
62 33
            if dim.spacing != b'regular__':
63 0
                raise ValueError('Irregular spacing not supported')
64 33
        self._spatial_dims = [name for name in self._dim_names
65
                              if name.endswith('space')]
66 21
        self._image_max = image['image-max']
67 21
        self._image_min = image['image-min']
68

69 33
    def _get_dimensions(self, var):
70
        # Dimensions for a particular variable
71
        # Differs for MINC1 and MINC2 - see:
72
        # https://en.wikibooks.org/wiki/MINC/Reference/MINC2.0_File_Format_Reference#Associating_HDF5_dataspaces_with_MINC_dimensions
73 21
        try:
74 21
            dimorder = var.attrs['dimorder'].decode()
75 21
        except KeyError:  # No specified dimensions
76 21
            return []
77
        # The dimension name list must contain only as many entries
78
        # as the variable has dimensions. This reduces errors when an
79
        # unnecessary dimorder attribute is left behind.
80 21
        return dimorder.split(',')[:len(var.shape)]
81

82 33
    def get_data_dtype(self):
83 21
        return self._image.dtype
84

85 33
    def get_data_shape(self):
86 21
        return self._image.shape
87

88 33
    def _get_valid_range(self):
89
        """ Return valid range for image data
90

91
        The valid range can come from the image 'valid_range' or
92
        failing that, from the data type range
93
        """
94 21
        ddt = self.get_data_dtype()
95 21
        info = np.iinfo(ddt.type)
96 21
        try:
97 21
            valid_range = self._image.attrs['valid_range']
98 21
        except (AttributeError, KeyError):
99 21
            valid_range = [info.min, info.max]
100
        else:
101 33
            if valid_range[0] < info.min or valid_range[1] > info.max:
102 0
                raise ValueError('Valid range outside input '
103
                                 'data type range')
104 21
        return np.asarray(valid_range, dtype=np.float)
105

106 33
    def _get_scalar(self, var):
107
        """ Get scalar value from HDF5 scalar """
108 21
        return var[()]
109

110 33
    def _get_array(self, var):
111
        """ Get array from HDF5 array """
112 21
        return np.asanyarray(var)
113

114 33
    def get_scaled_data(self, sliceobj=()):
115
        """ Return scaled data for slice definition `sliceobj`
116

117
        Parameters
118
        ----------
119
        sliceobj : tuple, optional
120
            slice definition. If not specified, return whole array
121

122
        Returns
123
        -------
124
        scaled_arr : array
125
            array from minc file with scaling applied
126
        """
127 33
        if sliceobj == ():
128 21
            raw_data = np.asanyarray(self._image)
129
        else:  # Try slicing into the HDF array (maybe it's possible)
130 21
            try:
131 21
                raw_data = self._image[sliceobj]
132 21
            except (ValueError, TypeError):
133 21
                raw_data = np.asanyarray(self._image)[sliceobj]
134
            else:
135 21
                raw_data = np.asanyarray(raw_data)
136 21
        return self._normalize(raw_data, sliceobj)
137

138

139 33
class Minc2Header(MincHeader):
140

141 33
    @classmethod
142 5
    def may_contain_header(klass, binaryblock):
143 33
        return binaryblock[:4] == b'\211HDF'
144

145

146 33
class Minc2Image(Minc1Image):
147
    """ Class for MINC2 images
148

149
    The MINC2 image class uses the default header type, rather than a
150
    specific MINC header type - and reads the relevant information from
151
    the MINC file on load.
152
    """
153
    # MINC2 does not do compressed whole files
154 33
    _compressed_suffixes = ()
155 33
    header_class = Minc2Header
156

157 33
    @classmethod
158 33
    def from_file_map(klass, file_map, *, mmap=True, keep_file_open=None):
159
        # Import of h5py might take awhile for MPI-enabled builds
160
        # So we are importing it here "on demand"
161 33
        from ._h5py_compat import h5py
162 33
        holder = file_map['image']
163 33
        if holder.filename is None:
164 0
            raise MincError('MINC2 needs filename for load')
165 33
        minc_file = Minc2File(h5py.File(holder.filename, 'r'))
166 21
        affine = minc_file.get_affine()
167 33
        if affine.shape != (4, 4):
168 0
            raise MincError('Image does not have 3 spatial dimensions')
169 21
        data_dtype = minc_file.get_data_dtype()
170 21
        shape = minc_file.get_data_shape()
171 21
        zooms = minc_file.get_zooms()
172 21
        header = klass.header_class(data_dtype, shape, zooms)
173 21
        data = klass.ImageArrayProxy(minc_file)
174 21
        return klass(data, affine, header, extra=None, file_map=file_map)
175

176

177 33
load = Minc2Image.load

Read our documentation on viewing source code .

Loading