@@ -219,3 +219,39 @@ def _aff_is_diag(aff):
219219 ''' Utility function returning True if affine is nearly diagonal '''
220220 rzs_aff = aff [:3 , :3 ]
221221 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