maite-datasets 0.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,215 @@
1
+ from __future__ import annotations
2
+
3
+ __all__ = []
4
+
5
+ from pathlib import Path
6
+ from typing import Any, Literal, Sequence, TypeVar
7
+
8
+ import numpy as np
9
+ from numpy.typing import NDArray
10
+
11
+ from maite_datasets._base import BaseICDataset, DataLocation
12
+ from maite_datasets._mixin._numpy import BaseDatasetNumpyMixin
13
+ from maite_datasets._protocols import Transform
14
+
15
+ MNISTClassStringMap = Literal[
16
+ "zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine"
17
+ ]
18
+ TMNISTClassMap = TypeVar(
19
+ "TMNISTClassMap", MNISTClassStringMap, int, list[MNISTClassStringMap], list[int]
20
+ )
21
+ CorruptionStringMap = Literal[
22
+ "identity",
23
+ "shot_noise",
24
+ "impulse_noise",
25
+ "glass_blur",
26
+ "motion_blur",
27
+ "shear",
28
+ "scale",
29
+ "rotate",
30
+ "brightness",
31
+ "translate",
32
+ "stripe",
33
+ "fog",
34
+ "spatter",
35
+ "dotted_line",
36
+ "zigzag",
37
+ "canny_edges",
38
+ ]
39
+
40
+
41
+ class MNIST(BaseICDataset[NDArray[np.number[Any]]], BaseDatasetNumpyMixin):
42
+ """`MNIST <https://en.wikipedia.org/wiki/MNIST_database>`_ Dataset and `Corruptions <https://arxiv.org/abs/1906.02337>`_.
43
+
44
+ There are 15 different styles of corruptions. This class downloads differently depending on if you
45
+ need just the original dataset or if you need corruptions. If you need both a corrupt version and the
46
+ original version then choose `corruption="identity"` as this downloads all of the corrupt datasets and
47
+ provides the original as `identity`. If you just need the original, then using `corruption=None` will
48
+ download only the original dataset to save time and space.
49
+
50
+ Parameters
51
+ ----------
52
+ root : str or pathlib.Path
53
+ Root directory where the data should be downloaded to or the ``minst`` folder of the already downloaded data.
54
+ image_set : "train", "test" or "base", default "train"
55
+ If "base", returns all of the data to allow the user to create their own splits.
56
+ corruption : "identity", "shot_noise", "impulse_noise", "glass_blur", "motion_blur", \
57
+ "shear", "scale", "rotate", "brightness", "translate", "stripe", "fog", "spatter", \
58
+ "dotted_line", "zigzag", "canny_edges" or None, default None
59
+ Corruption to apply to the data.
60
+ transforms : Transform, Sequence[Transform] or None, default None
61
+ Transform(s) to apply to the data.
62
+ download : bool, default False
63
+ If True, downloads the dataset from the internet and puts it in root directory.
64
+ Class checks to see if data is already downloaded to ensure it does not create a duplicate download.
65
+ verbose : bool, default False
66
+ If True, outputs print statements.
67
+
68
+ Attributes
69
+ ----------
70
+ path : pathlib.Path
71
+ Location of the folder containing the data.
72
+ image_set : "train", "test" or "base"
73
+ The selected image set from the dataset.
74
+ index2label : dict[int, str]
75
+ Dictionary which translates from class integers to the associated class strings.
76
+ label2index : dict[str, int]
77
+ Dictionary which translates from class strings to the associated class integers.
78
+ metadata : DatasetMetadata
79
+ Typed dictionary containing dataset metadata, such as `id` which returns the dataset class name.
80
+ corruption : str or None
81
+ Corruption applied to the data.
82
+ transforms : Sequence[Transform]
83
+ The transforms to be applied to the data.
84
+ size : int
85
+ The size of the dataset.
86
+
87
+ Note
88
+ ----
89
+ Data License: `CC BY 4.0 <https://creativecommons.org/licenses/by/4.0/>`_ for corruption dataset
90
+ """
91
+
92
+ _resources = [
93
+ DataLocation(
94
+ url="https://storage.googleapis.com/tensorflow/tf-keras-datasets/mnist.npz",
95
+ filename="mnist.npz",
96
+ md5=False,
97
+ checksum="731c5ac602752760c8e48fbffcf8c3b850d9dc2a2aedcf2cc48468fc17b673d1",
98
+ ),
99
+ DataLocation(
100
+ url="https://zenodo.org/record/3239543/files/mnist_c.zip",
101
+ filename="mnist_c.zip",
102
+ md5=True,
103
+ checksum="4b34b33045869ee6d424616cd3a65da3",
104
+ ),
105
+ ]
106
+
107
+ index2label: dict[int, str] = {
108
+ 0: "zero",
109
+ 1: "one",
110
+ 2: "two",
111
+ 3: "three",
112
+ 4: "four",
113
+ 5: "five",
114
+ 6: "six",
115
+ 7: "seven",
116
+ 8: "eight",
117
+ 9: "nine",
118
+ }
119
+
120
+ def __init__(
121
+ self,
122
+ root: str | Path,
123
+ image_set: Literal["train", "test", "base"] = "train",
124
+ corruption: CorruptionStringMap | None = None,
125
+ transforms: Transform[NDArray[np.number[Any]]]
126
+ | Sequence[Transform[NDArray[np.number[Any]]]]
127
+ | None = None,
128
+ download: bool = False,
129
+ verbose: bool = False,
130
+ ) -> None:
131
+ self.corruption = corruption
132
+ if self.corruption == "identity" and verbose:
133
+ print("Identity is not a corrupted dataset but the original MNIST dataset.")
134
+ self._resource_index = 0 if self.corruption is None else 1
135
+
136
+ super().__init__(
137
+ root,
138
+ image_set,
139
+ transforms,
140
+ download,
141
+ verbose,
142
+ )
143
+
144
+ def _load_data_inner(self) -> tuple[list[str], list[int], dict[str, Any]]:
145
+ """Function to load in the file paths for the data and labels from the correct data format"""
146
+ if self.corruption is None:
147
+ try:
148
+ file_path = self.path / self._resource.filename
149
+ self._loaded_data, labels = self._grab_data(file_path)
150
+ except FileNotFoundError:
151
+ self._loaded_data, labels = self._load_corruption()
152
+ else:
153
+ self._loaded_data, labels = self._load_corruption()
154
+
155
+ index_strings = np.arange(self._loaded_data.shape[0]).astype(str).tolist()
156
+ return index_strings, labels.tolist(), {}
157
+
158
+ def _load_corruption(self) -> tuple[NDArray[np.number[Any]], NDArray[np.uintp]]:
159
+ """Function to load in the file paths for the data and labels for the different corrupt data formats"""
160
+ corruption = self.corruption if self.corruption is not None else "identity"
161
+ base_path = self.path / "mnist_c" / corruption
162
+ if self.image_set == "base":
163
+ raw_data = []
164
+ raw_labels = []
165
+ for group in ["train", "test"]:
166
+ file_path = base_path / f"{group}_images.npy"
167
+ raw_data.append(self._grab_corruption_data(file_path))
168
+
169
+ label_path = base_path / f"{group}_labels.npy"
170
+ raw_labels.append(self._grab_corruption_data(label_path))
171
+
172
+ data = np.concatenate(raw_data, axis=0).transpose(0, 3, 1, 2)
173
+ labels = np.concatenate(raw_labels).astype(np.uintp)
174
+ else:
175
+ file_path = base_path / f"{self.image_set}_images.npy"
176
+ data = self._grab_corruption_data(file_path)
177
+ data = data.astype(np.float64).transpose(0, 3, 1, 2)
178
+
179
+ label_path = base_path / f"{self.image_set}_labels.npy"
180
+ labels = self._grab_corruption_data(label_path)
181
+ labels = labels.astype(np.uintp)
182
+
183
+ return data, labels
184
+
185
+ def _grab_data(
186
+ self, path: Path
187
+ ) -> tuple[NDArray[np.number[Any]], NDArray[np.uintp]]:
188
+ """Function to load in the data numpy array"""
189
+ with np.load(path, allow_pickle=True) as data_array:
190
+ if self.image_set == "base":
191
+ data = np.concatenate(
192
+ [data_array["x_train"], data_array["x_test"]], axis=0
193
+ )
194
+ labels = np.concatenate(
195
+ [data_array["y_train"], data_array["y_test"]], axis=0
196
+ ).astype(np.uintp)
197
+ else:
198
+ data, labels = (
199
+ data_array[f"x_{self.image_set}"],
200
+ data_array[f"y_{self.image_set}"].astype(np.uintp),
201
+ )
202
+ data = np.expand_dims(data, axis=1)
203
+ return data, labels
204
+
205
+ def _grab_corruption_data(self, path: Path) -> NDArray[np.number[Any]]:
206
+ """Function to load in the data numpy array for the previously chosen corrupt format"""
207
+ return np.load(path, allow_pickle=False)
208
+
209
+ def _read_file(self, path: str) -> NDArray[np.number[Any]]:
210
+ """
211
+ Function to grab the correct image from the loaded data.
212
+ Overwrite of the base `_read_file` because data is an all or nothing load.
213
+ """
214
+ index = int(path)
215
+ return self._loaded_data[index]
@@ -0,0 +1,150 @@
1
+ from __future__ import annotations
2
+
3
+ __all__ = []
4
+
5
+ from pathlib import Path
6
+ from typing import Any, Sequence
7
+
8
+ import numpy as np
9
+ from numpy.typing import NDArray
10
+
11
+ from maite_datasets._base import BaseICDataset, DataLocation
12
+ from maite_datasets._mixin._numpy import BaseDatasetNumpyMixin
13
+ from maite_datasets._protocols import Transform
14
+
15
+
16
+ class Ships(BaseICDataset[NDArray[np.number[Any]]], BaseDatasetNumpyMixin):
17
+ """
18
+ A dataset that focuses on identifying ships from satellite images.
19
+
20
+ The dataset comes from kaggle,
21
+ `Ships in Satellite Imagery <https://www.kaggle.com/datasets/rhammell/ships-in-satellite-imagery>`_.
22
+ The images come from Planet satellite imagery when they gave
23
+ `open-access to a portion of their data <https://www.planet.com/pulse/open-california-rapideye-data/>`_.
24
+
25
+ There are 4000 80x80x3 (HWC) images of ships, sea, and land.
26
+ There are also 8 larger scene images similar to what would be operationally provided.
27
+
28
+ Parameters
29
+ ----------
30
+ root : str or pathlib.Path
31
+ Root directory where the data should be downloaded to or the ``ships`` folder of the already downloaded data.
32
+ transforms : Transform, Sequence[Transform] or None, default None
33
+ Transform(s) to apply to the data.
34
+ download : bool, default False
35
+ If True, downloads the dataset from the internet and puts it in root directory.
36
+ Class checks to see if data is already downloaded to ensure it does not create a duplicate download.
37
+ verbose : bool, default False
38
+ If True, outputs print statements.
39
+
40
+ Attributes
41
+ ----------
42
+ path : pathlib.Path
43
+ Location of the folder containing the data.
44
+ image_set : "base"
45
+ The base image set is the only available image set for the Ships dataset.
46
+ index2label : dict[int, str]
47
+ Dictionary which translates from class integers to the associated class strings.
48
+ label2index : dict[str, int]
49
+ Dictionary which translates from class strings to the associated class integers.
50
+ metadata : DatasetMetadata
51
+ Typed dictionary containing dataset metadata, such as `id` which returns the dataset class name.
52
+ transforms : Sequence[Transform]
53
+ The transforms to be applied to the data.
54
+ size : int
55
+ The size of the dataset.
56
+
57
+ Note
58
+ ----
59
+ Data License: `CC BY-SA 4.0 <https://creativecommons.org/licenses/by-sa/4.0/>`_
60
+ """
61
+
62
+ _resources = [
63
+ DataLocation(
64
+ url="https://zenodo.org/record/3611230/files/ships-in-satellite-imagery.zip",
65
+ filename="ships-in-satellite-imagery.zip",
66
+ md5=True,
67
+ checksum="b2e8a41ed029592b373bd72ee4b89f32",
68
+ ),
69
+ ]
70
+
71
+ index2label: dict[int, str] = {
72
+ 0: "no ship",
73
+ 1: "ship",
74
+ }
75
+
76
+ def __init__(
77
+ self,
78
+ root: str | Path,
79
+ transforms: Transform[NDArray[np.number[Any]]]
80
+ | Sequence[Transform[NDArray[np.number[Any]]]]
81
+ | None = None,
82
+ download: bool = False,
83
+ verbose: bool = False,
84
+ ) -> None:
85
+ super().__init__(
86
+ root,
87
+ "base",
88
+ transforms,
89
+ download,
90
+ verbose,
91
+ )
92
+ self._scenes: list[str] = self._load_scenes()
93
+ self._remove_extraneous_json_file()
94
+
95
+ def _remove_extraneous_json_file(self) -> None:
96
+ json_path = self.path / "shipsnet.json"
97
+ if json_path.exists():
98
+ json_path.unlink()
99
+
100
+ def _load_data_inner(self) -> tuple[list[str], list[int], dict[str, Any]]:
101
+ """Function to load in the file paths for the data and labels"""
102
+ file_data = {
103
+ "label": [],
104
+ "scene_id": [],
105
+ "longitude": [],
106
+ "latitude": [],
107
+ "path": [],
108
+ }
109
+ data_folder = sorted((self.path / "shipsnet").glob("*.png"))
110
+ if not data_folder:
111
+ raise FileNotFoundError
112
+
113
+ for entry in data_folder:
114
+ # Remove file extension and split by "_"
115
+ parts = entry.stem.split("__")
116
+ file_data["label"].append(int(parts[0]))
117
+ file_data["scene_id"].append(parts[1])
118
+ lat_lon = parts[2].split("_")
119
+ file_data["longitude"].append(float(lat_lon[0]))
120
+ file_data["latitude"].append(float(lat_lon[1]))
121
+ file_data["path"].append(entry)
122
+ data = file_data.pop("path")
123
+ labels = file_data.pop("label")
124
+ return data, labels, file_data
125
+
126
+ def _load_scenes(self) -> list[str]:
127
+ """Function to load in the file paths for the scene images"""
128
+ return sorted(str(entry) for entry in (self.path / "scenes").glob("*.png"))
129
+
130
+ def get_scene(self, index: int) -> NDArray[np.number[Any]]:
131
+ """
132
+ Get the desired satellite image (scene) by passing in the index of the desired file.
133
+
134
+ Args
135
+ ----
136
+ index : int
137
+ Value of the desired data point
138
+
139
+ Returns
140
+ -------
141
+ NDArray[np.number]
142
+ Scene image
143
+
144
+ Note
145
+ ----
146
+ The scene will be returned with the channel axis first.
147
+ """
148
+ scene = self._read_file(self._scenes[index])
149
+ np.moveaxis(scene, -1, 0)
150
+ return scene
@@ -0,0 +1,20 @@
1
+ """Module for MAITE compliant Object Detection datasets."""
2
+
3
+ from maite_datasets.object_detection._antiuav import AntiUAVDetection
4
+ from maite_datasets.object_detection._milco import MILCO
5
+ from maite_datasets.object_detection._seadrone import SeaDrone
6
+ from maite_datasets.object_detection._voc import VOCDetection
7
+
8
+ __all__ = [
9
+ "AntiUAVDetection",
10
+ "MILCO",
11
+ "SeaDrone",
12
+ "VOCDetection",
13
+ ]
14
+
15
+ import importlib.util
16
+
17
+ if importlib.util.find_spec("torch") is not None:
18
+ from maite_datasets.object_detection._voc_torch import VOCDetectionTorch
19
+
20
+ __all__ += ["VOCDetectionTorch"]
@@ -0,0 +1,200 @@
1
+ from __future__ import annotations
2
+
3
+ __all__ = []
4
+
5
+ from pathlib import Path
6
+ from typing import Any, Literal, Sequence
7
+
8
+ import numpy as np
9
+ from defusedxml.ElementTree import parse
10
+ from numpy.typing import NDArray
11
+
12
+ from maite_datasets._base import BaseODDataset, DataLocation
13
+ from maite_datasets._mixin._numpy import BaseDatasetNumpyMixin
14
+ from maite_datasets._protocols import Transform
15
+
16
+
17
+ class AntiUAVDetection(
18
+ BaseODDataset[NDArray[np.number[Any]], list[str], str], BaseDatasetNumpyMixin
19
+ ):
20
+ """
21
+ A UAV detection dataset focused on detecting UAVs in natural images against large variation in backgrounds.
22
+
23
+ The dataset comes from the paper
24
+ `Vision-based Anti-UAV Detection and Tracking <https://ieeexplore.ieee.org/document/9785379>`_
25
+ by Jie Zhao et. al. (2022).
26
+
27
+ The dataset is approximately 1.3 GB and can be found `here <https://github.com/wangdongdut/DUT-Anti-UAV>`_.
28
+ Images are collected against a variety of different backgrounds with a variety in the number and type of UAV.
29
+ Ground truth labels are provided for the train, validation and test set.
30
+ There are 35 different types of drones along with a variety in lighting conditions and weather conditions.
31
+
32
+ There are 10,000 images: 5200 images in the training set, 2200 images in the validation set,
33
+ and 2600 images in the test set.
34
+ The dataset only has a single UAV class with the focus being on identifying object location in the image.
35
+ Ground-truth bounding boxes are provided in (x0, y0, x1, y1) format.
36
+ The images come in a variety of sizes from 3744 x 5616 to 160 x 240.
37
+
38
+ Parameters
39
+ ----------
40
+ root : str or pathlib.Path
41
+ Root directory where the data should be downloaded to or
42
+ the ``antiuavdetection`` folder of the already downloaded data.
43
+ image_set: "train", "val", "test", or "base", default "train"
44
+ If "base", then the full dataset is selected (train, val and test).
45
+ transforms : Transform, Sequence[Transform] or None, default None
46
+ Transform(s) to apply to the data.
47
+ download : bool, default False
48
+ If True, downloads the dataset from the internet and puts it in root directory.
49
+ Class checks to see if data is already downloaded to ensure it does not create a duplicate download.
50
+ verbose : bool, default False
51
+ If True, outputs print statements.
52
+
53
+ Attributes
54
+ ----------
55
+ path : pathlib.Path
56
+ Location of the folder containing the data.
57
+ image_set : "train", "val", "test", or "base"
58
+ The selected image set from the dataset.
59
+ index2label : dict[int, str]
60
+ Dictionary which translates from class integers to the associated class strings.
61
+ label2index : dict[str, int]
62
+ Dictionary which translates from class strings to the associated class integers.
63
+ metadata : DatasetMetadata
64
+ Typed dictionary containing dataset metadata, such as `id` which returns the dataset class name.
65
+ transforms : Sequence[Transform]
66
+ The transforms to be applied to the data.
67
+ size : int
68
+ The size of the dataset.
69
+
70
+ Note
71
+ ----
72
+ Data License: `Apache 2.0 <https://www.apache.org/licenses/LICENSE-2.0.txt>`_
73
+ """
74
+
75
+ # Need to run the sha256 on the files and then store that
76
+ _resources = [
77
+ DataLocation(
78
+ url="https://drive.usercontent.google.com/download?id=1RVsSGPUKTdmoyoPTBTWwroyulLek1eTj&export=download&authuser=0&confirm=t&uuid=6bca4f94-a242-4bc2-9663-fb03cd94ef2c&at=APcmpox0--NroQ_3bqeTFaJxP7Pw%3A1746552902927",
79
+ filename="train.zip",
80
+ md5=False,
81
+ checksum="14f927290556df60e23cedfa80dffc10dc21e4a3b6843e150cfc49644376eece",
82
+ ),
83
+ DataLocation(
84
+ url="https://drive.usercontent.google.com/download?id=1333uEQfGuqTKslRkkeLSCxylh6AQ0X6n&export=download&authuser=0&confirm=t&uuid=c2ad2f01-aca8-4a85-96bb-b8ef6e40feea&at=APcmpozY-8bhk3nZSFaYbE8rq1Fi%3A1746551543297",
85
+ filename="val.zip",
86
+ md5=False,
87
+ checksum="238be0ceb3e7c5be6711ee3247e49df2750d52f91f54f5366c68bebac112ebf8",
88
+ ),
89
+ DataLocation(
90
+ url="https://drive.usercontent.google.com/download?id=1L1zeW1EMDLlXHClSDcCjl3rs_A6sVai0&export=download&authuser=0&confirm=t&uuid=5a1d7650-d8cd-4461-8354-7daf7292f06c&at=APcmpozLQC1CuP-n5_UX2JnP53Zo%3A1746551676177",
91
+ filename="test.zip",
92
+ md5=False,
93
+ checksum="a671989a01cff98c684aeb084e59b86f4152c50499d86152eb970a9fc7fb1cbe",
94
+ ),
95
+ ]
96
+
97
+ index2label: dict[int, str] = {
98
+ 0: "unknown",
99
+ 1: "UAV",
100
+ }
101
+
102
+ def __init__(
103
+ self,
104
+ root: str | Path,
105
+ image_set: Literal["train", "val", "test", "base"] = "train",
106
+ transforms: Transform[NDArray[np.number[Any]]]
107
+ | Sequence[Transform[NDArray[np.number[Any]]]]
108
+ | None = None,
109
+ download: bool = False,
110
+ verbose: bool = False,
111
+ ) -> None:
112
+ super().__init__(
113
+ root,
114
+ image_set,
115
+ transforms,
116
+ download,
117
+ verbose,
118
+ )
119
+
120
+ def _load_data(self) -> tuple[list[str], list[str], dict[str, list[Any]]]:
121
+ filepaths: list[str] = []
122
+ targets: list[str] = []
123
+ datum_metadata: dict[str, list[Any]] = {}
124
+
125
+ # If base, load all resources
126
+ if self.image_set == "base":
127
+ metadata_list: list[dict[str, Any]] = []
128
+
129
+ for resource in self._resources:
130
+ self._resource = resource
131
+ resource_filepaths, resource_targets, resource_metadata = (
132
+ super()._load_data()
133
+ )
134
+ filepaths.extend(resource_filepaths)
135
+ targets.extend(resource_targets)
136
+ metadata_list.append(resource_metadata)
137
+
138
+ # Combine metadata
139
+ for data_dict in metadata_list:
140
+ for key, val in data_dict.items():
141
+ str_key = str(key) # Ensure key is string
142
+ if str_key not in datum_metadata:
143
+ datum_metadata[str_key] = []
144
+ datum_metadata[str_key].extend(val)
145
+
146
+ else:
147
+ # Grab only the desired data
148
+ for resource in self._resources:
149
+ if self.image_set in resource.filename:
150
+ self._resource = resource
151
+ resource_filepaths, resource_targets, resource_metadata = (
152
+ super()._load_data()
153
+ )
154
+ filepaths.extend(resource_filepaths)
155
+ targets.extend(resource_targets)
156
+ datum_metadata.update(resource_metadata)
157
+
158
+ return filepaths, targets, datum_metadata
159
+
160
+ def _load_data_inner(self) -> tuple[list[str], list[str], dict[str, Any]]:
161
+ resource_name = self._resource.filename[:-4]
162
+ base_dir = self.path / resource_name
163
+ data_folder = sorted((base_dir / "img").glob("*.jpg"))
164
+ if not data_folder:
165
+ raise FileNotFoundError
166
+
167
+ file_data = {
168
+ "image_id": [f"{resource_name}_{entry.name}" for entry in data_folder]
169
+ }
170
+ data = [str(entry) for entry in data_folder]
171
+ annotations = sorted(str(entry) for entry in (base_dir / "xml").glob("*.xml"))
172
+
173
+ return data, annotations, file_data
174
+
175
+ def _read_annotations(
176
+ self, annotation: str
177
+ ) -> tuple[list[list[float]], list[int], dict[str, Any]]:
178
+ """Function for extracting the info for the label and boxes"""
179
+ boxes: list[list[float]] = []
180
+ labels = []
181
+ root = parse(annotation).getroot()
182
+ if root is None:
183
+ raise ValueError(f"Unable to parse {annotation}")
184
+ additional_meta: dict[str, Any] = {
185
+ "image_width": int(root.findtext("size/width", default="-1")),
186
+ "image_height": int(root.findtext("size/height", default="-1")),
187
+ "image_depth": int(root.findtext("size/depth", default="-1")),
188
+ }
189
+ for obj in root.findall("object"):
190
+ labels.append(1 if obj.findtext("name", default="") == "UAV" else 0)
191
+ boxes.append(
192
+ [
193
+ float(obj.findtext("bndbox/xmin", default="0")),
194
+ float(obj.findtext("bndbox/ymin", default="0")),
195
+ float(obj.findtext("bndbox/xmax", default="0")),
196
+ float(obj.findtext("bndbox/ymax", default="0")),
197
+ ]
198
+ )
199
+
200
+ return boxes, labels, additional_meta