3
3
from nibabel .volumeutils import Recoder
4
4
from nibabel .affines import voxel_sizes
5
5
6
- from .base import StringBasedStruct , TransformFileError
6
+ from .base import BaseLinearTransformList , StringBasedStruct , TransformFileError
7
7
8
8
9
9
transform_codes = Recoder ((
16
16
17
17
18
18
class VolumeGeometry (StringBasedStruct ):
19
+ """Data structure for regularly gridded images."""
20
+
19
21
template_dtype = np .dtype ([
20
22
('valid' , 'i4' ), # Valid values: 0, 1
21
23
('volume' , 'i4' , (3 , 1 )), # width, height, depth
@@ -28,6 +30,7 @@ class VolumeGeometry(StringBasedStruct):
28
30
dtype = template_dtype
29
31
30
32
def as_affine (self ):
33
+ """Return the internal affine of this regular grid."""
31
34
affine = np .eye (4 )
32
35
sa = self .structarr
33
36
A = np .hstack ((sa ['xras' ], sa ['yras' ], sa ['zras' ])) * sa ['voxelsize' ]
@@ -36,7 +39,8 @@ def as_affine(self):
36
39
affine [:3 , [3 ]] = b
37
40
return affine
38
41
39
- def to_string (self ):
42
+ def __str__ (self ):
43
+ """Format the structure as a text file."""
40
44
sa = self .structarr
41
45
lines = [
42
46
'valid = {} # volume info {:s}valid' .format (
@@ -52,8 +56,13 @@ def to_string(self):
52
56
]
53
57
return '\n ' .join (lines )
54
58
59
+ def to_string (self ):
60
+ """Format the structure as a text file."""
61
+ return self .__str__ ()
62
+
55
63
@classmethod
56
64
def from_image (klass , img ):
65
+ """Create struct from an image."""
57
66
volgeom = klass ()
58
67
sa = volgeom .structarr
59
68
sa ['valid' ] = 1
@@ -75,6 +84,7 @@ def from_image(klass, img):
75
84
76
85
@classmethod
77
86
def from_string (klass , string ):
87
+ """Create a volume structure off of text."""
78
88
volgeom = klass ()
79
89
sa = volgeom .structarr
80
90
lines = string .splitlines ()
@@ -83,15 +93,21 @@ def from_string(klass, string):
83
93
label , valstring = lines .pop (0 ).split (' =' )
84
94
assert label .strip () == key
85
95
86
- val = np .genfromtxt ([valstring .encode ()],
87
- dtype = klass .dtype [key ])
88
- sa [key ] = val .reshape (sa [key ].shape ) if val .size else ''
89
-
96
+ val = ''
97
+ if valstring .strip ():
98
+ parsed = np .genfromtxt ([valstring .encode ()], autostrip = True ,
99
+ dtype = klass .dtype [key ])
100
+ if parsed .size :
101
+ val = parsed .reshape (sa [key ].shape )
102
+ sa [key ] = val
90
103
return volgeom
91
104
92
105
93
106
class LinearTransform (StringBasedStruct ):
107
+ """Represents a single LTA's transform structure."""
108
+
94
109
template_dtype = np .dtype ([
110
+ ('type' , 'i4' ),
95
111
('mean' , 'f4' , (3 , 1 )), # x0, y0, z0
96
112
('sigma' , 'f4' ),
97
113
('m_L' , 'f8' , (4 , 4 )),
@@ -103,12 +119,53 @@ class LinearTransform(StringBasedStruct):
103
119
dtype = template_dtype
104
120
105
121
def __getitem__ (self , idx ):
122
+ """Implement dictionary access."""
106
123
val = super (LinearTransform , self ).__getitem__ (idx )
107
124
if idx in ('src' , 'dst' ):
108
125
val = VolumeGeometry (val )
109
126
return val
110
127
128
+ def set_type (self , new_type ):
129
+ """
130
+ Convert the internal transformation matrix to a different type inplace.
131
+
132
+ Parameters
133
+ ----------
134
+ new_type : str, int
135
+ Tranformation type
136
+
137
+ """
138
+ sa = self .structarr
139
+ src = VolumeGeometry (sa ['src' ])
140
+ dst = VolumeGeometry (sa ['dst' ])
141
+ current = sa ['type' ]
142
+ if isinstance (new_type , str ):
143
+ new_type = transform_codes .code [new_type ]
144
+
145
+ if current == new_type :
146
+ return
147
+
148
+ # VOX2VOX -> RAS2RAS
149
+ if (current , new_type ) == (0 , 1 ):
150
+ M = dst .as_affine ().dot (sa ['m_L' ].dot (np .linalg .inv (src .as_affine ())))
151
+ sa ['m_L' ] = M
152
+ sa ['type' ] = new_type
153
+ return
154
+
155
+ raise NotImplementedError (
156
+ "Converting {0} to {1} is not yet available" .format (
157
+ transform_codes .label [current ],
158
+ transform_codes .label [new_type ]
159
+ )
160
+ )
161
+
162
+ def to_ras (self ):
163
+ """Return a nitransforms internal RAS+ matrix."""
164
+ self .set_type (1 )
165
+ return self .structarr ['m_L' ]
166
+
111
167
def to_string (self ):
168
+ """Convert this transform to text."""
112
169
sa = self .structarr
113
170
lines = [
114
171
'mean = {:6.4f} {:6.4f} {:6.4f}' .format (
@@ -120,14 +177,15 @@ def to_string(self):
120
177
('{:18.15e} ' * 4 ).format (* sa ['m_L' ][2 ]),
121
178
('{:18.15e} ' * 4 ).format (* sa ['m_L' ][3 ]),
122
179
'src volume info' ,
123
- self ['src' ]. to_string () ,
180
+ '%s' % self ['src' ],
124
181
'dst volume info' ,
125
- self ['dst' ]. to_string () ,
182
+ '%s' % self ['dst' ],
126
183
]
127
184
return '\n ' .join (lines )
128
185
129
186
@classmethod
130
187
def from_string (klass , string ):
188
+ """Read a transform from text."""
131
189
lt = klass ()
132
190
sa = lt .structarr
133
191
lines = string .splitlines ()
@@ -151,31 +209,32 @@ def from_string(klass, string):
151
209
return lt
152
210
153
211
154
- class LinearTransformArray (StringBasedStruct ):
212
+ class LinearTransformArray (BaseLinearTransformList ):
213
+ """A list of linear transforms generated by FreeSurfer."""
214
+
155
215
template_dtype = np .dtype ([
156
216
('type' , 'i4' ),
157
217
('nxforms' , 'i4' ),
158
218
('subject' , 'U1024' ),
159
219
('fscale' , 'f4' )])
160
220
dtype = template_dtype
161
- _xforms = None
162
-
163
- def __init__ (self ,
164
- binaryblock = None ,
165
- endianness = None ,
166
- check = True ):
167
- super (LinearTransformArray , self ).__init__ (binaryblock , endianness , check )
168
- self ._xforms = [LinearTransform ()
169
- for _ in range (self .structarr ['nxforms' ])]
221
+ _inner_type = LinearTransform
170
222
171
223
def __getitem__ (self , idx ):
224
+ """Allow dictionary access to the transforms."""
172
225
if idx == 'xforms' :
173
226
return self ._xforms
174
227
if idx == 'nxforms' :
175
228
return len (self ._xforms )
176
- return super (LinearTransformArray , self ).__getitem__ (idx )
229
+ return self .structarr [idx ]
230
+
231
+ def to_ras (self , moving = None , reference = None ):
232
+ """Set type to RAS2RAS and return the new matrix."""
233
+ self .structarr ['type' ] = 1
234
+ return [xfm .to_ras () for xfm in self .xforms ]
177
235
178
236
def to_string (self ):
237
+ """Convert this LTA into text format."""
179
238
code = int (self ['type' ])
180
239
header = [
181
240
'type = {} # {}' .format (code , transform_codes .label [code ]),
@@ -188,6 +247,7 @@ def to_string(self):
188
247
189
248
@classmethod
190
249
def from_string (klass , string ):
250
+ """Read this LTA from a text string."""
191
251
lta = klass ()
192
252
sa = lta .structarr
193
253
lines = [l .strip () for l in string .splitlines ()
@@ -203,7 +263,8 @@ def from_string(klass, string):
203
263
sa [key ] = val .reshape (sa [key ].shape ) if val .size else ''
204
264
for _ in range (sa ['nxforms' ]):
205
265
lta ._xforms .append (
206
- LinearTransform .from_string ('\n ' .join (lines [:25 ])))
266
+ klass ._inner_type .from_string ('\n ' .join (lines [:25 ])))
267
+ lta ._xforms [- 1 ].structarr ['type' ] = sa ['type' ]
207
268
lines = lines [25 :]
208
269
if lines :
209
270
for key in ('subject' , 'fscale' ):
@@ -223,37 +284,3 @@ def from_string(klass, string):
223
284
224
285
assert len (lta ._xforms ) == sa ['nxforms' ]
225
286
return lta
226
-
227
- @classmethod
228
- def from_fileobj (klass , fileobj , check = True ):
229
- return klass .from_string (fileobj .read ())
230
-
231
- def set_type (self , target ):
232
- """
233
- Convert the internal transformation matrix to a different type inplace
234
-
235
- Parameters
236
- ----------
237
- target : str, int
238
- Tranformation type
239
- """
240
- assert self ['nxforms' ] == 1 , "Cannot convert multiple transformations"
241
- xform = self ['xforms' ][0 ]
242
- src = xform ['src' ]
243
- dst = xform ['dst' ]
244
- current = self ['type' ]
245
- if isinstance (target , str ):
246
- target = transform_codes .code [target ]
247
-
248
- # VOX2VOX -> RAS2RAS
249
- if current == 0 and target == 1 :
250
- M = dst .as_affine ().dot (xform ['m_L' ].dot (np .linalg .inv (src .as_affine ())))
251
- else :
252
- raise NotImplementedError (
253
- "Converting {0} to {1} is not yet available" .format (
254
- transform_codes .label [current ],
255
- transform_codes .label [target ]
256
- )
257
- )
258
- xform ['m_L' ] = M
259
- self ['type' ] = target
0 commit comments