This function generate the new boxes when the corresponding image is cropped to the given ROI. When ``remove_empty=True``, it makes sure the bounding boxes are within the new cropped image. Args: boxes: bounding boxes, Nx4 or Nx6 torch tensor or ndarray. The box mode is assumed
(
boxes: NdarrayTensor,
roi_start: Sequence[int] | NdarrayOrTensor,
roi_end: Sequence[int] | NdarrayOrTensor,
remove_empty: bool = True,
)
| 1009 | |
| 1010 | |
| 1011 | def spatial_crop_boxes( |
| 1012 | boxes: NdarrayTensor, |
| 1013 | roi_start: Sequence[int] | NdarrayOrTensor, |
| 1014 | roi_end: Sequence[int] | NdarrayOrTensor, |
| 1015 | remove_empty: bool = True, |
| 1016 | ) -> tuple[NdarrayTensor, NdarrayOrTensor]: |
| 1017 | """ |
| 1018 | This function generate the new boxes when the corresponding image is cropped to the given ROI. |
| 1019 | When ``remove_empty=True``, it makes sure the bounding boxes are within the new cropped image. |
| 1020 | |
| 1021 | Args: |
| 1022 | boxes: bounding boxes, Nx4 or Nx6 torch tensor or ndarray. The box mode is assumed to be ``StandardMode`` |
| 1023 | roi_start: voxel coordinates for start of the crop ROI, negative values allowed. |
| 1024 | roi_end: voxel coordinates for end of the crop ROI, negative values allowed. |
| 1025 | remove_empty: whether to remove the boxes that are actually empty |
| 1026 | |
| 1027 | Returns: |
| 1028 | - cropped boxes, boxes[keep], does not share memory with original boxes |
| 1029 | - ``keep``, it indicates whether each box in ``boxes`` are kept when ``remove_empty=True``. |
| 1030 | """ |
| 1031 | |
| 1032 | # convert numpy to tensor if needed |
| 1033 | boxes_t = convert_data_type(boxes, torch.Tensor)[0].clone() |
| 1034 | |
| 1035 | # convert to float32 since torch.clamp_ does not support float16 |
| 1036 | boxes_t = boxes_t.to(dtype=COMPUTE_DTYPE) |
| 1037 | |
| 1038 | roi_start_t = convert_to_dst_type(src=roi_start, dst=boxes_t, wrap_sequence=True)[0].to(torch.int16) |
| 1039 | roi_end_t = convert_to_dst_type(src=roi_end, dst=boxes_t, wrap_sequence=True)[0].to(torch.int16) |
| 1040 | roi_end_t = torch.maximum(roi_end_t, roi_start_t) |
| 1041 | |
| 1042 | # makes sure the bounding boxes are within the patch |
| 1043 | spatial_dims = get_spatial_dims(boxes=boxes, spatial_size=roi_end) |
| 1044 | for axis in range(spatial_dims): |
| 1045 | boxes_t[:, axis] = boxes_t[:, axis].clamp(min=roi_start_t[axis], max=roi_end_t[axis] - TO_REMOVE) |
| 1046 | boxes_t[:, axis + spatial_dims] = boxes_t[:, axis + spatial_dims].clamp( |
| 1047 | min=roi_start_t[axis], max=roi_end_t[axis] - TO_REMOVE |
| 1048 | ) |
| 1049 | boxes_t[:, axis] -= roi_start_t[axis] |
| 1050 | boxes_t[:, axis + spatial_dims] -= roi_start_t[axis] |
| 1051 | |
| 1052 | # remove the boxes that are actually empty |
| 1053 | if remove_empty: |
| 1054 | keep_t = boxes_t[:, spatial_dims] >= boxes_t[:, 0] + 1 - TO_REMOVE |
| 1055 | for axis in range(1, spatial_dims): |
| 1056 | keep_t = keep_t & (boxes_t[:, axis + spatial_dims] >= boxes_t[:, axis] + 1 - TO_REMOVE) |
| 1057 | boxes_t = boxes_t[keep_t] |
| 1058 | else: |
| 1059 | keep_t = torch.full_like(boxes_t[:, 0], fill_value=True, dtype=torch.bool) |
| 1060 | |
| 1061 | # convert tensor back to numpy if needed |
| 1062 | boxes_keep, *_ = convert_to_dst_type(src=boxes_t, dst=boxes) |
| 1063 | keep, *_ = convert_to_dst_type(src=keep_t, dst=boxes, dtype=keep_t.dtype) |
| 1064 | |
| 1065 | return boxes_keep, keep |
| 1066 | |
| 1067 | |
| 1068 | def clip_boxes_to_image( |
no test coverage detected
searching dependent graphs…