@@ -125,3 +125,85 @@ def dseg_label(in_seg, label, newpath=None):
125
125
new .set_data_dtype (np .uint8 )
126
126
new .to_filename (out_file )
127
127
return out_file
128
+
129
+
130
+ def resample_by_spacing (in_file , zooms , order = 3 , clip = True ):
131
+ """Regrid the input image to match the new zooms."""
132
+ from pathlib import Path
133
+ import numpy as np
134
+ import nibabel as nb
135
+ from scipy .ndimage import map_coordinates
136
+
137
+ if isinstance (in_file , (str , Path )):
138
+ in_file = nb .load (in_file )
139
+
140
+ # Prepare output x-forms
141
+ sform , scode = in_file .get_sform (coded = True )
142
+ qform , qcode = in_file .get_qform (coded = True )
143
+
144
+ hdr = in_file .header .copy ()
145
+ dtype = hdr .get_data_dtype ()
146
+ data = np .asanyarray (in_file .dataobj )
147
+ zooms = np .array (zooms )
148
+
149
+ # Calculate the factors to normalize voxel size to the specific zooms
150
+ pre_zooms = np .array (in_file .header .get_zooms ()[:3 ])
151
+
152
+ # Calculate an affine aligned with cardinal axes, for simplicity
153
+ card = nb .affines .from_matvec (np .diag (pre_zooms ))
154
+ extent = card [:3 , :3 ].dot (np .array (in_file .shape [:3 ]))
155
+ card [:3 , 3 ] = - 0.5 * extent
156
+
157
+ # Cover the FoV with the new grid
158
+ new_size = np .ceil (extent / zooms ).astype (int )
159
+ offset = (extent - np .diag (zooms ).dot (new_size )) * 0.5
160
+ new_card = nb .affines .from_matvec (np .diag (zooms ), card [:3 , 3 ] + offset )
161
+
162
+ # Calculate the new indexes
163
+ new_grid = np .array (
164
+ np .meshgrid (
165
+ np .arange (new_size [0 ]),
166
+ np .arange (new_size [1 ]),
167
+ np .arange (new_size [2 ]),
168
+ indexing = "ij" ,
169
+ )
170
+ ).reshape ((3 , - 1 ))
171
+
172
+ # Calculate the locations of the new samples, w.r.t. the original grid
173
+ ijk = np .linalg .inv (card ).dot (
174
+ new_card .dot (np .vstack ((new_grid , np .ones ((1 , new_grid .shape [1 ])))))
175
+ )
176
+
177
+ # Resample data in the new grid
178
+ resampled = map_coordinates (
179
+ data ,
180
+ ijk [:3 , :],
181
+ output = dtype ,
182
+ order = order ,
183
+ mode = "constant" ,
184
+ cval = 0 ,
185
+ prefilter = True ,
186
+ ).reshape (new_size )
187
+ if clip :
188
+ resampled = np .clip (resampled , a_min = data .min (), a_max = data .max ())
189
+
190
+ # Set new zooms
191
+ hdr .set_zooms (zooms )
192
+
193
+ # Get the original image's affine
194
+ affine = in_file .affine .copy ()
195
+ # Determine rotations w.r.t. cardinal axis and eccentricity
196
+ rot = affine .dot (np .linalg .inv (card ))
197
+ # Apply to the new cardinal, so that the resampling is consistent
198
+ new_affine = rot .dot (new_card )
199
+
200
+ if qcode != 0 :
201
+ hdr .set_qform (new_affine .dot (np .linalg .inv (affine ).dot (qform )), code = int (qcode ))
202
+ if scode != 0 :
203
+ hdr .set_sform (new_affine .dot (np .linalg .inv (affine ).dot (sform )), code = int (scode ))
204
+ if (scode , qcode ) == (0 , 0 ):
205
+ hdr .set_qform (new_affine , code = 1 )
206
+ hdr .set_sform (new_affine , code = 1 )
207
+
208
+ # Create a new x-form affine, aligned with cardinal axes, 1mm3 and centered.
209
+ return nb .Nifti1Image (resampled , new_affine , hdr )
0 commit comments