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