_bounding_box.py 8.73 KB
Newer Older
1
2
from __future__ import annotations

3
from enum import Enum
4
from typing import Any, List, Optional, Sequence, Tuple, Union
Philip Meier's avatar
Philip Meier committed
5
6

import torch
vfdev's avatar
vfdev committed
7
from torchvision.transforms import InterpolationMode  # TODO: this needs to be moved out of transforms
Philip Meier's avatar
Philip Meier committed
8

Philip Meier's avatar
Philip Meier committed
9
from ._datapoint import _FillTypeJIT, Datapoint
Philip Meier's avatar
Philip Meier committed
10
11


12
class BoundingBoxFormat(Enum):
Philip Meier's avatar
Philip Meier committed
13
14
15
16
17
18
19
20
21
    """[BETA] Coordinate format of a bounding box.

    Available formats are

    * ``XYXY``
    * ``XYWH``
    * ``CXCYWH``
    """

22
23
24
    XYXY = "XYXY"
    XYWH = "XYWH"
    CXCYWH = "CXCYWH"
Philip Meier's avatar
Philip Meier committed
25
26


27
class BoundingBoxes(Datapoint):
Philip Meier's avatar
Philip Meier committed
28
29
30
31
32
    """[BETA] :class:`torch.Tensor` subclass for bounding boxes.

    Args:
        data: Any data that can be turned into a tensor with :func:`torch.as_tensor`.
        format (BoundingBoxFormat, str): Format of the bounding box.
Philip Meier's avatar
Philip Meier committed
33
        canvas_size (two-tuple of ints): Height and width of the corresponding image or video.
Philip Meier's avatar
Philip Meier committed
34
35
36
37
38
39
40
41
        dtype (torch.dtype, optional): Desired data type of the bounding box. If omitted, will be inferred from
            ``data``.
        device (torch.device, optional): Desired device of the bounding box. If omitted and ``data`` is a
            :class:`torch.Tensor`, the device is taken from it. Otherwise, the bounding box is constructed on the CPU.
        requires_grad (bool, optional): Whether autograd should record operations on the bounding box. If omitted and
            ``data`` is a :class:`torch.Tensor`, the value is taken from it. Otherwise, defaults to ``False``.
    """

Philip Meier's avatar
Philip Meier committed
42
    format: BoundingBoxFormat
Philip Meier's avatar
Philip Meier committed
43
    canvas_size: Tuple[int, int]
Philip Meier's avatar
Philip Meier committed
44

45
    @classmethod
Philip Meier's avatar
Philip Meier committed
46
    def _wrap(cls, tensor: torch.Tensor, *, format: BoundingBoxFormat, canvas_size: Tuple[int, int]) -> BoundingBoxes:
47
48
        bounding_boxes = tensor.as_subclass(cls)
        bounding_boxes.format = format
Philip Meier's avatar
Philip Meier committed
49
        bounding_boxes.canvas_size = canvas_size
50
        return bounding_boxes
51

52
    def __new__(
Philip Meier's avatar
Philip Meier committed
53
        cls,
54
55
56
        data: Any,
        *,
        format: Union[BoundingBoxFormat, str],
Philip Meier's avatar
Philip Meier committed
57
        canvas_size: Tuple[int, int],
58
59
        dtype: Optional[torch.dtype] = None,
        device: Optional[Union[torch.device, str, int]] = None,
60
        requires_grad: Optional[bool] = None,
61
    ) -> BoundingBoxes:
62
        tensor = cls._to_tensor(data, dtype=dtype, device=device, requires_grad=requires_grad)
63

Philip Meier's avatar
Philip Meier committed
64
        if isinstance(format, str):
65
            format = BoundingBoxFormat[format.upper()]
Philip Meier's avatar
Philip Meier committed
66

Philip Meier's avatar
Philip Meier committed
67
        return cls._wrap(tensor, format=format, canvas_size=canvas_size)
68

69
    @classmethod
