15
15
EQUALITY_TOL = 1e-5
16
16
17
17
18
- class ImageSpace (object ):
18
+ class ImageGrid (object ):
19
19
"""Class to represent spaces of gridded data (images)."""
20
20
21
21
__slots__ = ['_affine' , '_shape' , '_ndim' , '_ndindex' , '_coords' , '_nvox' ,
22
22
'_inverse' ]
23
23
24
24
def __init__ (self , image ):
25
+ """Create a gridded sampling reference."""
25
26
self ._affine = image .affine
26
27
self ._shape = image .shape
27
28
self ._ndim = len (image .shape )
@@ -34,26 +35,32 @@ def __init__(self, image):
34
35
35
36
@property
36
37
def affine (self ):
38
+ """Access the indexes-to-RAS affine."""
37
39
return self ._affine
38
40
39
41
@property
40
42
def inverse (self ):
43
+ """Access the RAS-to-indexes affine."""
41
44
return self ._inverse
42
45
43
46
@property
44
47
def shape (self ):
48
+ """Access the space's size of each dimension."""
45
49
return self ._shape
46
50
47
51
@property
48
52
def ndim (self ):
53
+ """Access the number of dimensions."""
49
54
return self ._ndim
50
55
51
56
@property
52
57
def nvox (self ):
58
+ """Access the total number of voxels."""
53
59
return self ._nvox
54
60
55
61
@property
56
62
def ndindex (self ):
63
+ """List the indexes corresponding to the space grid."""
57
64
if self ._ndindex is None :
58
65
indexes = tuple ([np .arange (s ) for s in self ._shape ])
59
66
self ._ndindex = np .array (np .meshgrid (
@@ -62,6 +69,7 @@ def ndindex(self):
62
69
63
70
@property
64
71
def ndcoords (self ):
72
+ """List the physical coordinates of this gridded space samples."""
65
73
if self ._coords is None :
66
74
self ._coords = np .tensordot (
67
75
self ._affine ,
@@ -70,14 +78,14 @@ def ndcoords(self):
70
78
)[:3 , ...]
71
79
return self ._coords
72
80
73
- def map_voxels (self , coordinates ):
81
+ def index (self , coordinates ):
82
+ """Get the image array's indexes corresponding to coordinates."""
74
83
coordinates = np .array (coordinates )
75
84
ncoords = coordinates .shape [- 1 ]
76
85
coordinates = np .vstack ((coordinates , np .ones ((1 , ncoords ))))
77
86
78
87
# Back to grid coordinates
79
- return np .tensordot (np .linalg .inv (self ._affine ),
80
- coordinates , axes = 1 )[:3 , ...]
88
+ return np .tensordot (self ._inverse , coordinates , axes = 1 )[:3 , ...]
81
89
82
90
def _to_hdf5 (self , group ):
83
91
group .attrs ['Type' ] = 'image'
@@ -86,14 +94,9 @@ def _to_hdf5(self, group):
86
94
group .create_dataset ('shape' , data = self .shape )
87
95
88
96
def __eq__ (self , other ):
89
- try :
90
- return (
91
- np .allclose (self .affine , other .affine , rtol = EQUALITY_TOL )
92
- and self .shape == other .shape
93
- )
94
- except AttributeError :
95
- pass
96
- return False
97
+ """Overload equals operator."""
98
+ return (np .allclose (self .affine , other .affine , rtol = EQUALITY_TOL ) and
99
+ self .shape == other .shape )
97
100
98
101
99
102
class TransformBase (object ):
@@ -102,6 +105,7 @@ class TransformBase(object):
102
105
__slots__ = ['_reference' ]
103
106
104
107
def __init__ (self ):
108
+ """Instantiate a transform."""
105
109
self ._reference = None
106
110
107
111
def __eq__ (self , other ):
@@ -110,25 +114,31 @@ def __eq__(self, other):
110
114
return False
111
115
return np .allclose (self .matrix , other .matrix , rtol = EQUALITY_TOL )
112
116
117
+ def __call__ (self , x ):
118
+ """Apply y = f(x)."""
119
+ return self .map (x )
120
+
113
121
@property
114
122
def reference (self ):
115
- '''A reference space where data will be resampled onto'''
123
+ """Access a reference space where data will be resampled onto."""
116
124
if self ._reference is None :
117
125
raise ValueError ('Reference space not set' )
118
126
return self ._reference
119
127
120
128
@reference .setter
121
129
def reference (self , image ):
122
- self ._reference = ImageSpace (image )
130
+ self ._reference = ImageGrid (image )
123
131
124
132
@property
125
133
def ndim (self ):
134
+ """Access the dimensions of the reference space."""
126
135
return self .reference .ndim
127
136
128
137
def resample (self , moving , order = 3 , mode = 'constant' , cval = 0.0 , prefilter = True ,
129
138
output_dtype = None ):
130
139
"""
131
140
Resample the moving image in reference space.
141
+
132
142
Parameters
133
143
----------
134
144
moving : `spatialimage`
@@ -150,18 +160,25 @@ def resample(self, moving, order=3, mode='constant', cval=0.0, prefilter=True,
150
160
slightly blurred if *order > 1*, unless the input is prefiltered,
151
161
i.e. it is the result of calling the spline filter on the original
152
162
input.
163
+
153
164
Returns
154
165
-------
155
166
moved_image : `spatialimage`
156
167
The moving imaged after resampling to reference space.
168
+
157
169
"""
158
170
moving_data = np .asanyarray (moving .dataobj )
159
171
if output_dtype is None :
160
172
output_dtype = moving_data .dtype
161
173
174
+ moving_grid = ImageGrid (moving )
175
+
176
+ def _map_indexes (ijk ):
177
+ return moving_grid .inverse .dot (self .map (self .reference .affine .dot (ijk )))
178
+
162
179
moved = ndi .geometric_transform (
163
180
moving_data ,
164
- mapping = self . map_voxel ,
181
+ mapping = _map_indexes ,
165
182
output_shape = self .reference .shape ,
166
183
output = output_dtype ,
167
184
order = order ,
@@ -175,16 +192,8 @@ def resample(self, moving, order=3, mode='constant', cval=0.0, prefilter=True,
175
192
moved_image .header .set_data_dtype (output_dtype )
176
193
return moved_image
177
194
178
- def map_point (self , coords ):
179
- """Apply y = f(x), where x is the argument `coords`."""
180
- raise NotImplementedError
181
-
182
- def map_voxel (self , index , moving = None ):
183
- """Apply ijk' = f_ijk((i, j, k)), equivalent to the above with indexes."""
184
- raise NotImplementedError
185
-
186
- def _to_hdf5 (self , x5_root ):
187
- """Serialize this object into the x5 file format."""
195
+ def map (self , x ):
196
+ """Apply y = f(x)."""
188
197
raise NotImplementedError
189
198
190
199
def to_filename (self , filename , fmt = 'X5' ):
@@ -196,3 +205,7 @@ def to_filename(self, filename, fmt='X5'):
196
205
self ._to_hdf5 (root )
197
206
198
207
return filename
208
+
209
+ def _to_hdf5 (self , x5_root ):
210
+ """Serialize this object into the x5 file format."""
211
+ raise NotImplementedError
0 commit comments