Skip to content

Commit 71277ae

Browse files
authored
Merge pull request #36 from mgxd/tst/lta
TST+FIX: LTA conversions
2 parents 455f337 + 6733cba commit 71277ae

File tree

5 files changed

+82
-47
lines changed

5 files changed

+82
-47
lines changed

nitransforms/io/lta.py

+24-17
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from nibabel.volumeutils import Recoder
44
from nibabel.affines import voxel_sizes
55

6-
from .base import StringBasedStruct
6+
from .base import StringBasedStruct, TransformFileError
77

88

99
transform_codes = Recoder((
@@ -20,10 +20,10 @@ class VolumeGeometry(StringBasedStruct):
2020
('valid', 'i4'), # Valid values: 0, 1
2121
('volume', 'i4', (3, 1)), # width, height, depth
2222
('voxelsize', 'f4', (3, 1)), # xsize, ysize, zsize
23-
('xras', 'f4', (3, 1)), # x_r, x_a, x_s
24-
('yras', 'f4', (3, 1)), # y_r, y_a, y_s
25-
('zras', 'f4', (3, 1)), # z_r, z_a, z_s
26-
('cras', 'f4', (3, 1)), # c_r, c_a, c_s
23+
('xras', 'f8', (3, 1)), # x_r, x_a, x_s
24+
('yras', 'f8', (3, 1)), # y_r, y_a, y_s
25+
('zras', 'f8', (3, 1)), # z_r, z_a, z_s
26+
('cras', 'f8', (3, 1)), # c_r, c_a, c_s
2727
('filename', 'U1024')]) # Not conformant (may be >1024 bytes)
2828
dtype = template_dtype
2929

@@ -80,7 +80,7 @@ def from_string(klass, string):
8080
lines = string.splitlines()
8181
for key in ('valid', 'filename', 'volume', 'voxelsize',
8282
'xras', 'yras', 'zras', 'cras'):
83-
label, valstring = lines.pop(0).split(' = ')
83+
label, valstring = lines.pop(0).split(' =')
8484
assert label.strip() == key
8585

