@@ -431,17 +431,33 @@ class SpatialCropd(Cropd):
431431 - a spatial center and size
432432 - the start and end coordinates of the ROI
433433
434+ ROI parameters (``roi_center``, ``roi_size``, ``roi_start``, ``roi_end``) can also be specified as
435+ string dictionary keys. When a string is provided, the actual coordinate values are read from the
436+ data dictionary at call time. This enables pipelines where coordinates are computed by earlier
437+ transforms (e.g., :py:class:`monai.transforms.TransformPointsWorldToImaged`) and stored in the
438+ data dictionary under the given key.
439+
440+ Example::
441+
442+ from monai.transforms import Compose, TransformPointsWorldToImaged, SpatialCropd
443+
444+ pipeline = Compose([
445+ TransformPointsWorldToImaged(keys="roi_start", refer_keys="image"),
446+ TransformPointsWorldToImaged(keys="roi_end", refer_keys="image"),
447+ SpatialCropd(keys="image", roi_start="roi_start", roi_end="roi_end"),
448+ ])
449+
434450 This transform is capable of lazy execution. See the :ref:`Lazy Resampling topic<lazy_resampling>`
435451 for more information.
436452 """
437453
438454 def __init__ (
439455 self ,
440456 keys : KeysCollection ,
441- roi_center : Sequence [int ] | int | None = None ,
442- roi_size : Sequence [int ] | int | None = None ,
443- roi_start : Sequence [int ] | int | None = None ,
444- roi_end : Sequence [int ] | int | None = None ,
457+ roi_center : Sequence [int ] | int | str | None = None ,
458+ roi_size : Sequence [int ] | int | str | None = None ,
459+ roi_start : Sequence [int ] | int | str | None = None ,
460+ roi_end : Sequence [int ] | int | str | None = None ,
445461 roi_slices : Sequence [slice ] | None = None ,
446462 allow_missing_keys : bool = False ,
447463 lazy : bool = False ,
@@ -450,19 +466,75 @@ def __init__(
450466 Args:
451467 keys: keys of the corresponding items to be transformed.
452468 See also: :py:class:`monai.transforms.compose.MapTransform`
453- roi_center: voxel coordinates for center of the crop ROI.
469+ roi_center: voxel coordinates for center of the crop ROI, or a string key to look up
470+ the coordinates from the data dictionary.
454471 roi_size: size of the crop ROI, if a dimension of ROI size is larger than image size,
455- will not crop that dimension of the image.
456- roi_start: voxel coordinates for start of the crop ROI.
472+ will not crop that dimension of the image. Can also be a string key.
473+ roi_start: voxel coordinates for start of the crop ROI, or a string key to look up
474+ the coordinates from the data dictionary.
457475 roi_end: voxel coordinates for end of the crop ROI, if a coordinate is out of image,
458- use the end coordinate of image.
476+ use the end coordinate of image. Can also be a string key.
459477 roi_slices: list of slices for each of the spatial dimensions.
460478 allow_missing_keys: don't raise exception if key is missing.
461479 lazy: a flag to indicate whether this transform should execute lazily or not. Defaults to False.
462480 """
463- cropper = SpatialCrop (roi_center , roi_size , roi_start , roi_end , roi_slices , lazy = lazy )
481+ self ._roi_center = roi_center
482+ self ._roi_size = roi_size
483+ self ._roi_start = roi_start
484+ self ._roi_end = roi_end
485+ self ._roi_slices = roi_slices
486+ self ._has_str_roi = any (isinstance (v , str ) for v in [roi_center , roi_size , roi_start , roi_end ])
487+
488+ if not self ._has_str_roi :
489+ cropper = SpatialCrop (roi_center , roi_size , roi_start , roi_end , roi_slices , lazy = lazy )
490+ else :
491+ # Placeholder cropper for the string-key path. Actual cropping is done with a
492+ # local SpatialCrop created at __call__ time once string keys are resolved.
493+ # Crop.inverse() reads crop info from the MetaTensor's transform stack (not from
494+ # cropper state), so the placeholder still works correctly for inverse operations.
495+ cropper = SpatialCrop (roi_start = [0 ], roi_end = [1 ], lazy = lazy )
464496 super ().__init__ (keys , cropper = cropper , allow_missing_keys = allow_missing_keys , lazy = lazy )
465497
498+ @staticmethod
499+ def _resolve_roi_param (val , d ):
500+ """Resolve an ROI parameter: if it's a string, look it up in the data dict and flatten."""
501+ if not isinstance (val , str ):
502+ return val
503+ if val not in d :
504+ raise KeyError (f"ROI key '{ val } ' not found in the data dictionary." )
505+ resolved = d [val ]
506+ # ApplyTransformToPoints outputs tensors of shape (C, N, dims).
507+ # A single coordinate like [142.5, -67.3, 301.8] becomes shape (1, 1, 3).
508+ # Flatten to 1-D and round to integers for compute_slices.
509+ # Uses banker's rounding (torch.round) to avoid systematic bias in spatial coordinates.
510+ if isinstance (resolved , torch .Tensor ):
511+ resolved = torch .round (resolved .flatten ()).to (torch .int64 )
512+ return resolved
513+
514+ @property
515+ def requires_current_data (self ):
516+ return self ._has_str_roi
517+
518+ def __call__ (self , data : Mapping [Hashable , torch .Tensor ], lazy : bool | None = None ) -> dict [Hashable , torch .Tensor ]:
519+ if not self ._has_str_roi :
520+ return super ().__call__ (data , lazy = lazy )
521+
522+ d = dict (data )
523+ roi_center = self ._resolve_roi_param (self ._roi_center , d )
524+ roi_size = self ._resolve_roi_param (self ._roi_size , d )
525+ roi_start = self ._resolve_roi_param (self ._roi_start , d )
526+ roi_end = self ._resolve_roi_param (self ._roi_end , d )
527+
528+ lazy_ = self .lazy if lazy is None else lazy
529+ cropper = SpatialCrop (
530+ roi_center = roi_center , roi_size = roi_size ,
531+ roi_start = roi_start , roi_end = roi_end ,
532+ roi_slices = self ._roi_slices , lazy = lazy_ ,
533+ )
534+ for key in self .key_iterator (d ):
535+ d [key ] = cropper (d [key ], lazy = lazy_ )
536+ return d
537+
466538
467539class CenterSpatialCropd (Cropd ):
468540 """
0 commit comments