/usr/share/pyshared/nibabel/minc.py is in python-nibabel 1.3.0-2.
This file is owned by root:root, with mode 0o644.
The actual contents of the file can be viewed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 | # emacs: -*- mode: python-mode; py-indent-offset: 4; indent-tabs-mode: nil -*-
# vi: set ft=python sts=4 ts=4 sw=4 et:
### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##
#
# See COPYING file distributed along with the NiBabel package for the
# copyright and license terms.
#
### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##
import numpy as np
from .externals.netcdf import netcdf_file
from .py3k import asbytes, asstr
from .spatialimages import SpatialImage
_dt_dict = {
('b','unsigned'): np.uint8,
('b','signed__'): np.int8,
('c','unsigned'): 'S1',
('h','unsigned'): np.uint16,
('h','signed__'): np.int16,
('i','unsigned'): np.uint32,
('i','signed__'): np.int32,
}
# See http://www.bic.mni.mcgill.ca/software/minc/minc1_format/node15.html
_default_dir_cos = {
'xspace': [1,0,0],
'yspace': [0,1,0],
'zspace': [0,0,1]}
class MincError(Exception):
pass
class MincFile(object):
''' Class to wrap MINC file
Although it has some of the same methods as a ``Header``, we use
this only when reading a MINC file, to pull out useful header
information, and for the method of reading the data out
'''
def __init__(self, mincfile):
self._mincfile = mincfile
self._image = mincfile.variables['image']
self._dim_names = self._image.dimensions
# The code below will error with vector_dimensions. See:
# http://www.bic.mni.mcgill.ca/software/minc/minc1_format/node3.html
# http://www.bic.mni.mcgill.ca/software/minc/prog_guide/node11.html
self._dims = [self._mincfile.variables[s]
for s in self._dim_names]
# We don't currently support irregular spacing
# http://www.bic.mni.mcgill.ca/software/minc/minc1_format/node15.html
for dim in self._dims:
if dim.spacing != asbytes('regular__'):
raise ValueError('Irregular spacing not supported')
self._spatial_dims = [name for name in self._dim_names
if name.endswith('space')]
def get_data_dtype(self):
typecode = self._image.typecode()
if typecode == 'f':
dtt = np.dtype(np.float32)
elif typecode == 'd':
dtt = np.dtype(np.float64)
else:
signtype = asstr(self._image.signtype)
dtt = _dt_dict[(typecode, signtype)]
return np.dtype(dtt).newbyteorder('>')
def get_data_shape(self):
return self._image.data.shape
def get_zooms(self):
return tuple(
[float(dim.step) for dim in self._dims])
def get_affine(self):
nspatial = len(self._spatial_dims)
rot_mat = np.eye(nspatial)
steps = np.zeros((nspatial,))
starts = np.zeros((nspatial,))
dim_names = list(self._dim_names) # for indexing in loop
for i, name in enumerate(self._spatial_dims):
dim = self._dims[dim_names.index(name)]
try:
dir_cos = dim.direction_cosines
except AttributeError:
dir_cos = _default_dir_cos[name]
rot_mat[:, i] = dir_cos
steps[i] = dim.step
starts[i] = dim.start
origin = np.dot(rot_mat, starts)
aff = np.eye(nspatial+1)
aff[:nspatial, :nspatial] = rot_mat * steps
aff[:nspatial, nspatial] = origin
return aff
def _get_valid_range(self):
''' Return valid range for image data
The valid range can come from the image 'valid_range' or
image 'valid_min' and 'valid_max', or, failing that, from the
data type range
'''
ddt = self.get_data_dtype()
info = np.iinfo(ddt.type)
try:
valid_range = self._image.valid_range
except AttributeError:
try:
valid_range = [self._image.valid_min,
self._image.valid_max]
except AttributeError:
valid_range = [info.min, info.max]
if valid_range[0] < info.min or valid_range[1] > info.max:
raise ValueError('Valid range outside input '
'data type range')
return np.asarray(valid_range, dtype=np.float)
def _normalize(self, data):
""" Scale image data with recorded scalefactors
http://www.bic.mni.mcgill.ca/software/minc/prog_guide/node13.html
MINC normalization uses "image-min" and "image-max" variables to
map the data from the valid range of the image to the range
specified by "image-min" and "image-max".
The "image-max" and "image-min" are variables that describe the
"max" and "min" of image over some dimensions of "image".
The usual case is that "image" has dimensions ["zspace",
"yspace", "xspace"] and "image-max" has dimensions
["zspace"].
"""
ddt = self.get_data_dtype()
if ddt.type in np.sctypes['float']:
return data
# the MINC standard appears to allow the following variables to
# be undefined.
# http://www.bic.mni.mcgill.ca/software/minc/minc1_format/node16.html
# It wasn't immediately obvious what the defaults were.
image_max = self._mincfile.variables['image-max']
image_min = self._mincfile.variables['image-min']
if image_max.dimensions != image_min.dimensions:
raise MincError('"image-max" and "image-min" do not '
'have the same dimensions')
nscales = len(image_max.dimensions)
if image_max.dimensions != self._dim_names[:nscales]:
raise MincError('image-max and image dimensions '
'do not match')
dmin, dmax = self._get_valid_range()
if nscales == 0:
imax = np.asarray(image_max)
imin = np.asarray(image_min)
sc = (imax-imin) / (dmax-dmin)
return np.clip(data, dmin, dmax) * sc + (imin - dmin * sc)
out_data = np.empty(data.shape, np.float)
def _norm_slice(sdef):
imax = image_max[sdef]
imin = image_min[sdef]
in_data = np.clip(data[sdef], dmin, dmax)
sc = (imax-imin) / (dmax-dmin)
return in_data * sc + (imin - dmin * sc)
if nscales == 1:
for i in range(data.shape[0]):
out_data[i] = _norm_slice(i)
elif nscales == 2:
for i in range(data.shape[0]):
for j in range(data.shape[1]):
out_data[i, j] = _norm_slice((i,j))
else:
raise MincError('More than two scaling dimensions')
return out_data
def get_scaled_data(self):
dtype = self.get_data_dtype()
data = np.asarray(self._image.data).view(dtype)
return self._normalize(data)
class MincImage(SpatialImage):
''' Class for MINC images
The MINC image class uses the default header type, rather than a
specific MINC header type - and reads the relevant information from
the MINC file on load.
'''
files_types = (('image', '.mnc'),)
_compressed_exts = ('.gz', '.bz2')
class ImageArrayProxy(object):
''' Minc implemention of array proxy protocol
The array proxy allows us to freeze the passed fileobj and
header such that it returns the expected data array.
'''
def __init__(self, minc_file):
self.minc_file = minc_file
self._data = None
self.shape = minc_file.get_data_shape()
def __array__(self):
''' Cached read of data from file '''
if self._data is None:
self._data = self.minc_file.get_scaled_data()
return self._data
@classmethod
def from_file_map(klass, file_map):
fobj = file_map['image'].get_prepare_fileobj()
minc_file = MincFile(netcdf_file(fobj))
affine = minc_file.get_affine()
if affine.shape != (4, 4):
raise MincError('Image does not have 3 spatial dimensions')
data_dtype = minc_file.get_data_dtype()
shape = minc_file.get_data_shape()
zooms = minc_file.get_zooms()
header = klass.header_class(data_dtype, shape, zooms)
data = klass.ImageArrayProxy(minc_file)
return MincImage(data, affine, header, extra=None, file_map=file_map)
load = MincImage.load
|