예제 #1
0
    def __init__(
        self,
        rotate_range: Optional[Union[Sequence[float], float]] = None,
        shear_range: Optional[Union[Sequence[float], float]] = None,
        translate_range: Optional[Union[Sequence[float], float]] = None,
        scale_range: Optional[Union[Sequence[float], float]] = None,
        as_tensor_output: bool = True,
        device: Optional[torch.device] = None,
    ) -> None:
        """
        Args:
            rotate_range: angle range in radians. rotate_range[0] with be used to generate the 1st rotation
                parameter from `uniform[-rotate_range[0], rotate_range[0])`. Similarly, `rotate_range[1]` and
                `rotate_range[2]` are used in 3D affine for the range of 2nd and 3rd axes.
            shear_range: shear_range[0] with be used to generate the 1st shearing parameter from
                `uniform[-shear_range[0], shear_range[0])`. Similarly, `shear_range[1]` to
                `shear_range[N]` controls the range of the uniform distribution used to generate the 2nd to
                N-th parameter.
            translate_range : translate_range[0] with be used to generate the 1st shift parameter from
                `uniform[-translate_range[0], translate_range[0])`. Similarly, `translate_range[1]`
                to `translate_range[N]` controls the range of the uniform distribution used to generate
                the 2nd to N-th parameter.
            scale_range: scaling_range[0] with be used to generate the 1st scaling factor from
                `uniform[-scale_range[0], scale_range[0]) + 1.0`. Similarly, `scale_range[1]` to
                `scale_range[N]` controls the range of the uniform distribution used to generate the 2nd to
                N-th parameter.
            as_tensor_output: whether to output tensor instead of numpy array.
                defaults to True.
            device: device to store the output grid data.

        See also:
            - :py:meth:`monai.transforms.utils.create_rotate`
            - :py:meth:`monai.transforms.utils.create_shear`
            - :py:meth:`monai.transforms.utils.create_translate`
            - :py:meth:`monai.transforms.utils.create_scale`
        """
        self.rotate_range = ensure_tuple(rotate_range)
        self.shear_range = ensure_tuple(shear_range)
        self.translate_range = ensure_tuple(translate_range)
        self.scale_range = ensure_tuple(scale_range)

        self.rotate_params: Optional[List[float]] = None
        self.shear_params: Optional[List[float]] = None
        self.translate_params: Optional[List[float]] = None
        self.scale_params: Optional[List[float]] = None

        self.as_tensor_output = as_tensor_output
        self.device = device
예제 #2
0
 def __init__(
     self,
     spatial_size: Union[Sequence[int], int],
     label: Optional[np.ndarray] = None,
     pos: float = 1.0,
     neg: float = 1.0,
     num_samples: int = 1,
     image: Optional[np.ndarray] = None,
     image_threshold: float = 0.0,
     fg_indices: Optional[np.ndarray] = None,
     bg_indices: Optional[np.ndarray] = None,
 ) -> None:
     self.spatial_size = ensure_tuple(spatial_size)
     self.label = label
     if pos < 0 or neg < 0:
         raise ValueError(
             f"pos and neg must be nonnegative, got pos={pos} neg={neg}.")
     if pos + neg == 0:
         raise ValueError("Incompatible values: pos=0 and neg=0.")
     self.pos_ratio = pos / (pos + neg)
     self.num_samples = num_samples
     self.image = image
     self.image_threshold = image_threshold
     self.centers: Optional[List[List[np.ndarray]]] = None
     self.fg_indices = fg_indices
     self.bg_indices = bg_indices
예제 #3
0
def generate_spatial_bounding_box(
    img: np.ndarray,
    select_fn: Callable = lambda x: x > 0,
    channel_indices: Optional[IndexSelection] = None,
    margin: int = 0,
) -> Tuple[List[int], List[int]]:
    """
    generate the spatial bounding box of foreground in the image with start-end positions.
    Users can define arbitrary function to select expected foreground from the whole image or specified channels.
    And it can also add margin to every dim of the bounding box.

    Args:
        img: source image to generate bounding box from.
        select_fn: function to select expected foreground, default is to select values > 0.
        channel_indices: if defined, select foreground only on the specified channels
            of image. if None, select foreground on the whole image.
        margin: add margin to all dims of the bounding box.
    """
    assert isinstance(margin, int), "margin must be int type."
    data = img[[*(ensure_tuple(channel_indices))
                ]] if channel_indices is not None else img
    data = np.any(select_fn(data), axis=0)
    nonzero_idx = np.nonzero(data)

    box_start = list()
    box_end = list()
    for i in range(data.ndim):
        assert len(nonzero_idx[i]
                   ) > 0, f"did not find nonzero index at spatial dim {i}"
        box_start.append(max(0, np.min(nonzero_idx[i]) - margin))
        box_end.append(min(data.shape[i], np.max(nonzero_idx[i]) + margin + 1))
    return box_start, box_end
