fepegar / torchio

Medical imaging toolkit for deep learning
https://torchio.org
Apache License 2.0
2.08k stars 239 forks source link

Pad to Target Shape #1234

Open rickymwalsh opened 1 week ago

rickymwalsh commented 1 week ago

šŸš€ Feature

A transform with most of the same functionality as CropOrPad but never crops, only pads to ensure a minimum target shape is achieved.

Motivation

Using a patch-based pipeline, I need to pad my images to ensure they are are not smaller than the patch size. For example, the S-I size varies between ~250 to >1000, and my patch size is usually 320 or 496. But I don't want to crop because I still want to be able to take multiple patches from the volumes.

Pitch

Either a new class to achieve this, built on top of the existing CropOrPad and just removing the crop section of apply_transform:

class PadToTargetShape(tio.CropOrPad):
    """Pad, if necessary, to match a target shape."""
    def __init__(self, target_shape, **kwargs):
        super().__init__(target_shape=target_shape, **kwargs)

    def apply_transform(self, subject: tio.Subject) -> tio.Subject:
        subject.check_consistent_space()
        padding_params, _ = self.compute_crop_or_pad(subject)
        padding_kwargs = {'padding_mode': self.padding_mode}
        if padding_params is not None:
            pad = Pad(padding_params, **padding_kwargs)
            subject = pad(subject)  # type: ignore[assignment]
        return subject

Or change the existing CropOrPad class to add flags.

class CropOrPad(SpatialTransform):
    def __init__(
        self,
        target_shape: Union[int, TypeTripletInt, None] = None,
        padding_mode: Union[str, float] = 0,
        mask_name: Optional[str] = None,
        labels: Optional[Sequence[int]] = None,
        apply_crop: bool = True,
        apply_pad: bool = True,
        **kwargs,
    ):
        .....
        self.apply_crop = apply_crop
        self.apply_pad = apply_pad

    def apply_transform(self, subject: Subject) -> Subject:
        subject.check_consistent_space()
        padding_params, cropping_params = self.compute_crop_or_pad(subject)
        padding_kwargs = {'padding_mode': self.padding_mode}
        if self.apply_pad and padding_params is not None:  # Added check here!
            pad = Pad(padding_params, **padding_kwargs)
            subject = pad(subject)  # type: ignore[assignment]
        if self.apply_crop and cropping_params is not None:  # Added check here!
            crop = Crop(cropping_params)
            subject = crop(subject)  # type: ignore[assignment]
        return subject

Alternatives

Maybe I'm missing something obvious, an easy way of achieving this already, if so let me know :)