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