예제 #4
0
 def __init__(
     self,
     transforms: Optional[Union[Sequence[Callable],
                                Callable]] = None) -> None:
     if transforms is None:
         transforms = []
     self.transforms = ensure_tuple(transforms)
     self.set_random_state(seed=get_seed())
예제 #5
0
 def __init__(self, keys: KeysCollection) -> None:
     self.keys: Tuple[Hashable, ...] = ensure_tuple(keys)
     if not self.keys:
         raise ValueError("keys must be non empty.")
     for key in self.keys:
         if not isinstance(key, Hashable):
             raise TypeError(
                 f"keys must be one of (Hashable, Iterable[Hashable]) but is {type(keys).__name__}."
             )
예제 #6
0
def create_translate(spatial_dims: int, shift: Union[Sequence[float],
                                                     float]) -> np.ndarray:
    """
    create a translation matrix

    Args:
        spatial_dims: spatial rank
        shift: translate factors, defaults to 0.
    """
    shift = ensure_tuple(shift)
    affine = np.eye(spatial_dims + 1)
    for i, a in enumerate(shift[:spatial_dims]):
        affine[i, spatial_dims] = a
    return affine
예제 #7
0
 def __init__(self,
              select_fn: Callable = lambda x: x > 0,
              channel_indices: Optional[IndexSelection] = None,
              margin: int = 0) -> None:
     """
     Args:
         select_fn: function to select expected foreground, default is to select values > 0.
         channel_indices: if defined, select foreground only on the specified channels
             of image. if None, select foreground on the whole image.
         margin: add margin to all dims of the bounding box.
     """
     self.select_fn = select_fn
     self.channel_indices = ensure_tuple(
         channel_indices) if channel_indices is not None else None
     self.margin = margin
예제 #8
0
def create_rotate(spatial_dims: int, radians: Union[Sequence[float],
                                                    float]) -> np.ndarray:
    """
    create a 2D or 3D rotation matrix

    Args:
        spatial_dims: {``2``, ``3``} spatial rank
        radians: rotation radians
            when spatial_dims == 3, the `radians` sequence corresponds to
            rotation in the 1st, 2nd, and 3rd dim respectively.

    Raises:
        ValueError: When ``radians`` is empty.
        ValueError: When ``spatial_dims`` is not one of [2, 3].

    """
    radians = ensure_tuple(radians)
    if spatial_dims == 2:
        if len(radians) >= 1:
            sin_, cos_ = np.sin(radians[0]), np.cos(radians[0])
            return np.array([[cos_, -sin_, 0.0], [sin_, cos_, 0.0],
                             [0.0, 0.0, 1.0]])
        raise ValueError("radians must be non empty.")

    if spatial_dims == 3:
        affine = None
        if len(radians) >= 1:
            sin_, cos_ = np.sin(radians[0]), np.cos(radians[0])
            affine = np.array([[1.0, 0.0, 0.0, 0.0], [0.0, cos_, -sin_, 0.0],
                               [0.0, sin_, cos_, 0.0], [0.0, 0.0, 0.0, 1.0]])
        if len(radians) >= 2:
            sin_, cos_ = np.sin(radians[1]), np.cos(radians[1])
            affine = affine @ np.array(
                [[cos_, 0.0, sin_, 0.0], [0.0, 1.0, 0.0, 0.0],
                 [-sin_, 0.0, cos_, 0.0], [0.0, 0.0, 0.0, 1.0]])
        if len(radians) >= 3:
            sin_, cos_ = np.sin(radians[2]), np.cos(radians[2])
            affine = affine @ np.array(
                [[cos_, -sin_, 0.0, 0.0], [sin_, cos_, 0.0, 0.0],
                 [0.0, 0.0, 1.0, 0.0], [0.0, 0.0, 0.0, 1.0]])
        if affine is None:
            raise ValueError("radians must be non empty.")
        return affine

    raise ValueError(
        f"Unsupported spatial_dims: {spatial_dims}, available options are [2, 3]."
    )
