1
1
"""Read/write ITK transforms."""
2
2
import numpy as np
3
- from .base import StringBasedStruct
3
+
4
+ from nibabel .affines import from_matvec
5
+ from .base import StringBasedStruct , _read_mat
6
+
7
+ LPS = np .diag ([- 1 , - 1 , 1 , 1 ])
4
8
5
9
6
10
class ITKLinearTransform (StringBasedStruct ):
@@ -13,20 +17,24 @@ class ITKLinearTransform(StringBasedStruct):
13
17
('offset' , 'f4' , 3 ), # Center of rotation
14
18
])
15
19
dtype = template_dtype
20
+ # files_types = (('string', '.tfm'), ('binary', '.mat'))
21
+ # valid_exts = ('.tfm', '.mat')
16
22
17
- def __init__ (self ):
23
+ def __init__ (self , parameters = None , offset = None ):
18
24
"""Initialize with default offset and index."""
19
25
super ().__init__ ()
20
- self .structarr ['offset' ] = [0 , 0 , 0 ]
21
26
self .structarr ['index' ] = 1
27
+ self .structarr ['offset' ] = offset or [0 , 0 , 0 ]
22
28
self .structarr ['parameters' ] = np .eye (4 )
29
+ if parameters is not None :
30
+ self .structarr ['parameters' ] = parameters
23
31
24
32
def __str__ (self ):
25
33
"""Generate a string representation."""
26
34
sa = self .structarr
27
35
lines = [
28
36
'#Transform {:d}' .format (sa ['index' ]),
29
- 'Transform: MatrixOffsetTransformBase_double_3_3 ' ,
37
+ 'Transform: AffineTransform_float_3_3 ' ,
30
38
'Parameters: {}' .format (' ' .join (
31
39
['%g' % p
32
40
for p in sa ['parameters' ][:3 , :3 ].reshape (- 1 ).tolist () +
@@ -36,6 +44,23 @@ def __str__(self):
36
44
]
37
45
return '\n ' .join (lines )
38
46
47
+ def to_filename (self , filename ):
48
+ """Store this transform to a file with the appropriate format."""
49
+ if str (filename ).endswith ('.mat' ):
50
+ raise NotImplementedError
51
+
52
+ with open (str (filename ), 'w' ) as f :
53
+ f .write (self .to_string ())
54
+
55
+ def to_ras (self ):
56
+ """Return a nitransforms' internal RAS matrix."""
57
+ sa = self .structarr
58
+ matrix = sa ['parameters' ]
59
+ offset = sa ['offset' ]
60
+ c_neg = from_matvec (np .eye (3 ), offset * - 1.0 )
61
+ c_pos = from_matvec (np .eye (3 ), offset )
62
+ return LPS .dot (c_pos .dot (matrix .dot (c_neg .dot (LPS ))))
63
+
39
64
def to_string (self , banner = None ):
40
65
"""Convert to a string directly writeable to file."""
41
66
string = '%s'
@@ -48,9 +73,47 @@ def to_string(self, banner=None):
48
73
return string % self
49
74
50
75
@classmethod
51
- def from_string (klass , string ):
76
+ def from_binary (cls , byte_stream , index = None ):
77
+ """Read the struct from a matlab binary file."""
78
+ mdict = _read_mat (byte_stream )
79
+ return cls .from_matlab_dict (mdict , index = index )
80
+
81
+ @classmethod
82
+ def from_fileobj (cls , fileobj , check = True ):
83
+ """Read the struct from a file object."""
84
+ if fileobj .name .endswith ('.mat' ):
85
+ return cls .from_binary (fileobj )
86
+ return cls .from_string (fileobj .read ())
87
+
88
+ @classmethod
89
+ def from_matlab_dict (cls , mdict , index = None ):
90
+ """Read the struct from a matlab dictionary."""
91
+ tf = cls ()
92
+ sa = tf .structarr
93
+ if index is not None :
94
+ raise NotImplementedError
95
+
96
+ sa ['index' ] = 1
97
+ parameters = np .eye (4 , dtype = 'f4' )
98
+ parameters [:3 , :3 ] = mdict ['AffineTransform_float_3_3' ][:- 3 ].reshape ((3 , 3 ))
99
+ parameters [:3 , 3 ] = mdict ['AffineTransform_float_3_3' ][- 3 :].flatten ()
100
+ sa ['parameters' ] = parameters
101
+ sa ['offset' ] = mdict ['fixed' ].flatten ()
102
+ return tf
103
+
104
+ @classmethod
105
+ def from_ras (cls , ras , index = 0 ):
106
+ """Create an ITK affine from a nitransform's RAS+ matrix."""
107
+ tf = cls ()
108
+ sa = tf .structarr
109
+ sa ['index' ] = index + 1
110
+ sa ['parameters' ] = LPS .dot (ras .dot (LPS ))
111
+ return tf
112
+
113
+ @classmethod
114
+ def from_string (cls , string ):
52
115
"""Read the struct from string."""
53
- tf = klass ()
116
+ tf = cls ()
54
117
sa = tf .structarr
55
118
lines = [l for l in string .splitlines ()
56
119
if l .strip ()]
@@ -61,19 +124,14 @@ def from_string(klass, string):
61
124
parameters = np .eye (4 , dtype = 'f4' )
62
125
sa ['index' ] = int (lines [0 ][lines [0 ].index ('T' ):].split ()[1 ])
63
126
sa ['offset' ] = np .genfromtxt ([lines [3 ].split (':' )[- 1 ].encode ()],
64
- dtype = klass .dtype ['offset' ])
127
+ dtype = cls .dtype ['offset' ])
65
128
vals = np .genfromtxt ([lines [2 ].split (':' )[- 1 ].encode ()],
66
129
dtype = 'f4' )
67
130
parameters [:3 , :3 ] = vals [:- 3 ].reshape ((3 , 3 ))
68
131
parameters [:3 , 3 ] = vals [- 3 :]
69
132
sa ['parameters' ] = parameters
70
133
return tf
71
134
72
- @classmethod
73
- def from_fileobj (klass , fileobj , check = True ):
74
- """Read the struct from a file object."""
75
- return klass .from_string (fileobj .read ())
76
-
77
135
78
136
class ITKLinearTransformArray (StringBasedStruct ):
79
137
"""A string-based structure for series of ITK linear transforms."""
@@ -89,33 +147,80 @@ def __init__(self,
89
147
check = True ):
90
148
"""Initialize with (optionally) a list of transforms."""
91
149
super ().__init__ (binaryblock , endianness , check )
92
- self ._xforms = []
93
- for i , mat in enumerate (xforms or []):
94
- xfm = ITKLinearTransform ()
95
- xfm ['parameters' ] = mat
96
- xfm ['index' ] = i + 1
97
- self ._xforms .append (xfm )
150
+ self .xforms = [ITKLinearTransform (parameters = mat )
151
+ for mat in xforms or []]
152
+
153
+ @property
154
+ def xforms (self ):
155
+ """Get the list of internal ITKLinearTransforms."""
156
+ return self ._xforms
157
+
158
+ @xforms .setter
159
+ def xforms (self , value ):
160
+ self ._xforms = value
161
+
162
+ # Update indexes
163
+ for i , val in enumerate (self ._xforms ):
164
+ val ['index' ] = i + 1
98
165
99
166
def __getitem__ (self , idx ):
100
167
"""Allow dictionary access to the transforms."""
101
168
if idx == 'xforms' :
102
169
return self ._xforms
103
170
if idx == 'nxforms' :
104
171
return len (self ._xforms )
105
- return super ().__getitem__ (idx )
172
+ raise KeyError (idx )
173
+
174
+ def to_filename (self , filename ):
175
+ """Store this transform to a file with the appropriate format."""
176
+ if str (filename ).endswith ('.mat' ):
177
+ raise NotImplementedError
178
+
179
+ with open (str (filename ), 'w' ) as f :
180
+ f .write (self .to_string ())
181
+
182
+ def to_ras (self ):
183
+ """Return a nitransforms' internal RAS matrix."""
184
+ return np .stack ([xfm .to_ras () for xfm in self ._xforms ])
106
185
107
186
def to_string (self ):
108
187
"""Convert to a string directly writeable to file."""
109
188
strings = []
110
- for i , xfm in enumerate (self ._xforms ):
189
+ for i , xfm in enumerate (self .xforms ):
111
190
xfm .structarr ['index' ] = i + 1
112
191
strings .append (xfm .to_string ())
113
192
return '\n ' .join (strings )
114
193
115
194
@classmethod
116
- def from_string (klass , string ):
195
+ def from_binary (cls , byte_stream ):
196
+ """Read the struct from a matlab binary file."""
197
+ mdict = _read_mat (byte_stream )
198
+ nxforms = mdict ['fixed' ].shape [0 ]
199
+
200
+ _self = cls ()
201
+ _self .xforms = [ITKLinearTransform .from_matlab_dict (mdict , i )
202
+ for i in range (nxforms )]
203
+ return _self
204
+
205
+ @classmethod
206
+ def from_fileobj (cls , fileobj , check = True ):
207
+ """Read the struct from a file object."""
208
+ if fileobj .name .endswith ('.mat' ):
209
+ return cls .from_binary (fileobj )
210
+ return cls .from_string (fileobj .read ())
211
+
212
+ @classmethod
213
+ def from_ras (cls , ras ):
214
+ """Create an ITK affine from a nitransform's RAS+ matrix."""
215
+ _self = cls ()
216
+ _self .xforms = [ITKLinearTransform .from_ras (ras [i , ...], i )
217
+ for i in range (ras .shape [0 ])]
218
+ return _self
219
+
220
+ @classmethod
221
+ def from_string (cls , string ):
117
222
"""Read the struct from string."""
118
- _self = klass ()
223
+ _self = cls ()
119
224
lines = [l .strip () for l in string .splitlines ()
120
225
if l .strip ()]
121
226
@@ -124,11 +229,6 @@ def from_string(klass, string):
124
229
125
230
string = '\n ' .join (lines [1 :])
126
231
for xfm in string .split ('#' )[1 :]:
127
- _self ._xforms .append (ITKLinearTransform .from_string (
232
+ _self .xforms .append (ITKLinearTransform .from_string (
128
233
'#%s' % xfm ))
129
234
return _self
130
-
131
- @classmethod
132
- def from_fileobj (klass , fileobj , check = True ):
133
- """Read the struct from a file object."""
134
- return klass .from_string (fileobj .read ())
0 commit comments