Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add pre-cropping #317

Merged
merged 1 commit into from
Oct 19, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
11 changes: 11 additions & 0 deletions docs/customization.rst
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,17 @@ Feature Extractor Level
.. note::
Resampling is disabled when either `resampledPixelSpacing` or `interpolator` is set to `None`

*Pre-Cropping*

- preCrop [False]: Boolean, if true and resampling is disabled, crops the image onto the bounding box with additional
padding as specified in ``padDistance``. Similar to padding after resampling, padding does not exceed original image
bounds after pre-cropping. Setting ``preCrop`` to true speeds up extraction and makes it less memory intensive,
especially in the case of large images with only small ROIs.

.. note::
Because image and mask are also cropped onto the bounding box before they are passed to the feature classes,
pre-crop is only beneficial when filters are enabled.

*Resegmentation*

- resegmentRange [None]: List of 2 floats, specifies the lower and upper threshold, respectively. Segmented voxels
Expand Down
4 changes: 3 additions & 1 deletion examples/exampleSettings/exampleMR_NoResampling.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,9 @@ setting:

# Resampling:
# Not enabled in this example. However, because texture calculation assumes isotropic spacing, a forced 2D extraction
# is used, therefore only requiring the voxels to be isotropic in-plane.
# is used, therefore only requiring the voxels to be isotropic in-plane. Enable pre-cropping to reduce memory
# footprint and speed up applying the filters.
preCrop: true

# Forced 2D extracion:
# This allows to calculate texture features using anisotropic voxels (although it assumes that voxels are isotropic
Expand Down
10 changes: 10 additions & 0 deletions radiomics/featureextractor.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ class specific are defined in the respective feature classes and and not include
'removeOutliers': None,
'resampledPixelSpacing': None, # No resampling by default
'interpolator': 'sitkBSpline', # Alternative: sitk.sitkBSpline
'preCrop': False,
'padDistance': 5,
'distances': [1],
'force2D': False,
Expand Down Expand Up @@ -446,6 +447,15 @@ def loadImage(self, ImageFilePath, MaskFilePath):
self.settings['interpolator'],
self.settings['label'],
self.settings['padDistance'])
elif self.settings['preCrop']:
bb, correctedMask = imageoperations.checkMask(image, mask, **self.settings)
if correctedMask is not None:
# Update the mask if it had to be resampled
mask = correctedMask
if bb is None:
# Mask checks failed
return None, None
image, mask = imageoperations.cropToTumorMask(image, mask, bb, self.settings['padDistance'])

return image, mask

Expand Down
10 changes: 7 additions & 3 deletions radiomics/imageoperations.py
Original file line number Diff line number Diff line change
Expand Up @@ -363,7 +363,7 @@ def _checkROI(imageNode, maskNode, label):
return bb


def cropToTumorMask(imageNode, maskNode, boundingBox):
def cropToTumorMask(imageNode, maskNode, boundingBox, padDistance=0):
"""
Create a sitkImage of the segmented region of the image based on the input label.

Expand All @@ -382,8 +382,12 @@ def cropToTumorMask(imageNode, maskNode, boundingBox):
maskNode = sitk.Cast(maskNode, sitk.sitkInt32)
size = numpy.array(maskNode.GetSize())

ijkMinBounds = boundingBox[0::2]
ijkMaxBounds = size - boundingBox[1::2] - 1
ijkMinBounds = boundingBox[0::2] - padDistance
ijkMaxBounds = size - boundingBox[1::2] - padDistance - 1

# Ensure cropped area is not outside original image bounds
ijkMinBounds = numpy.maximum(ijkMinBounds, 0)
ijkMaxBounds = numpy.maximum(ijkMaxBounds, 0)

# Crop Image
logger.debug('Cropping to size %s', (boundingBox[1::2] - boundingBox[0::2]) + 1)
Expand Down
2 changes: 2 additions & 0 deletions radiomics/schemas/paramSchema.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@ mapping:
resegmentRange:
seq:
- type: float
preCrop:
type: bool
sigma:
seq:
- type: float
Expand Down