8686
val = np.genfromtxt([valstring.encode()],
@@ -92,11 +92,11 @@ def from_string(klass, string):
9292

9393
class LinearTransform(StringBasedStruct):
9494
template_dtype = np.dtype([
95-
('mean', 'f4', (3, 1)), # x0, y0, z0
95+
('mean', 'f4', (3, 1)), # x0, y0, z0
9696
('sigma', 'f4'),
97-
('m_L', 'f4', (4, 4)),
98-
('m_dL', 'f4', (4, 4)),
99-
('m_last_dL', 'f4', (4, 4)),
97+
('m_L', 'f8', (4, 4)),
98+
('m_dL', 'f8', (4, 4)),
99+
('m_last_dL', 'f8', (4, 4)),
100100
('src', VolumeGeometry),
101101
('dst', VolumeGeometry),
102102
('label', 'i4')])
@@ -190,7 +190,10 @@ def to_string(self):
190190
def from_string(klass, string):
191191
lta = klass()
192192
sa = lta.structarr
193-
lines = string.splitlines()
193+
lines = [l.strip() for l in string.splitlines()
194+
if l.strip() and not l.strip().startswith('#')]
195+
if not lines or not lines[0].startswith('type'):
196+
raise TransformFileError("Invalid LTA format")
194197
for key in ('type', 'nxforms'):
195198
label, valstring = lines.pop(0).split(' = ')
196199
assert label.strip() == key
@@ -207,12 +210,16 @@ def from_string(klass, string):
207210
# Optional keys
208211
if not lines[0].startswith(key):
209212
continue
210-
label, valstring = lines.pop(0).split(' ')
211-
assert label.strip() == key
213+
try:
214+
label, valstring = lines.pop(0).split(' ')
215+
except ValueError:
216+
sa[key] = ''
217+
else:
218+
assert label.strip() == key
212219

213-
val = np.genfromtxt([valstring.encode()],
214-
dtype=klass.dtype[key])
215-
sa[key] = val.reshape(sa[key].shape) if val.size else ''
220+
val = np.genfromtxt([valstring.encode()],
221+
dtype=klass.dtype[key])
222+
sa[key] = val.reshape(sa[key].shape) if val.size else ''
216223

217224
assert len(lta._xforms) == sa['nxforms']
218225
return lta
@@ -240,7 +247,7 @@ def set_type(self, target):
240247

241248
# VOX2VOX -> RAS2RAS
242249
if current == 0 and target == 1:
243-
M = np.linalg.inv(src.as_affine()).dot(xform['m_L']).dot(dst.as_affine())
250+
M = dst.as_affine().dot(xform['m_L'].dot(np.linalg.inv(src.as_affine())))
244251
else:
245252
raise NotImplementedError(
246253
"Converting {0} to {1} is not yet available".format(

nitransforms/linear.py

+2
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,8 @@ def load(filename, fmt='X5', reference=None):
247247
if lta['nxforms'] > 1:
248248
raise NotImplementedError("Multiple transforms are not yet supported.")
249249
if lta['type'] != 1:
250+
# To make transforms generalize across use-cases, LTA transforms
251+
# are converted to RAS-to-RAS.
250252
lta.set_type(1)
251253
matrix = lta['xforms'][0]['m_L']
252254
elif fmt.lower() in ('x5', 'bids'):
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# transform file affine-RAS.fs.v2v.lta
2+
# created by a user on Mon Oct 28 10:09:56 2019
3+
4+
type = 0 # LINEAR_VOX_TO_VOX
5+
nxforms = 1
6+
mean = 0.0000 0.0000 0.0000
7+
sigma = 1.0000
8+
1 4 4
9+
9.999989867210388e-01 -9.999993490055203e-04 9.999998146668077e-04 1.454572677612305e+00
10+
1.404936425387859e-03 6.216088533401489e-01 -7.833265066146851e-01 3.912971496582031e+01
11+
1.617172238184139e-04 7.833272218704224e-01 6.216096878051758e-01 -1.376794815063477e+01
12+
0.000000000000000e+00 0.000000000000000e+00 0.000000000000000e+00 1.000000000000000e+00
13+
src volume info
14+
valid = 1 # volume info valid
15+
filename = unknown
16+
volume = 57 67 56
17+
voxelsize = 2.750000000000000e+00 2.750000000000000e+00 2.750000000000000e+00
18+
xras = 1.000000000000000e+00 0.000000000000000e+00 0.000000000000000e+00
19+
yras = 0.000000000000000e+00 1.000000000000000e+00 0.000000000000000e+00
20+
zras = 0.000000000000000e+00 0.000000000000000e+00 1.000000000000000e+00
21+
cras = 3.750000000000000e-01 1.125000000000000e+00 -1.400000000000000e+01
22+
dst volume info
23+
valid = 1 # volume info valid
24+
filename = unknown
25+
volume = 57 67 56
26+
voxelsize = 2.750000000000000e+00 2.750000000000000e+00 2.750000000000000e+00
27+
xras = 1.000000000000000e+00 0.000000000000000e+00 0.000000000000000e+00
28+
yras = 0.000000000000000e+00 1.000000000000000e+00 0.000000000000000e+00
29+
zras = 0.000000000000000e+00 0.000000000000000e+00 1.000000000000000e+00
30+
cras = 3.750000000000000e-01 1.125000000000000e+00 -1.400000000000000e+01
31+
fscale 0.100000

nitransforms/tests/data/inv.lta

-29
This file was deleted.

nitransforms/tests/test_io.py

+25-1
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,13 @@ def test_LinearTransformArray(tmpdir, data_path):
4545
assert lta['nxforms'] == 0
4646
assert len(lta['xforms']) == 0
4747

48-
test_lta = str(data_path / 'inv.lta')
48+
# read invalid LTA file
49+
test_lta = str(data_path / 'affine-RAS.fsl')
50+
with pytest.raises(TransformFileError):
51+
with open(test_lta) as fp:
52+
LTA.from_fileobj(fp)
53+
54+
test_lta = str(data_path / 'affine-RAS.fs.lta')
4955
with open(test_lta) as fp:
5056
lta = LTA.from_fileobj(fp)
5157

@@ -66,6 +72,24 @@ def test_LinearTransformArray(tmpdir, data_path):
6672
assert np.allclose(lta['xforms'][0]['m_L'], lta2['xforms'][0]['m_L'])
6773

6874

75+
def test_LT_conversions(data_path):
76+
r = str(data_path / 'affine-RAS.fs.lta')
77+
v = str(data_path / 'affine-RAS.fs.v2v.lta')
78+
with open(r) as fa, open(v) as fb:
79+
r2r = LTA.from_fileobj(fa)
80+
v2v = LTA.from_fileobj(fb)
81+
assert r2r['type'] == 1
82+
assert v2v['type'] == 0
83+
84+
r2r_m = r2r['xforms'][0]['m_L']
85+
v2v_m = v2v['xforms'][0]['m_L']
86+
assert np.any(r2r_m != v2v_m)
87+
# convert vox2vox LTA to ras2ras
88+
v2v.set_type('LINEAR_RAS_TO_RAS')
89+
assert v2v['type'] == 1
90+
assert np.allclose(r2r_m, v2v_m, atol=1e-05)
91+
92+
6993
def test_ITKLinearTransform(tmpdir, data_path):
7094
tmpdir.chdir()
7195

0 commit comments

Comments
 (0)