예제 #9
0
    def __call__(
            self,
            img: np.ndarray,
            mode: Optional[Union[NumpyPadMode, str]] = None) -> np.ndarray:
        """
        Args:
            img: data to be transformed, assuming `img` is channel-first and
                padding doesn't apply to the channel dim.
            mode: {``"constant"``, ``"edge"``, ``"linear_ramp"``, ``"maximum"``, ``"mean"``,
                ``"median"``, ``"minimum"``, ``"reflect"``, ``"symmetric"``, ``"wrap"``, ``"empty"``}
                One of the listed string values or a user supplied function. Defaults to ``self.mode``.
                See also: https://numpy.org/doc/1.18/reference/generated/numpy.pad.html

        Raises:
            ValueError: When ``self.spatial_border`` contains a nonnegative int.
            ValueError: When ``self.spatial_border`` length is not one of
                [1, len(spatial_shape), 2*len(spatial_shape)].

        """
        spatial_shape = img.shape[1:]
        spatial_border = ensure_tuple(self.spatial_border)
        for b in spatial_border:
            if not isinstance(b, int) or b < 0:
                raise ValueError(
                    f"self.spatial_border must contain only nonnegative ints, got {spatial_border}."
                )

        if len(spatial_border) == 1:
            data_pad_width = [(spatial_border[0], spatial_border[0])
                              for _ in range(len(spatial_shape))]
        elif len(spatial_border) == len(spatial_shape):
            data_pad_width = [(spatial_border[i], spatial_border[i])
                              for i in range(len(spatial_shape))]
        elif len(spatial_border) == len(spatial_shape) * 2:
            data_pad_width = [(spatial_border[2 * i],
                               spatial_border[2 * i + 1])
                              for i in range(len(spatial_shape))]
        else:
            raise ValueError(
                f"Unsupported spatial_border length: {len(spatial_border)}, available options are "
                f"[1, len(spatial_shape)={len(spatial_shape)}, 2*len(spatial_shape)={2*len(spatial_shape)}]."
            )

        return np.pad(
            img, [(0, 0)] + data_pad_width,
            mode=self.mode.value if mode is None else NumpyPadMode(mode).value)
예제 #10
0
 def __call__(
     self,
     img: Union[np.ndarray, torch.Tensor],
     spatial_size: Optional[Union[Tuple[int, int], int]] = None,
     mode: Optional[Union[GridSampleMode, str]] = None,
     padding_mode: Optional[Union[GridSamplePadMode, str]] = None,
 ) -> Union[np.ndarray, torch.Tensor]:
     """
     Args:
         img: shape must be (num_channels, H, W),
         spatial_size: specifying output image spatial size [h, w].
             if `spatial_size` and `self.spatial_size` are not defined, or smaller than 1,
             the transform will use the spatial size of `img`.
         mode: {``"bilinear"``, ``"nearest"``}
             Interpolation mode to calculate output values. Defaults to ``self.mode``.
             See also: https://pytorch.org/docs/stable/nn.functional.html#grid-sample
         padding_mode: {``"zeros"``, ``"border"``, ``"reflection"``}
             Padding mode for outside grid values. Defaults to ``self.padding_mode``.
             See also: https://pytorch.org/docs/stable/nn.functional.html#grid-sample
     """
     sp_size = fall_back_tuple(spatial_size or self.spatial_size,
                               img.shape[1:])
     self.randomize(spatial_size=sp_size)
     if self.do_transform:
         grid = self.deform_grid(spatial_size=sp_size)
         grid = self.rand_affine_grid(grid=grid)
         grid = _torch_interp(
             input=grid.unsqueeze(0),
             scale_factor=list(ensure_tuple(self.deform_grid.spacing)),
             mode=InterpolateMode.BICUBIC.value,
             align_corners=False,
         )
         grid = CenterSpatialCrop(roi_size=sp_size)(grid[0])
     else:
         grid = create_grid(spatial_size=sp_size)
     return self.resampler(img,
                           grid,
                           mode=mode or self.mode,
                           padding_mode=padding_mode or self.padding_mode)