70
    def wrap_like(
71
        cls,
72
        other: BoundingBoxes,
73
        tensor: torch.Tensor,
74
        *,
75
        format: Optional[BoundingBoxFormat] = None,
Philip Meier's avatar
Philip Meier committed
76
        canvas_size: Optional[Tuple[int, int]] = None,
77
78
    ) -> BoundingBoxes:
        """Wrap a :class:`torch.Tensor` as :class:`BoundingBoxes` from a reference.
Philip Meier's avatar
Philip Meier committed
79
80

        Args:
81
82
            other (BoundingBoxes): Reference bounding box.
            tensor (Tensor): Tensor to be wrapped as :class:`BoundingBoxes`
Philip Meier's avatar
Philip Meier committed
83
84
            format (BoundingBoxFormat, str, optional): Format of the bounding box.  If omitted, it is taken from the
                reference.
Philip Meier's avatar
Philip Meier committed
85
            canvas_size (two-tuple of ints, optional): Height and width of the corresponding image or video. If
Philip Meier's avatar
Philip Meier committed
86
87
88
89
                omitted, it is taken from the reference.

        """
        if isinstance(format, str):
90
            format = BoundingBoxFormat[format.upper()]
Philip Meier's avatar
Philip Meier committed
91

92
93
        return cls._wrap(
            tensor,
94
            format=format if format is not None else other.format,
Philip Meier's avatar
Philip Meier committed
95
            canvas_size=canvas_size if canvas_size is not None else other.canvas_size,
96
97
        )

98
    def __repr__(self, *, tensor_contents: Any = None) -> str:  # type: ignore[override]
Philip Meier's avatar
Philip Meier committed
99
        return self._make_repr(format=self.format, canvas_size=self.canvas_size)
100

101
102
    def horizontal_flip(self) -> BoundingBoxes:
        output = self._F.horizontal_flip_bounding_boxes(
Philip Meier's avatar
Philip Meier committed
103
            self.as_subclass(torch.Tensor), format=self.format, canvas_size=self.canvas_size
104
        )
105
        return BoundingBoxes.wrap_like(self, output)
106

107
108
    def vertical_flip(self) -> BoundingBoxes:
        output = self._F.vertical_flip_bounding_boxes(
Philip Meier's avatar
Philip Meier committed
109
            self.as_subclass(torch.Tensor), format=self.format, canvas_size=self.canvas_size
110
        )
111
        return BoundingBoxes.wrap_like(self, output)
112
113
114
115

    def resize(  # type: ignore[override]
        self,
        size: List[int],
116
        interpolation: Union[InterpolationMode, int] = InterpolationMode.BILINEAR,
117
        max_size: Optional[int] = None,
118
        antialias: Optional[Union[str, bool]] = "warn",
119
    ) -> BoundingBoxes:
Philip Meier's avatar
Philip Meier committed
120
        output, canvas_size = self._F.resize_bounding_boxes(
121
            self.as_subclass(torch.Tensor),
Philip Meier's avatar
Philip Meier committed
122
            canvas_size=self.canvas_size,
123
124
            size=size,
            max_size=max_size,
125
        )
Philip Meier's avatar
Philip Meier committed
126
        return BoundingBoxes.wrap_like(self, output, canvas_size=canvas_size)
127

128
    def crop(self, top: int, left: int, height: int, width: int) -> BoundingBoxes:
Philip Meier's avatar
Philip Meier committed
129
        output, canvas_size = self._F.crop_bounding_boxes(
130
            self.as_subclass(torch.Tensor), self.format, top=top, left=left, height=height, width=width
131
        )
Philip Meier's avatar
Philip Meier committed
132
        return BoundingBoxes.wrap_like(self, output, canvas_size=canvas_size)
133

134
    def center_crop(self, output_size: List[int]) -> BoundingBoxes:
Philip Meier's avatar
Philip Meier committed
135
136
        output, canvas_size = self._F.center_crop_bounding_boxes(
            self.as_subclass(torch.Tensor), format=self.format, canvas_size=self.canvas_size, output_size=output_size
137
        )
Philip Meier's avatar
Philip Meier committed
138
        return BoundingBoxes.wrap_like(self, output, canvas_size=canvas_size)
139
140
141
142
143
144
145
146

    def resized_crop(
        self,
        top: int,
        left: int,
        height: int,
        width: int,
        size: List[int],
147
        interpolation: Union[InterpolationMode, int] = InterpolationMode.BILINEAR,
148
        antialias: Optional[Union[str, bool]] = "warn",
149
    ) -> BoundingBoxes:
Philip Meier's avatar
Philip Meier committed
150
        output, canvas_size = self._F.resized_crop_bounding_boxes(
151
152
            self.as_subclass(torch.Tensor), self.format, top, left, height, width, size=size
        )
Philip Meier's avatar
Philip Meier committed
153
        return BoundingBoxes.wrap_like(self, output, canvas_size=canvas_size)
154
155

    def pad(
156
157
        self,
        padding: Union[int, Sequence[int]],
158
        fill: Optional[Union[int, float, List[float]]] = None,
159
        padding_mode: str = "constant",
160
    ) -> BoundingBoxes:
Philip Meier's avatar
Philip Meier committed
161
        output, canvas_size = self._F.pad_bounding_boxes(
162
163
            self.as_subclass(torch.Tensor),
            format=self.format,
Philip Meier's avatar
Philip Meier committed
164
            canvas_size=self.canvas_size,
165
166
            padding=padding,
            padding_mode=padding_mode,
167
        )
Philip Meier's avatar
Philip Meier committed
168
        return BoundingBoxes.wrap_like(self, output, canvas_size=canvas_size)
169
170
171
172

    def rotate(
        self,
        angle: float,
173
        interpolation: Union[InterpolationMode, int] = InterpolationMode.NEAREST,
174
175
        expand: bool = False,
        center: Optional[List[float]] = None,
Philip Meier's avatar
Philip Meier committed
176
        fill: _FillTypeJIT = None,
177
    ) -> BoundingBoxes:
Philip Meier's avatar
Philip Meier committed
178
        output, canvas_size = self._F.rotate_bounding_boxes(
179
180
            self.as_subclass(torch.Tensor),
            format=self.format,
Philip Meier's avatar
Philip Meier committed
181
            canvas_size=self.canvas_size,
182
183
184
            angle=angle,
            expand=expand,
            center=center,
185
        )
Philip Meier's avatar
Philip Meier committed
186
        return BoundingBoxes.wrap_like(self, output, canvas_size=canvas_size)
187
188
189

    def affine(
        self,
190
        angle: Union[int, float],
191
192
193
        translate: List[float],
        scale: float,
        shear: List[float],
194
        interpolation: Union[InterpolationMode, int] = InterpolationMode.NEAREST,
Philip Meier's avatar
Philip Meier committed
195
        fill: _FillTypeJIT = None,
196
        center: Optional[List[float]] = None,
197
198
    ) -> BoundingBoxes:
        output = self._F.affine_bounding_boxes(
199
            self.as_subclass(torch.Tensor),
200
            self.format,
Philip Meier's avatar
Philip Meier committed
201
            self.canvas_size,
202
203
204
205
206
207
            angle,
            translate=translate,
            scale=scale,
            shear=shear,
            center=center,
        )
208
        return BoundingBoxes.wrap_like(self, output)
209
210
211

    def perspective(
        self,
212
213
        startpoints: Optional[List[List[int]]],
        endpoints: Optional[List[List[int]]],
214
        interpolation: Union[InterpolationMode, int] = InterpolationMode.BILINEAR,
Philip Meier's avatar
Philip Meier committed
215
        fill: _FillTypeJIT = None,
216
        coefficients: Optional[List[float]] = None,
217
218
    ) -> BoundingBoxes:
        output = self._F.perspective_bounding_boxes(
219
220
            self.as_subclass(torch.Tensor),
            format=self.format,
Philip Meier's avatar
Philip Meier committed
221
            canvas_size=self.canvas_size,
222
223
224
            startpoints=startpoints,
            endpoints=endpoints,
            coefficients=coefficients,
225
        )
226
        return BoundingBoxes.wrap_like(self, output)
227
228
229
230

    def elastic(
        self,
        displacement: torch.Tensor,
231
        interpolation: Union[InterpolationMode, int] = InterpolationMode.BILINEAR,
Philip Meier's avatar
Philip Meier committed
232
        fill: _FillTypeJIT = None,
233
234
    ) -> BoundingBoxes:
        output = self._F.elastic_bounding_boxes(
Philip Meier's avatar
Philip Meier committed
235
            self.as_subclass(torch.Tensor), self.format, self.canvas_size, displacement=displacement
236
        )
237
        return BoundingBoxes.wrap_like(self, output)