@@ -219,3 +219,39 @@ def _aff_is_diag(aff):
219
219
''' Utility function returning True if affine is nearly diagonal '''
220
220
rzs_aff = aff [:3 , :3 ]
221
221
return np .allclose (rzs_aff , np .diag (np .diag (rzs_aff )))
222
+
223
+
224
+ def crop_image (img , mask = None ):
225
+ ''' Crop ``img`` to smallest region that contains all non-zero data
226
+
227
+ The image is cropped in the current orientation; no rotations or resampling
228
+ are performed.
229
+ The affine matrix is updated with the new intercept, so that all values are
230
+ found at the same RAS locations.
231
+
232
+ Parameters
233
+ ----------
234
+ img : ``spatialimage``
235
+ mask : ``spatialimage``, optional
236
+ If supplied, use the bounding box of ``mask``, rather than ``img``
237
+
238
+ Returns
239
+ -------
240
+ cropped_img : ``spatialimage``
241
+ Version of `img` with cropped data array and updated affine matrix
242
+ '''
243
+ if mask is None :
244
+ mask = img
245
+ elif not np .allclose (img .affine == mask .affine ):
246
+ raise ValueError ('Affine for image does not match affine for mask' )
247
+
248
+ bounds = np .sort (np .vstack (np .nonzero (mask .get_data ())))[:, [0 , - 1 ]]
249
+ x , y , z = bounds
250
+ new_origin = np .vstack ((bounds [:, [0 ]], [1 ]))
251
+
252
+ new_data = img .get_data ()[x [0 ]:x [1 ] + 1 , y [0 ]:y [1 ] + 1 , z [0 ]:z [1 ] + 1 ]
253
+
254
+ new_aff = img .affine .copy ()
255
+ new_aff [:, [3 ]] = img .affine .dot (new_origin )
256
+
257
+ return img .__class__ (new_data , new_aff , img .header )
0 commit comments