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 | 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 |
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 |
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 |
for dim in self._dims: |
|
62 |
if dim.spacing != b'regular__': |
|
63 |
raise ValueError('Irregular spacing not supported') |
|
64 |
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 |
if valid_range[0] < info.min or valid_range[1] > info.max: |
|
102 |
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 |
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 |
if holder.filename is None: |
|
164 |
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 |
if affine.shape != (4, 4): |
|
168 |
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 .