Repository: Project-MONAI/MONAI
File: monai/transforms/spatial/array.py (Python)

Code before:
        x_step, y_step = steps
        n_channels = image.shape[0]
        strided_image = as_strided_func(
            image,
            (*self.grid, n_channels, split_size[0], split_size[1]),
            (x_stride * x_step, y_stride * y_step, c_stride, x_stride, y_stride),
        )
        # Flatten the first two dimensions
        strided_image = strided_image.reshape(-1, *strided_image.shape[2:])
        # Make a list of contiguous patches
        if isinstance(image, torch.Tensor):
            patches = [p.contiguous() for p in strided_image]
        elif isinstance(image, np.ndarray):
            patches = [np.ascontiguousarray(p) for p in strided_image]

        return patches

    def _get_params(self, image_size: Sequence[int] | np.ndarray, size: Sequence[int] | np.ndarray | None = None):
        """
        Calculate the size and step required for splitting the image
        Args:
            The size of the input image
        """
        if size is None:
            # infer each sub-image size from the image size and the grid
            size = tuple(image_size[i] // self.grid[i] for i in range(len(self.grid)))

        if any(size[i] > image_size[i] for i in range(len(self.grid))):
            raise ValueError(f"The image size ({image_size})is smaller than the requested split size ({size})")

        steps = tuple(
            (image_size[i] - size[i]) // (self.grid[i] - 1) if self.grid[i] > 1 else image_size[i]
            for i in range(len(self.grid))
        )

        return size, steps


class GridPatch(Transform, MultiSampleTrait):
    """
    Extract all the patches sweeping the entire image in a row-major sliding-window manner with possible overlaps.
    It can sort the patches and return all or a subset of them.

    Args:
        patch_size: size of patches to generate slices for, 0 or None selects whole dimension
        offset: offset of starting position in the array, default is 0 for each dimension.
        num_patches: number of patches (or maximum number of patches) to return.
            If the requested number of patches is greater than the number of available patches,
            padding will be applied to provide exactly `num_patches` patches unless `threshold` is set.
            Defaults to None, which returns all the available patches.
        overlap: the amount of overlap of neighboring patches in each dimension (a value between 0.0 and 1.0).

Review comment (suggestion):
wondering if the order of these operations matters 🤔 (filter_threshold - filter_count) vs (filter_count - filter_threshold)
I have noticed an increase in memory consumption (dataloader worker killed errors) after enabling filter_threshold on top of filter_count. 
I was able to use all available cpu cores previously... but now I have to restrict it to half the cores
  

Code after:
        Sort the patches based on the sum of their intensity, and just keep `self.num_patches` of them.

        Args:
            image_np: a numpy.ndarray representing a stack of patches.
            locations: a numpy.ndarray representing the stack of location of each patch.
        """
        if self.sort_fn is None:
            image_np = image_np[: self.num_patches]
            locations = locations[: self.num_patches]
        elif self.num_patches is not None:
            n_dims = len(image_np.shape)
            if self.sort_fn == GridPatchSort.MIN:
                idx = np.argsort(image_np.sum(axis=tuple(range(1, n_dims))))
            elif self.sort_fn == GridPatchSort.MAX:
                idx = np.argsort(-image_np.sum(axis=tuple(range(1, n_dims))))
            else:
                raise ValueError(f'`sort_fn` should be either "min", "max" or None! {self.sort_fn} provided!')
            idx = idx[: self.num_patches]
            image_np = image_np[idx]
            locations = locations[idx]
        return image_np, locations

    def __call__(self, array: NdarrayOrTensor) -> MetaTensor:
        """
        Extract the patches (sweeping the entire image in a row-major sliding-window manner with possible overlaps).

        Args:
            array: a input image as `numpy.ndarray` or `torch.Tensor`

        Return:
            MetaTensor: the extracted patches as a single tensor (with patch dimension as the first dimension),
                with defined `PatchKeys.LOCATION` and `PatchKeys.COUNT` metadata.
        """
        # create the patch iterator which sweeps the image row-by-row
        array_np, *_ = convert_data_type(array, np.ndarray)
        patch_iterator = iter_patch(
            array_np,
            patch_size=(None,) + self.patch_size,  # expand to have the channel dim
            start_pos=(0,) + self.offset,  # expand to have the channel dim
            overlap=self.overlap,
            copy_back=False,
            mode=self.pad_mode,
            **self.pad_kwargs,
        )
        patches = list(zip(*patch_iterator))
        patched_image = np.array(patches[0])
        locations = np.array(patches[1])[:, 1:, 0]  # only keep the starting location
        del patches  # it will free up some memory if padding is used.

        # Apply threshold filtering
        if self.threshold is not None: