Skip to content

boxes

add_centroids(df, prefix=None, input_bbox_format='coco')

Computes bbox centroids.

Parameters:

Name Type Description Default
df pd.DataFrame

pd.DataFrame with COCO annotations.

required
prefix str

Prefix to apply to column names, use for scaled data. Defaults to None.

None
input_bbox_format str

Input bounding box format. Can be "coco" or "corners". "coco" ["col_left", "row_top", "width", "height"] "corners" ["col_left", "row_top", "col_right", "row_bottom"] Defaults to "coco".

'coco'

Returns:

Type Description
pd.DataFrame

pd.DataFrame with new columns [prefix_]row_centroid/col_centroid

Source code in pyodi/core/boxes.py
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
def add_centroids(
    df: pd.DataFrame, prefix: str = None, input_bbox_format: str = "coco"
) -> pd.DataFrame:
    """Computes bbox centroids.

    Args:
        df: pd.DataFrame with COCO annotations.
        prefix: Prefix to apply to column names, use for scaled data. Defaults to None.
        input_bbox_format: Input bounding box format. Can be "coco" or "corners".
            "coco" ["col_left", "row_top", "width", "height"]
            "corners" ["col_left", "row_top", "col_right", "row_bottom"]
            Defaults to "coco".

    Returns:
        pd.DataFrame with new columns [prefix_]row_centroid/col_centroid

    """
    columns = ["col_centroid", "row_centroid"]
    bboxes = get_bbox_array(df, prefix=prefix, input_bbox_format=input_bbox_format)

    if prefix:
        columns = [f"{prefix}_{col}" for col in columns]

    df[columns[0]] = bboxes[:, 0] + bboxes[:, 2] // 2
    df[columns[1]] = bboxes[:, 1] + bboxes[:, 3] // 2

    return df

check_bbox_formats(args)

Check if bounding boxes are in a valid format.

Source code in pyodi/core/boxes.py
 8
 9
10
11
12
13
14
def check_bbox_formats(*args: Any) -> None:
    """Check if bounding boxes are in a valid format."""
    for arg in args:
        if not (arg in ["coco", "corners"]):
            raise ValueError(
                f"Invalid format {arg}, only coco and corners format are allowed"
            )

coco_to_corners(bboxes)

Transforms bboxes array from coco format to corners.

Parameters:

Name Type Description Default
bboxes np.ndarray

Array with dimension N x 4 with bbox coordinates in corner format

required

Returns:

Type Description
np.ndarray

Array with dimension N x 4 with bbox coordinates in coco format

np.ndarray

["col_left", "row_top", "col_right", "row_bottom"]

Source code in pyodi/core/boxes.py
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
def coco_to_corners(bboxes: np.ndarray) -> np.ndarray:
    """Transforms bboxes array from coco format to corners.

    Args:
        bboxes: Array with dimension N x 4 with bbox coordinates in corner format
        [col_left, row_top, width, height].

    Returns:
        Array with dimension N x 4 with bbox coordinates in coco format
        ["col_left", "row_top", "col_right", "row_bottom"]

    """
    bboxes = bboxes.copy()
    bboxes[..., 2:] = bboxes[..., :2] + bboxes[..., 2:]

    if (bboxes < 0).any():
        logger.warning("Clipping bboxes to min corner 0, found negative value")
        bboxes = np.clip(bboxes, 0, None)
    return bboxes

corners_to_coco(bboxes)

Transforms bboxes array from corners format to coco.

Parameters:

Name Type Description Default
bboxes np.ndarray

Array with dimension N x 4 with bbox coordinates in corner format

required

Returns:

Type Description
np.ndarray

Array with dimension N x 4 with bbox coordinates in coco format

np.ndarray

[col_left, row_top, width, height].

Source code in pyodi/core/boxes.py
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
def corners_to_coco(bboxes: np.ndarray) -> np.ndarray:
    """Transforms bboxes array from corners format to coco.

    Args:
        bboxes: Array with dimension N x 4 with bbox coordinates in corner format
        ["col_left", "row_top", "col_right", "row_bottom"]

    Returns:
        Array with dimension N x 4 with bbox coordinates in coco format
        [col_left, row_top, width, height].

    """
    bboxes = bboxes.copy()
    bboxes[..., 2:] = bboxes[..., 2:] - bboxes[..., :2]
    return bboxes

denormalize(bboxes, image_width, image_height)

Transforms bboxes array from (0, 1) range to pixels.

Bboxes can be in both formats

"coco" ["col_left", "row_top", "width", "height"] "corners" ["col_left", "row_top", "col_right", "row_bottom"]

Parameters:

Name Type Description Default
bboxes np.ndarray

Bounding boxes.

required
image_width int

Image width in pixels.

required
image_height int

Image height in pixels.

required

Returns:

Type Description
np.ndarray

Bounding boxes with coordinates in pixels.

Source code in pyodi/core/boxes.py
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
def denormalize(bboxes: np.ndarray, image_width: int, image_height: int) -> np.ndarray:
    """Transforms bboxes array from (0, 1) range to pixels.

    Bboxes can be in both formats:
        "coco" ["col_left", "row_top", "width", "height"]
        "corners" ["col_left", "row_top", "col_right", "row_bottom"]

    Args:
        bboxes: Bounding boxes.
        image_width: Image width in pixels.
        image_height: Image height in pixels.

    Returns:
        Bounding boxes with coordinates in pixels.

    """
    norms = np.array([image_width, image_height, image_width, image_height])
    bboxes = bboxes * norms
    return bboxes

filter_zero_area_bboxes(df)

Filters those bboxes with height or width equal to zero.

Parameters:

Name Type Description Default
df pd.DataFrame

pd.DataFrame with COCO annotations.

required

Returns:

Type Description
pd.DataFrame

Filtered pd.DataFrame with COCO annotations.

Source code in pyodi/core/boxes.py
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
def filter_zero_area_bboxes(df: pd.DataFrame) -> pd.DataFrame:
    """Filters those bboxes with height or width equal to zero.

    Args:
        df: pd.DataFrame with COCO annotations.

    Returns:
        Filtered pd.DataFrame with COCO annotations.

    """
    cols = ["width", "height"]
    all_bboxes = len(df)
    df = df[(df[cols] > 0).all(axis=1)].reset_index()
    filtered_bboxes = len(df)

    n_filtered = all_bboxes - filtered_bboxes

    if n_filtered:
        logger.warning(
            f"A total of {n_filtered} bboxes have been filtered from your data "
            "for having area equal to zero."
        )

    return df

get_bbox_array(df, prefix=None, input_bbox_format='coco', output_bbox_format='coco')

Returns array with bbox coordinates.

Parameters:

Name Type Description Default
df pd.DataFrame

pd.DataFrame with COCO annotations.

required
prefix Optional[str]

Prefix to apply to column names, use for scaled data. Defaults to None.

None
input_bbox_format str

Input bounding box format. Can be "coco" or "corners". Defaults to "coco".

'coco'
output_bbox_format str

Output bounding box format. Can be "coco" or "corners". Defaults to "coco".

'coco'

Returns:

Type Description
np.ndarray

Array with dimension N x 4 with bbox coordinates.

Examples:

coco:

>>>[col_left, row_top, width, height]

corners:

>>>[col_left, row_top, col_right, row_bottom]
Source code in pyodi/core/boxes.py
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
def get_bbox_array(
    df: pd.DataFrame,
    prefix: Optional[str] = None,
    input_bbox_format: str = "coco",
    output_bbox_format: str = "coco",
) -> np.ndarray:
    """Returns array with bbox coordinates.

    Args:
        df: pd.DataFrame with COCO annotations.
        prefix: Prefix to apply to column names, use for scaled data. Defaults to None.
        input_bbox_format: Input bounding box format. Can be "coco" or "corners".
            Defaults to "coco".
        output_bbox_format: Output bounding box format. Can be "coco" or "corners".
            Defaults to "coco".

    Returns:
        Array with dimension N x 4 with bbox coordinates.

    Examples:
        `coco`:
        >>>[col_left, row_top, width, height]

        `corners`:
        >>>[col_left, row_top, col_right, row_bottom]

    """
    check_bbox_formats(input_bbox_format, output_bbox_format)

    columns = get_bbox_column_names(input_bbox_format, prefix=prefix)
    bboxes = df[columns].to_numpy()

    if input_bbox_format != output_bbox_format:
        convert = globals()[f"{input_bbox_format}_to_{output_bbox_format}"]
        bboxes = convert(bboxes)

    return bboxes

get_bbox_column_names(bbox_format, prefix=None)

Returns predefined column names for each format.

When bbox_format is 'coco' column names are ["col_left", "row_top", "width", "height"], when 'corners' ["col_left", "row_top", "col_right", "row_bottom"].

Parameters:

Name Type Description Default
bbox_format str

Bounding box format. Can be "coco" or "corners".

required
prefix Optional[str]

Prefix to apply to column names, use for scaled data. Defaults to None.

None

Returns:

Type Description
List[str]

Column names for specified bbox format

Source code in pyodi/core/boxes.py
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
def get_bbox_column_names(bbox_format: str, prefix: Optional[str] = None) -> List[str]:
    """Returns predefined column names for each format.

    When bbox_format is 'coco' column names are
    ["col_left", "row_top", "width", "height"], when 'corners'
    ["col_left", "row_top", "col_right", "row_bottom"].

    Args:
        bbox_format: Bounding box format. Can be "coco" or "corners".
        prefix: Prefix to apply to column names, use for scaled data. Defaults to None.

    Returns:
        Column names for specified bbox format

    """
    if bbox_format == "coco":
        columns = ["col_left", "row_top", "width", "height"]
    elif bbox_format == "corners":
        columns = ["col_left", "row_top", "col_right", "row_bottom"]
    else:
        raise ValueError(f"Invalid bbox format, {bbox_format} does not exist")

    if prefix:
        columns = [f"{prefix}_{col}" for col in columns]

    return columns

get_df_from_bboxes(bboxes, input_bbox_format='coco', output_bbox_format='corners')

Creates pd.DataFrame of annotations in Coco format from array of bboxes.

Parameters:

Name Type Description Default
bboxes np.ndarray

Array of bboxes of shape [n, 4].

required
input_bbox_format str

Input bounding box format. Can be "coco" or "corners". Defaults to "coco".

'coco'
output_bbox_format str

Output bounding box format. Can be "coco" or "corners". Defaults to "corners".

'corners'

Returns:

Type Description
pd.DataFrame

pd.DataFrame with Coco annotations.

Source code in pyodi/core/boxes.py
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
def get_df_from_bboxes(
    bboxes: np.ndarray,
    input_bbox_format: str = "coco",
    output_bbox_format: str = "corners",
) -> pd.DataFrame:
    """Creates pd.DataFrame of annotations in Coco format from array of bboxes.

    Args:
        bboxes: Array of bboxes of shape [n, 4].
        input_bbox_format: Input bounding box format. Can be "coco" or "corners".
            Defaults to "coco".
        output_bbox_format: Output bounding box format. Can be "coco" or "corners".
            Defaults to "corners".

    Returns:
        pd.DataFrame with Coco annotations.

    """
    check_bbox_formats(input_bbox_format, output_bbox_format)

    if input_bbox_format != output_bbox_format:
        convert = globals()[f"{input_bbox_format}_to_{output_bbox_format}"]
        bboxes = convert(bboxes)

    return pd.DataFrame(bboxes, columns=get_bbox_column_names(output_bbox_format))

get_scale_and_ratio(df, prefix=None)

Returns df with area and ratio per bbox measurements.

Parameters:

Name Type Description Default
df pd.DataFrame

pd.DataFrame with COCO annotations.

required
prefix str

Prefix to apply to column names, use for scaled data.

None

Returns:

Type Description
pd.DataFrame

pd.DataFrame with new columns [prefix_]area/ratio

Source code in pyodi/core/boxes.py
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
def get_scale_and_ratio(df: pd.DataFrame, prefix: str = None) -> pd.DataFrame:
    """Returns df with area and ratio per bbox measurements.

    Args:
        df: pd.DataFrame with COCO annotations.
        prefix: Prefix to apply to column names, use for scaled data.

    Returns:
        pd.DataFrame with new columns [prefix_]area/ratio

    """
    columns = ["width", "height", "scale", "ratio"]

    if prefix:
        columns = [f"{prefix}_{col}" for col in columns]

    df[columns[2]] = np.sqrt(df[columns[0]] * df[columns[1]])
    df[columns[3]] = df[columns[1]] / df[columns[0]]

    return df

normalize(bboxes, image_width, image_height)

Transforms bboxes array from pixels to (0, 1) range.

Bboxes can be in both formats

"coco" ["col_left", "row_top", "width", "height"] "corners" ["col_left", "row_top", "col_right", "row_bottom"]

Parameters:

Name Type Description Default
bboxes np.ndarray

Bounding boxes.

required
image_width int

Image width in pixels.

required
image_height int

Image height in pixels.

required

Returns:

Type Description
np.ndarray

Bounding boxes with coordinates in (0, 1) range.

Source code in pyodi/core/boxes.py
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
def normalize(bboxes: np.ndarray, image_width: int, image_height: int) -> np.ndarray:
    """Transforms bboxes array from pixels to (0, 1) range.

    Bboxes can be in both formats:
        "coco" ["col_left", "row_top", "width", "height"]
        "corners" ["col_left", "row_top", "col_right", "row_bottom"]

    Args:
        bboxes: Bounding boxes.
        image_width: Image width in pixels.
        image_height: Image height in pixels.

    Returns:
        Bounding boxes with coordinates in (0, 1) range.
    """
    norms = np.array([image_width, image_height, image_width, image_height])
    bboxes = bboxes * 1 / norms

    return bboxes

scale_bbox_dimensions(df, input_size=(1280, 720), keep_ratio=False)

Resizes bboxes dimensions to model input size.

Parameters:

Name Type Description Default
df pd.DataFrame

pd.DataFrame with COCO annotations.

required
input_size Tuple[int, int]

Model input size. Defaults to (1280, 720).

(1280, 720)
keep_ratio bool

Whether to keep the aspect ratio or not. Defaults to False.

False

Returns:

Type Description
pd.DataFrame

pd.DataFrame with COCO annotations and scaled image sizes.

Source code in pyodi/core/boxes.py
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
def scale_bbox_dimensions(
    df: pd.DataFrame,
    input_size: Tuple[int, int] = (1280, 720),
    keep_ratio: bool = False,
) -> pd.DataFrame:
    """Resizes bboxes dimensions to model input size.

    Args:
        df: pd.DataFrame with COCO annotations.
        input_size: Model input size. Defaults to (1280, 720).
        keep_ratio: Whether to keep the aspect ratio or not. Defaults to False.

    Returns:
        pd.DataFrame with COCO annotations and scaled image sizes.

    """
    if keep_ratio:
        scale_factor = pd.concat(
            [
                max(input_size) / df[["img_height", "img_width"]].max(1),
                min(input_size) / df[["img_height", "img_width"]].min(1),
            ],
            axis=1,
        ).min(1)
        w_scale = np.round(df["img_width"] * scale_factor) / df["img_width"]
        h_scale = np.round(df["img_height"] * scale_factor) / df["img_height"]
    else:
        w_scale = input_size[0] / df["img_width"]
        h_scale = input_size[1] / df["img_height"]

    df["scaled_col_left"] = np.ceil(df["col_left"] * w_scale)
    df["scaled_row_top"] = np.ceil(df["row_top"] * h_scale)
    df["scaled_width"] = np.ceil(df["width"] * w_scale)
    df["scaled_height"] = np.ceil(df["height"] * h_scale)

    return df