Skip to content

Commit 87afc75

Browse files
Merge pull request #493 from rdvincent/rdv-minc-reader-fixes
MRG: Miscellaneous MINC reader fixes This PR is a potpourri of small fixes for the MINC1 and MINC2 readers.
2 parents ec4567f + 77cf9bb commit 87afc75

File tree

8 files changed

+76
-14
lines changed

8 files changed

+76
-14
lines changed

nibabel/externals/netcdf.py

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,14 @@ def __init__(self, filename, mode='r', mmap=None, version=1):
210210
self.fp = open(self.filename, '%sb' % mode)
211211
if mmap is None:
212212
mmap = True
213+
try:
214+
self.fp.seek(0, 2)
215+
except ValueError:
216+
self.file_bytes = -1 # Unknown file length (gzip).
217+
else:
218+
self.file_bytes = self.fp.tell()
219+
self.fp.seek(0)
220+
213221
self.use_mmap = mmap
214222
self.version_byte = version
215223

@@ -599,14 +607,22 @@ def _read_var_array(self):
599607
else: # not a record variable
600608
# Calculate size to avoid problems with vsize (above)
601609
a_size = reduce(mul, shape, 1) * size
602-
if self.use_mmap:
610+
if self.file_bytes >= 0 and begin_ + a_size > self.file_bytes:
611+
data = fromstring(b'\x00'*a_size, dtype=dtype_)
612+
elif self.use_mmap:
603613
mm = mmap(self.fp.fileno(), begin_+a_size, access=ACCESS_READ)
604614
data = ndarray.__new__(ndarray, shape, dtype=dtype_,
605615
buffer=mm, offset=begin_, order=0)
606616
else:
607617
pos = self.fp.tell()
608618
self.fp.seek(begin_)
609-
data = fromstring(self.fp.read(a_size), dtype=dtype_)
619+
# Try to read file, which may fail because the data is
620+
# at or past the end of file. In that case, we treat
621+
# this data as zeros.
622+
buf = self.fp.read(a_size)
623+
if len(buf) < a_size:
624+
buf = b'\x00'*a_size
625+
data = fromstring(buf, dtype=dtype_)
610626
data.shape = shape
611627
self.fp.seek(pos)
612628

nibabel/minc1.py

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -95,8 +95,8 @@ def get_data_shape(self):
9595
def get_zooms(self):
9696
""" Get real-world sizes of voxels """
9797
# zooms must be positive; but steps in MINC can be negative
98-
return tuple(
99-
[abs(float(dim.step)) for dim in self._dims])
98+
return tuple([abs(float(dim.step)) if hasattr(dim, 'step') else 1.0
99+
for dim in self._dims])
100100

101101
def get_affine(self):
102102
nspatial = len(self._spatial_dims)
@@ -106,13 +106,11 @@ def get_affine(self):
106106
dim_names = list(self._dim_names) # for indexing in loop
107107
for i, name in enumerate(self._spatial_dims):
108108
dim = self._dims[dim_names.index(name)]
109-
try:
110-
dir_cos = dim.direction_cosines
111-
except AttributeError:
112-
dir_cos = _default_dir_cos[name]
113-
rot_mat[:, i] = dir_cos
114-
steps[i] = dim.step
115-
starts[i] = dim.start
109+
rot_mat[:, i] = (dim.direction_cosines
110+
if hasattr(dim, 'direction_cosines')
111+
else _default_dir_cos[name])
112+
steps[i] = dim.step if hasattr(dim, 'step') else 1.0
113+
starts[i] = dim.start if hasattr(dim, 'start') else 0.0
116114
origin = np.dot(rot_mat, starts)
117115
aff = np.eye(nspatial + 1)
118116
aff[:nspatial, :nspatial] = rot_mat * steps

nibabel/minc2.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,10 @@ def _get_dimensions(self, var):
7777
dimorder = var.attrs['dimorder'].decode()
7878
except KeyError: # No specified dimensions
7979
return []
80-
return dimorder.split(',')
80+
# The dimension name list must contain only as many entries
81+
# as the variable has dimensions. This reduces errors when an
82+
# unnecessary dimorder attribute is left behind.
83+
return dimorder.split(',')[:len(var.shape)]
8184

8285
def get_data_dtype(self):
8386
return self._image.dtype
@@ -95,7 +98,7 @@ def _get_valid_range(self):
9598
info = np.iinfo(ddt.type)
9699
try:
97100
valid_range = self._image.attrs['valid_range']
98-
except AttributeError:
101+
except (AttributeError, KeyError):
99102
valid_range = [info.min, info.max]
100103
else:
101104
if valid_range[0] < info.min or valid_range[1] > info.max:

nibabel/tests/data/minc1-no-att.mnc

6.71 KB
Binary file not shown.

nibabel/tests/data/minc2-4d-d.mnc

180 KB
Binary file not shown.

nibabel/tests/data/minc2-no-att.mnc

21.2 KB
Binary file not shown.

nibabel/tests/test_minc1.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,21 @@
8383
max=1.498039216,
8484
mean=0.9090422837),
8585
is_proxy=True),
86+
dict(
87+
fname=pjoin(data_path, 'minc1-no-att.mnc'),
88+
shape=(10, 20, 20),
89+
dtype=np.uint8,
90+
affine=np.array([[0, 0, 1.0, 0],
91+
[0, 1.0, 0, 0],
92+
[1.0, 0, 0, 0],
93+
[0, 0, 0, 1]]),
94+
zooms=(1., 1., 1.),
95+
# These values from SPM2/mincstats
96+
data_summary=dict(
97+
min=0.20784314,
98+
max=0.74901961,
99+
mean=0.6061103),
100+
is_proxy=True),
86101
]
87102

88103

nibabel/tests/test_minc2.py

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,37 @@
7272
min=0.2078431373,
7373
max=1.498039216,
7474
mean=0.9090422837),
75-
is_proxy=True)
75+
is_proxy=True),
76+
dict(
77+
fname=pjoin(data_path, 'minc2-no-att.mnc'),
78+
shape=(10, 20, 20),
79+
dtype=np.uint8,
80+
affine=np.array([[0, 0, 1.0, 0],
81+
[0, 1.0, 0, 0],
82+
[1.0, 0, 0, 0],
83+
[0, 0, 0, 1]]),
84+
zooms=(1., 1., 1.),
85+
# These values from SPM2/mincstats
86+
data_summary=dict(
87+
min=0.20784314,
88+
max=0.74901961,
89+
mean=0.6061103),
90+
is_proxy=True),
91+
dict(
92+
fname=pjoin(data_path, 'minc2-4d-d.mnc'),
93+
shape=(5, 16, 16, 16),
94+
dtype=np.float64,
95+
affine=np.array([[1., 0., 0., -6.96 ],
96+
[0., 1., 0., -12.453],
97+
[0., 0., 1., -9.48 ],
98+
[0., 0., 0., 1.]]),
99+
zooms=(1., 1., 1., 1.),
100+
# These values from mincstats
101+
data_summary=dict(
102+
min=0.0,
103+
max=5.0,
104+
mean=2.00078125),
105+
is_proxy=True),
76106
]
77107

78108
if have_h5py:

0 commit comments

Comments
 (0)