Repository: Project-MONAI/MONAI

Issue:
cropforegroundd transform to return the coordinates
**Is your feature request related to a problem? Please describe.**
would be useful to include the cropping coordinates in the returned dictionary for `cropforegroundd`
https://github.com/Project-MONAI/MONAI/blob/fa2f580bf6d0f58260eaf16aa95386d4da2ae455/monai/transforms/croppad/dictionary.py#L363



Discussion:

Resolving patch:
diff --git a/monai/transforms/croppad/array.py b/monai/transforms/croppad/array.py
--- a/monai/transforms/croppad/array.py
+++ b/monai/transforms/croppad/array.py
@@ -395,6 +395,7 @@ def __init__(
         select_fn: Callable = lambda x: x > 0,
         channel_indices: Optional[IndexSelection] = None,
         margin: Union[Sequence[int], int] = 0,
+        return_coords: bool = False,
     ) -> None:
         """
         Args:
@@ -402,10 +403,12 @@ def __init__(
             channel_indices: if defined, select foreground only on the specified channels
                 of image. if None, select foreground on the whole image.
             margin: add margin value to spatial dims of the bounding box, if only 1 value provided, use it for all dims.
+            return_coords: whether return the coordinates of spatial bounding box for foreground.
         """
         self.select_fn = select_fn
         self.channel_indices = ensure_tuple(channel_indices) if channel_indices is not None else None
         self.margin = margin
+        self.return_coords = return_coords
 
     def __call__(self, img: np.ndarray) -> np.ndarray:
         """
@@ -413,8 +416,11 @@ def __call__(self, img: np.ndarray) -> np.ndarray:
         slicing doesn't change the channel dim.
         """
         box_start, box_end = generate_spatial_bounding_box(img, self.select_fn, self.channel_indices, self.margin)
-        cropper = SpatialCrop(roi_start=box_start, roi_end=box_end)
-        return cropper(img)
+        cropped = SpatialCrop(roi_start=box_start, roi_end=box_end)(img)
+
+        if self.return_coords:
+            return cropped, box_start, box_end
+        return cropped
 
 
 class RandCropByPosNegLabel(Randomizable, Transform):
diff --git a/monai/transforms/croppad/dictionary.py b/monai/transforms/croppad/dictionary.py
--- a/monai/transforms/croppad/dictionary.py
+++ b/monai/transforms/croppad/dictionary.py
@@ -341,6 +341,8 @@ def __init__(
         select_fn: Callable = lambda x: x > 0,
         channel_indices: Optional[IndexSelection] = None,
         margin: int = 0,
+        start_coord_key: str = "foreground_start_coord",
+        end_coord_key: str = "foreground_end_coord",
     ) -> None:
         """
         Args:
@@ -351,18 +353,24 @@ def __init__(
             channel_indices: if defined, select foreground only on the specified channels
                 of image. if None, select foreground on the whole image.
             margin: add margin value to spatial dims of the bounding box, if only 1 value provided, use it for all dims.
+            start_coord_key: key to record the start coordinate of spatial bounding box for foreground.
+            end_coord_key: key to record the end coordinate of spatial bounding box for foreground.
         """
         super().__init__(keys)
         self.source_key = source_key
         self.select_fn = select_fn
         self.channel_indices = ensure_tuple(channel_indices) if channel_indices is not None else None
         self.margin = margin
+        self.start_coord_key = start_coord_key
+        self.end_coord_key = end_coord_key
 
     def __call__(self, data: Mapping[Hashable, np.ndarray]) -> Dict[Hashable, np.ndarray]:
         d = dict(data)
         box_start, box_end = generate_spatial_bounding_box(
             d[self.source_key], self.select_fn, self.channel_indices, self.margin
         )
+        d[self.start_coord_key] = box_start
+        d[self.end_coord_key] = box_end
         cropper = SpatialCrop(roi_start=box_start, roi_end=box_end)
         for key in self.keys:
             d[key] = cropper(d[key])


Test patch:
diff --git a/tests/test_crop_foreground.py b/tests/test_crop_foreground.py
--- a/tests/test_crop_foreground.py
+++ b/tests/test_crop_foreground.py
@@ -53,6 +53,14 @@ def test_value(self, argments, image, expected_data):
         result = CropForeground(**argments)(image)
         np.testing.assert_allclose(result, expected_data)
 
+    @parameterized.expand([TEST_CASE_1])
+    def test_return_coords(self, argments, image, _):
+        argments["return_coords"] = True
+        _, start_coord, end_coord = CropForeground(**argments)(image)
+        argments["return_coords"] = False
+        self.assertListEqual(start_coord, [1, 1])
+        self.assertListEqual(end_coord, [4, 4])
+
 
 if __name__ == "__main__":
     unittest.main()
diff --git a/tests/test_crop_foregroundd.py b/tests/test_crop_foregroundd.py
--- a/tests/test_crop_foregroundd.py
+++ b/tests/test_crop_foregroundd.py
@@ -62,6 +62,18 @@ def test_value(self, argments, image, expected_data):
         result = CropForegroundd(**argments)(image)
         np.testing.assert_allclose(result["img"], expected_data)
 
+    @parameterized.expand([TEST_CASE_1])
+    def test_foreground_position(self, argments, image, _):
+        result = CropForegroundd(**argments)(image)
+        self.assertListEqual(result["foreground_start_coord"], [1, 1])
+        self.assertListEqual(result["foreground_end_coord"], [4, 4])
+
+        argments["start_coord_key"] = "test_start_coord"
+        argments["end_coord_key"] = "test_end_coord"
+        result = CropForegroundd(**argments)(image)
+        self.assertListEqual(result["test_start_coord"], [1, 1])
+        self.assertListEqual(result["test_end_coord"], [4, 4])
+
 
 if __name__ == "__main__":
     unittest.main()
diff --git a/tests/test_rand_gaussian_noised.py b/tests/test_rand_gaussian_noised.py
--- a/tests/test_rand_gaussian_noised.py
+++ b/tests/test_rand_gaussian_noised.py
@@ -24,6 +24,8 @@
 seed = 0
 
 # Test with numpy
+
+
 class TestRandGaussianNoisedNumpy(NumpyImageTestCase2D):
     @parameterized.expand(TEST_CASES)
     def test_correct_results(self, _, keys, mean, std):