Skip to content

Commit 4ef5573

Browse files
committed
Merge pull request #164 from Eric89GXL/vox2ras_tkr
ENH: Adding vox2ras_tkr This adds the support requested in issue #163 for access to the vox2ras_tkr output from the MGH format.
2 parents e6832af + 06a0a23 commit 4ef5573

File tree

2 files changed

+105
-14
lines changed

2 files changed

+105
-14
lines changed

nibabel/freesurfer/mghformat.py

Lines changed: 33 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -229,11 +229,26 @@ def get_affine(self):
229229
M[0:3, 3] = pxyz_0.T
230230
return M
231231

232+
# For compatibility with nifti (multiple affines)
233+
get_best_affine = get_affine
234+
232235
def get_vox2ras(self):
233236
'''return the get_affine()
234237
'''
235238
return self.get_affine()
236239

240+
def get_vox2ras_tkr(self):
241+
''' Get the vox2ras-tkr transform. See "Torig" here:
242+
http://surfer.nmr.mgh.harvard.edu/fswiki/CoordinateSystems
243+
'''
244+
ds = np.array(self._header_data['delta'])
245+
ns = (np.array(self._header_data['dims'][:3]) * ds) / 2.0
246+
v2rtkr = np.array([[-ds[0], 0, 0, ns[0]],
247+
[0, 0, ds[2], -ns[2]],
248+
[0, -ds[1], 0, ns[1]],
249+
[0, 0, 0, 1]], dtype=np.float32)
250+
return v2rtkr
251+
237252
def get_ras2vox(self):
238253
'''return the inverse get_affine()
239254
'''
@@ -542,20 +557,25 @@ def update_header(self):
542557
hdr = self._header
543558
if not self._data is None:
544559
hdr.set_data_shape(self._data.shape)
560+
# If the affine is not None, and it is different from the main affine in
561+
# the header, update the heaader
562+
if self._affine is None:
563+
return
564+
if np.allclose(self._affine, hdr.get_best_affine()):
565+
return
566+
# for more information, go through save_mgh.m in FreeSurfer dist
567+
MdcD = self._affine[:3, :3]
568+
delta = np.sqrt(np.sum(MdcD * MdcD, axis=0))
569+
Mdc = MdcD / np.tile(delta, (3, 1))
570+
Pcrs_c = np.array([0, 0, 0, 1], dtype=np.float)
571+
Pcrs_c[:3] = np.array([self._data.shape[0], self._data.shape[1],
572+
self._data.shape[2]], dtype=np.float) / 2.0
573+
Pxyz_c = np.dot(self._affine, Pcrs_c)
574+
575+
hdr['delta'][:] = delta
576+
hdr['Mdc'][:, :] = Mdc.T
577+
hdr['Pxyz_c'][:] = Pxyz_c[:3]
545578

546-
if not self._affine is None:
547-
# for more information, go through save_mgh.m in FreeSurfer dist
548-
MdcD = self._affine[:3, :3]
549-
delta = np.sqrt(np.sum(MdcD * MdcD, axis=0))
550-
Mdc = MdcD / np.tile(delta, (3, 1))
551-
Pcrs_c = np.array([0, 0, 0, 1], dtype=np.float)
552-
Pcrs_c[:3] = np.array([self._data.shape[0], self._data.shape[1],
553-
self._data.shape[2]], dtype=np.float) / 2.0
554-
Pxyz_c = np.dot(self._affine, Pcrs_c)
555-
556-
hdr['delta'][:] = delta
557-
hdr['Mdc'][:, :] = Mdc.T
558-
hdr['Pxyz_c'][:] = Pxyz_c[:3]
559579

560580
load = MGHImage.load
561581
save = MGHImage.instance_to_filename

nibabel/freesurfer/tests/test_mghformat.py

Lines changed: 72 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,29 @@
88
### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##
99
'''Tests for mghformat reading writing'''
1010
from __future__ import with_statement
11+
1112
import os
13+
from io import StringIO, BytesIO
14+
1215
import numpy as np
1316
from .. import load, save, MGHImage
1417
from ..mghformat import MGHError
1518
from ...tmpdirs import InTemporaryDirectory
19+
from ...py3k import unicode
20+
from ...fileholders import FileHolder
21+
1622
from ...testing import data_path
1723
from numpy.testing import assert_equal, assert_array_equal, \
1824
assert_array_almost_equal, assert_almost_equal, assert_raises
1925

20-
# sample voxel to ras matrix
26+
# sample voxel to ras matrix (mri_info --vox2ras)
2127
v2r = np.array([[1, 2, 3, -13], [2, 3, 1, -11.5],
2228
[3, 1, 2, -11.5], [0, 0, 0, 1]], dtype=np.float32)
29+
# sample voxel to ras - tkr matrix (mri_info --vox2ras-tkr)
30+
v2rtkr = np.array([[-1.0, 0.0, 0.0, 1.5],
31+
[0.0, 0.0, 1.0, -2.5],
32+
[0.0, -1.0, 0.0, 2.0],
33+
[0.0, 0.0, 0.0, 1.0]], dtype=np.float32)
2334

2435

2536
def test_read_mgh():
@@ -38,7 +49,9 @@ def test_read_mgh():
3849
assert_equal(h['goodRASFlag'], 1)
3950
assert_array_equal(h['dims'], [3, 4, 5, 2])
4051
assert_array_almost_equal(h['mrparms'], [2.0, 0.0, 0.0, 0.0])
52+
assert_array_almost_equal(h.get_zooms(), 1)
4153
assert_array_almost_equal(h.get_vox2ras(), v2r)
54+
assert_array_almost_equal(h.get_vox2ras_tkr(), v2rtkr)
4255

4356
# data. will be different for your own mri_volsynth invocation
4457
v = mgz.get_data()
@@ -139,3 +152,61 @@ def test_filename_exts():
139152
img_back = load(fname)
140153
assert_array_equal(img_back.get_data(), v)
141154
del img_back
155+
156+
157+
def _mgh_rt(img, fobj):
158+
file_map = {'image': FileHolder(fileobj=fobj)}
159+
img.to_file_map(file_map)
160+
return MGHImage.from_file_map(file_map)
161+
162+
163+
def test_header_updating():
164+
# Don't update the header information if the affine doesn't change.
165+
# Luckily the test.mgz dataset had a bad set of cosine vectors, so these
166+
# will be changed if the affine gets updated
167+
mgz_path = os.path.join(data_path, 'test.mgz')
168+
mgz = load(mgz_path)
169+
hdr = mgz.get_header()
170+
# Test against mri_info output
171+
exp_aff = np.loadtxt(StringIO(unicode("""
172+
1.0000 2.0000 3.0000 -13.0000
173+
2.0000 3.0000 1.0000 -11.5000
174+
3.0000 1.0000 2.0000 -11.5000
175+
0.0000 0.0000 0.0000 1.0000""")))
176+
assert_almost_equal(mgz.get_affine(), exp_aff, 6)
177+
assert_almost_equal(hdr.get_affine(), exp_aff, 6)
178+
# Test that initial wonky header elements have not changed
179+
assert_equal(hdr['delta'], 1)
180+
assert_almost_equal(hdr['Mdc'], exp_aff[:3, :3].T)
181+
# Save, reload, same thing
182+
img_fobj = BytesIO()
183+
mgz2 = _mgh_rt(mgz, img_fobj)
184+
hdr2 = mgz2.get_header()
185+
assert_almost_equal(hdr2.get_affine(), exp_aff, 6)
186+
assert_equal(hdr2['delta'], 1)
187+
# Change affine, change underlying header info
188+
exp_aff_d = exp_aff.copy()
189+
exp_aff_d[0, -1] = -14
190+
# This will (probably) become part of the official API
191+
mgz2._affine[:] = exp_aff_d
192+
mgz2.update_header()
193+
assert_almost_equal(hdr2.get_affine(), exp_aff_d, 6)
194+
RZS = exp_aff_d[:3, :3]
195+
assert_almost_equal(hdr2['delta'], np.sqrt(np.sum(RZS ** 2, axis=0)))
196+
assert_almost_equal(hdr2['Mdc'], (RZS / hdr2['delta']).T)
197+
198+
199+
def test_cosine_order():
200+
# Test we are interpreting the cosine order right
201+
data = np.arange(60).reshape((3, 4, 5)).astype(np.int32)
202+
aff = np.diag([2., 3, 4, 1])
203+
aff[0] = [2, 1, 0, 10]
204+
img = MGHImage(data, aff)
205+
assert_almost_equal(img.get_affine(), aff, 6)
206+
img_fobj = BytesIO()
207+
img2 = _mgh_rt(img, img_fobj)
208+
hdr2 = img2.get_header()
209+
RZS = aff[:3, :3]
210+
zooms = np.sqrt(np.sum(RZS ** 2, axis=0))
211+
assert_almost_equal(hdr2['Mdc'], (RZS / zooms).T)
212+
assert_almost_equal(hdr2['delta'], zooms)

0 commit comments

Comments
 (0)