edgefirst-validator 4.2.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.
Files changed (73) hide show
  1. deepview/modelpack/utils/argmax.py +16 -0
  2. edgefirst/validator/__init__.py +1 -0
  3. edgefirst/validator/__main__.py +375 -0
  4. edgefirst/validator/datasets/__init__.py +118 -0
  5. edgefirst/validator/datasets/cache.py +296 -0
  6. edgefirst/validator/datasets/core.py +250 -0
  7. edgefirst/validator/datasets/darknet.py +446 -0
  8. edgefirst/validator/datasets/database.py +1067 -0
  9. edgefirst/validator/datasets/instance/__init__.py +4 -0
  10. edgefirst/validator/datasets/instance/core.py +222 -0
  11. edgefirst/validator/datasets/instance/detection.py +145 -0
  12. edgefirst/validator/datasets/instance/multitask.py +80 -0
  13. edgefirst/validator/datasets/instance/segmentation.py +120 -0
  14. edgefirst/validator/datasets/utils/fetch.py +682 -0
  15. edgefirst/validator/datasets/utils/readers.py +425 -0
  16. edgefirst/validator/datasets/utils/transformations.py +1695 -0
  17. edgefirst/validator/evaluators/__init__.py +17 -0
  18. edgefirst/validator/evaluators/callbacks/__init__.py +3 -0
  19. edgefirst/validator/evaluators/callbacks/core.py +192 -0
  20. edgefirst/validator/evaluators/callbacks/plots.py +900 -0
  21. edgefirst/validator/evaluators/callbacks/studio.py +234 -0
  22. edgefirst/validator/evaluators/core.py +257 -0
  23. edgefirst/validator/evaluators/detection.py +749 -0
  24. edgefirst/validator/evaluators/multitask.py +270 -0
  25. edgefirst/validator/evaluators/parameters/__init__.py +53 -0
  26. edgefirst/validator/evaluators/parameters/core.py +554 -0
  27. edgefirst/validator/evaluators/parameters/dataset.py +239 -0
  28. edgefirst/validator/evaluators/parameters/model.py +338 -0
  29. edgefirst/validator/evaluators/parameters/validation.py +528 -0
  30. edgefirst/validator/evaluators/segmentation.py +729 -0
  31. edgefirst/validator/evaluators/utils/__init__.py +3 -0
  32. edgefirst/validator/evaluators/utils/classify.py +292 -0
  33. edgefirst/validator/evaluators/utils/match.py +262 -0
  34. edgefirst/validator/evaluators/utils/timer.py +132 -0
  35. edgefirst/validator/metrics/__init__.py +9 -0
  36. edgefirst/validator/metrics/data/__init__.py +7 -0
  37. edgefirst/validator/metrics/data/label.py +668 -0
  38. edgefirst/validator/metrics/data/metrics.py +759 -0
  39. edgefirst/validator/metrics/data/plots.py +476 -0
  40. edgefirst/validator/metrics/data/stats.py +507 -0
  41. edgefirst/validator/metrics/detection.py +595 -0
  42. edgefirst/validator/metrics/segmentation.py +173 -0
  43. edgefirst/validator/metrics/utils/math.py +717 -0
  44. edgefirst/validator/publishers/__init__.py +3 -0
  45. edgefirst/validator/publishers/console.py +147 -0
  46. edgefirst/validator/publishers/studio.py +128 -0
  47. edgefirst/validator/publishers/tensorboard.py +119 -0
  48. edgefirst/validator/publishers/utils/logger.py +111 -0
  49. edgefirst/validator/publishers/utils/table.py +403 -0
  50. edgefirst/validator/runners/__init__.py +8 -0
  51. edgefirst/validator/runners/core.py +727 -0
  52. edgefirst/validator/runners/deepviewrt.py +177 -0
  53. edgefirst/validator/runners/hailo.py +263 -0
  54. edgefirst/validator/runners/keras.py +150 -0
  55. edgefirst/validator/runners/kinara.py +265 -0
  56. edgefirst/validator/runners/offline.py +228 -0
  57. edgefirst/validator/runners/onnx.py +241 -0
  58. edgefirst/validator/runners/processing/decode.py +320 -0
  59. edgefirst/validator/runners/processing/dvapi.py +4192 -0
  60. edgefirst/validator/runners/processing/nms.py +637 -0
  61. edgefirst/validator/runners/processing/outputs.py +507 -0
  62. edgefirst/validator/runners/tensorrt.py +321 -0
  63. edgefirst/validator/runners/tflite.py +221 -0
  64. edgefirst/validator/validate.py +843 -0
  65. edgefirst/validator/visualize/__init__.py +3 -0
  66. edgefirst/validator/visualize/detection.py +623 -0
  67. edgefirst/validator/visualize/segmentation.py +281 -0
  68. edgefirst/validator/visualize/utils/plots.py +635 -0
  69. edgefirst_validator-4.2.1.dist-info/METADATA +111 -0
  70. edgefirst_validator-4.2.1.dist-info/RECORD +73 -0
  71. edgefirst_validator-4.2.1.dist-info/WHEEL +5 -0
  72. edgefirst_validator-4.2.1.dist-info/entry_points.txt +2 -0
  73. edgefirst_validator-4.2.1.dist-info/top_level.txt +2 -0
@@ -0,0 +1,296 @@
1
+ """
2
+ Implementations for downloading and caching the dataset.
3
+ """
4
+
5
+ from __future__ import annotations
6
+
7
+ import os
8
+ import json
9
+ import glob
10
+ from typing import TYPE_CHECKING, List, Tuple
11
+
12
+ import tqdm
13
+ import lmdb
14
+ import numpy as np
15
+
16
+ from edgefirst_client import Client, FileType, AnnotationType
17
+
18
+ from edgefirst.validator.datasets import LMDBDatabase
19
+ from edgefirst.validator.publishers.utils.logger import logger
20
+
21
+ if TYPE_CHECKING:
22
+ from edgefirst.validator.evaluators import DatasetParameters
23
+ from edgefirst.validator.datasets import Dataset
24
+
25
+
26
+ class StudioCache:
27
+ def __init__(
28
+ self,
29
+ parameters: DatasetParameters,
30
+ stages: List[Tuple[str, str]],
31
+ client: Client = None,
32
+ session_id: str = None,
33
+ val_group: str = "val"
34
+ ):
35
+ """
36
+ Communicate with EdgeFirst Studio for
37
+ fetching and caching the dataset and post the progress.
38
+
39
+ Parameters
40
+ ----------
41
+ parameters: DatasetParameters
42
+ This contains dataset parameters set from the command line.
43
+ stages: List[Tuple[str, str]]
44
+ This contains the stages that tracks each progress in Studio.
45
+ A stage contains ("stage identifier", "stage description").
46
+ client: Client
47
+ EdgeFirst Client object.
48
+ session_id: str
49
+ This is the validation session ID in EdgeFirst Studio for
50
+ posting validation metrics.
51
+ val_group: str
52
+ The dataset validation group set in EdgeFirst Studio.
53
+ """
54
+
55
+ self.parameters = parameters
56
+ self.stages = stages
57
+ self.client = client
58
+ self.val_session = None
59
+ self.val_group = val_group
60
+
61
+ if self.client is not None:
62
+ self.val_session = self.client.validation_session(session_id)
63
+
64
+ def complete_stage(self, stage: str, message: str):
65
+ """
66
+ Completes the stage on studio at the end of the task.
67
+
68
+ Parameters
69
+ ----------
70
+ stage: str
71
+ The stage identifier.
72
+ message: str
73
+ The message to project in the Studio GUI.
74
+ """
75
+ self.client.update_stage(
76
+ self.val_session.task.id,
77
+ stage=stage,
78
+ status="Running",
79
+ message=message,
80
+ percentage=100
81
+ )
82
+
83
+ def download(self, dataset: str):
84
+ """
85
+ Download the dataset from EdgeFirst Studio into the device.
86
+
87
+ Parameters
88
+ ----------
89
+ dataset: str
90
+ The path to directory to save the dataset.
91
+
92
+ Returns
93
+ -------
94
+ pl.DataFrame
95
+ This is the polars dataframe of the annotations.
96
+ """
97
+
98
+ if self.client is None:
99
+ raise ValueError(
100
+ "EdgeFirst Client needs to be defined to download the dataset.")
101
+
102
+ annotation_types = [AnnotationType.Box2d, AnnotationType.Mask]
103
+ os.makedirs(dataset, exist_ok=True)
104
+
105
+ dataset_id = self.val_session.dataset_id
106
+ annotation_id = self.val_session.annotation_set_id
107
+
108
+ # Download Images
109
+ with tqdm.tqdm(
110
+ total=0,
111
+ desc=f"Downloading Images from Dataset ID: ds-{dataset_id.value:x}"
112
+ ) as bar:
113
+ def progress(current, total):
114
+ if total != bar.total:
115
+ bar.reset(total)
116
+ bar.update(current - bar.n)
117
+ self.client.update_stage(
118
+ self.val_session.task.id,
119
+ stage=self.stages[0][0],
120
+ status="Running",
121
+ message=self.stages[0][1],
122
+ percentage=int(100 * current / total)
123
+ )
124
+
125
+ self.client.download_dataset(
126
+ dataset_id=dataset_id,
127
+ groups=[self.val_group],
128
+ types=[FileType.Image],
129
+ output=os.path.join(dataset, "images"),
130
+ progress=progress,
131
+ )
132
+
133
+ total_images = len(glob.glob(os.path.join(dataset, "images", "*"),
134
+ recursive=True))
135
+ logger(f"Downloaded a total of {total_images} images.", code="INFO")
136
+
137
+ # Download Annotations
138
+ with tqdm.tqdm(
139
+ total=0,
140
+ desc=f"Downloading Annotations from Annotation ID: as-{annotation_id.value:x}"
141
+ ) as bar:
142
+ def progress(current, total):
143
+ if total != bar.total:
144
+ bar.reset(total)
145
+ bar.update(current - bar.n)
146
+ self.client.update_stage(
147
+ self.val_session.task.id,
148
+ stage=self.stages[1][0],
149
+ status="Running",
150
+ message=self.stages[1][1],
151
+ percentage=int(100 * current / total)
152
+ )
153
+
154
+ dataframe = self.client.annotations_dataframe(
155
+ annotation_set_id=annotation_id,
156
+ groups=[self.val_group],
157
+ annotation_types=annotation_types,
158
+ progress=progress
159
+ )
160
+ dataframe.write_ipc(os.path.join(dataset, "dataset.arrow"))
161
+
162
+ logger(f"Downloaded a total of {dataframe.shape[0]} annotations.",
163
+ code="INFO")
164
+
165
+ return dataframe
166
+
167
+ def cache(self, dataset: Dataset,
168
+ cache: str = "cache/val.db") -> LMDBDatabase:
169
+ """
170
+ Cache the dataset provided into an LMDB file.
171
+
172
+ Parameters
173
+ ----------
174
+ dataset: Dataset
175
+ This can either be a DarkNet or EdgeFirst Dataset object.
176
+ cache: str
177
+ The path to the cache file to save the cache.
178
+
179
+ Returns
180
+ -------
181
+ LMDBDatabase
182
+ The instantiated cached dataset.
183
+ """
184
+
185
+ # Remove existing cache file.
186
+ if os.path.exists(cache):
187
+ os.remove(cache)
188
+ os.makedirs(os.path.dirname(cache), exist_ok=True)
189
+
190
+ dbenv = lmdb.open(
191
+ cache,
192
+ map_size=1024 ** 4,
193
+ max_dbs=10,
194
+ subdir=False,
195
+ lock=False
196
+ )
197
+
198
+ classes_db = dbenv.open_db(b'classes') # Unique labels in the dataset.
199
+ # Name of each image in the dataset.
200
+ names_db = dbenv.open_db(b'names')
201
+ # All preprocessed images in the dataset.
202
+ images_db = dbenv.open_db(b'images')
203
+ # Images used for visualization.
204
+ visual_images_db = dbenv.open_db(b'visual')
205
+ boxes_db = dbenv.open_db(b'box2d') # 2D box annotations.
206
+ # Integer labels for each boxes or masks.
207
+ labels_db = dbenv.open_db(b'labels')
208
+ masks_db = dbenv.open_db(b'masks') # 2D masks annotations.
209
+
210
+ # Store the unique labels in the dataset.
211
+ with dbenv.begin(write=True) as txn:
212
+ txn.put(
213
+ b'classes',
214
+ json.dumps(self.parameters.labels).encode(),
215
+ db=classes_db
216
+ )
217
+
218
+ samples = tqdm.tqdm(dataset.collect_samples(), colour="green")
219
+ samples.set_description("Caching dataset")
220
+
221
+ for i, sample in enumerate(samples):
222
+ gt_instance = dataset.read_sample(sample)
223
+
224
+ with dbenv.begin(write=True) as txn:
225
+ image_path = gt_instance.image_path
226
+ image = gt_instance.image
227
+ visual_image = gt_instance.visual_image
228
+ shapes = gt_instance.shapes
229
+ image_shape = gt_instance.image_shape
230
+ labels = gt_instance.labels
231
+
232
+ if self.parameters.common.with_boxes:
233
+ boxes = gt_instance.boxes
234
+ else:
235
+ boxes = np.array([], dtype=np.float32)
236
+
237
+ if self.parameters.common.with_masks:
238
+ masks = gt_instance.mask
239
+ else:
240
+ masks = np.zeros(shapes[0], dtype=np.uint8)
241
+
242
+ name = os.path.basename(image_path)
243
+ txn.put(name.encode(), None, db=names_db)
244
+ txn.put(name.encode(), image.tobytes(), db=images_db)
245
+ # Place the current shape of the image.
246
+ txn.put(f'{name}/shape'.encode(),
247
+ np.array(image.shape, dtype=np.int32).tobytes(),
248
+ db=images_db)
249
+
250
+ if visual_image is not None:
251
+ txn.put(
252
+ name.encode(),
253
+ visual_image.tobytes(),
254
+ db=visual_images_db)
255
+ # Place the current shape of the visualization image.
256
+ txn.put(f'{name}/shape'.encode(),
257
+ np.array(visual_image.shape,
258
+ dtype=np.int32).tobytes(),
259
+ db=visual_images_db)
260
+
261
+ # Place the label transformation shapes after letterboxing.
262
+ txn.put(f'{name}/shapes'.encode(),
263
+ json.dumps(shapes).encode(),
264
+ db=images_db)
265
+ # Place the original shape of the image.
266
+ txn.put(f'{name}/im_shape'.encode(),
267
+ np.array(image_shape, dtype=np.int32).tobytes(),
268
+ db=images_db)
269
+ txn.put(name.encode(), boxes.tobytes(), db=boxes_db)
270
+ txn.put(name.encode(), labels.tobytes(), db=labels_db)
271
+ txn.put(name.encode(), masks.tobytes(), db=masks_db)
272
+ txn.put(f'{name}/mask_shape'.encode(),
273
+ np.array(masks.shape, dtype=np.int32).tobytes(),
274
+ db=masks_db)
275
+
276
+ if self.client is not None:
277
+ self.client.update_stage(
278
+ self.val_session.task.id,
279
+ stage=self.stages[2][0],
280
+ status="Running",
281
+ message=self.stages[2][1],
282
+ percentage=int(100 * i / len(dataset))
283
+ )
284
+
285
+ dbenv.close()
286
+ if self.client is not None:
287
+ self.complete_stage(
288
+ stage=self.stages[2][0],
289
+ message=self.stages[2][1]
290
+ )
291
+
292
+ return LMDBDatabase(
293
+ source=cache,
294
+ parameters=self.parameters,
295
+ timer=dataset.timer
296
+ )
@@ -0,0 +1,250 @@
1
+ """
2
+ Common parent dataset implementations.
3
+ """
4
+
5
+ from __future__ import annotations
6
+ from typing import TYPE_CHECKING, Union, Tuple
7
+
8
+ import numpy as np
9
+ import tqdm
10
+
11
+ from edgefirst.validator.datasets.utils.readers import read_image
12
+ from edgefirst.validator.datasets.utils.transformations import (xcycwh2xyxy,
13
+ xywh2xyxy,
14
+ normalize,
15
+ denormalize_polygon)
16
+
17
+ if TYPE_CHECKING:
18
+ from edgefirst.validator.evaluators import DatasetParameters, TimerContext
19
+ from edgefirst_python import TensorImage # type: ignore
20
+
21
+ from edgefirst.validator.datasets import (
22
+ SegmentationInstance, DetectionInstance, MultitaskInstance, Instance)
23
+
24
+
25
+ class Dataset:
26
+ """
27
+ Abstract dataset class for providing template methods in the dataset.
28
+
29
+ Parameters
30
+ ----------
31
+ source: str
32
+ The path to the source dataset.
33
+ parameters: DatasetParameters
34
+ This contains dataset parameters set from the command line.
35
+ timer: TimerContext
36
+ A timer object for handling validation timings for the model.
37
+ info_dataset: dict
38
+ Contains information such as:
39
+
40
+ .. code-block:: python
41
+
42
+ {
43
+ "classes": [list of unique labels],
44
+ "validation":
45
+ {
46
+ "images: 'path to the images',
47
+ "annotations": 'path to the annotations'
48
+ }
49
+ }
50
+
51
+ *Note: the classes are optional and the path to the images
52
+ and annotations can be the same.*
53
+
54
+ Raises
55
+ ------
56
+ ValueError
57
+ Raised if the provided parameters in certain methods
58
+ does not conform to the specified data type.
59
+ """
60
+
61
+ def __init__(
62
+ self,
63
+ source: str,
64
+ parameters: DatasetParameters,
65
+ timer: TimerContext,
66
+ info_dataset: dict = None
67
+ ):
68
+ self.source = source
69
+ self.parameters = parameters
70
+ self.timer = timer
71
+ self.info_dataset = info_dataset
72
+ self.samples = []
73
+
74
+ self.transformer = None
75
+ if self.parameters.box_format == 'xcycwh':
76
+ self.transformer = xcycwh2xyxy
77
+ elif self.parameters.box_format == 'xywh':
78
+ self.transformer = xywh2xyxy
79
+ else:
80
+ self.transformer = None
81
+
82
+ self.normalizer = None
83
+ self.denormalizer = None
84
+ if self.parameters.normalized:
85
+ if self.parameters.common.with_masks:
86
+ self.denormalizer = denormalize_polygon
87
+ else:
88
+ if self.parameters.common.with_boxes:
89
+ self.normalizer = normalize
90
+
91
+ def __len__(self) -> int:
92
+ """
93
+ Returns the number of samples in the dataset.
94
+ """
95
+ return len(self.samples)
96
+
97
+ def __iter__(self):
98
+ """
99
+ Reads all the samples in the dataset.
100
+
101
+ Yields
102
+ -------
103
+ Instance
104
+ Yields one sample of the ground truth
105
+ instance which contains information on the image
106
+ as a NumPy array, boxes, labels, and image path.
107
+ """
108
+ if self.parameters.silent:
109
+ samples = self.collect_samples()
110
+ for sample in samples:
111
+ yield self.read_sample(sample)
112
+ else:
113
+ samples = tqdm.tqdm(self.collect_samples(), colour="green")
114
+ samples.set_description("Validation Progress")
115
+ for sample in samples:
116
+ yield self.read_sample(sample)
117
+
118
+ def verify_dataset(self):
119
+ """Abstract Method"""
120
+ pass
121
+
122
+ def read_sample(self,
123
+ sample: Union[list, Tuple[str, str], str]) -> Instance:
124
+ """
125
+ Reads one sample from the dataset.
126
+
127
+ Parameters
128
+ -----------
129
+ sample: Union[list, Tuple[str, str], str]
130
+ For EdgeFirstDatabase, this is a list. For Darknet datasets,
131
+ this is a Tuple[str, str] containing the path to the image
132
+ and annotations. For dataset cache, this is a string
133
+ as the image name.
134
+
135
+ A single dataset sample contains the indices
136
+ in the dataframe pointing to all the annotations
137
+ in the dataset for this sample.
138
+
139
+ Returns
140
+ -------
141
+ Instance
142
+ The ground truth instance objects contains the annotations
143
+ representing the ground truth of the image.
144
+ """
145
+ if self.parameters.common.with_boxes and self.parameters.common.with_masks:
146
+ return self.build_multitask_instance(sample)
147
+ elif self.parameters.common.with_boxes:
148
+ return self.build_detection_instance(sample)
149
+ elif self.parameters.common.with_masks:
150
+ return self.build_segmentation_instance(sample)
151
+ else:
152
+ raise ValueError(
153
+ "Could not determine model task as detection or segmentation.")
154
+
155
+ def load_image(
156
+ self,
157
+ image_path: str,
158
+ backend: str = "hal"
159
+ ) -> Union[TensorImage, np.ndarray]:
160
+ """
161
+ Load the image into memory using various libraries: "hal", "opencv",
162
+ or "pillow".
163
+
164
+ Parameters
165
+ ----------
166
+ image_path: str
167
+ The path to the image.
168
+ backend: str
169
+ Specify the backend library for resizing the image
170
+ from the options "hal", "opencv", "pillow".
171
+
172
+ Returns
173
+ -------
174
+ Union[edgefirst_python.TensorImage, np.ndarray]
175
+ TensorImage is returned when using "hal". Otherwise, a
176
+ NumPy array is returned.
177
+ """
178
+
179
+ if backend == "hal":
180
+ try:
181
+ import edgefirst_python # type: ignore
182
+ except ImportError:
183
+ raise ImportError(
184
+ "EdgeFirst HAL is needed to read the image.")
185
+ # Read the image.
186
+ return edgefirst_python.TensorImage.load(
187
+ image_path, fourcc=edgefirst_python.FourCC.RGBA)
188
+ elif backend == "opencv":
189
+ try:
190
+ import cv2 # type: ignore
191
+ except ImportError:
192
+ raise ImportError("OpenCV is needed to read the image.")
193
+
194
+ return cv2.imread(image_path)
195
+ else:
196
+ return read_image(image_path, rotate=True)
197
+
198
+ def image(self, sample: Union[tuple, list]):
199
+ """Abstract Method"""
200
+ raise NotImplementedError("Abstract Method")
201
+
202
+ def labels(self, sample: Union[tuple, list]) -> np.ndarray:
203
+ """
204
+ Fetch the labels of the specified sample.
205
+
206
+ Parameters
207
+ ----------
208
+ sample: Union[tuple, list]
209
+ A tuple containing the (image path, annotation path) or
210
+ a list of indices in the polars dataframe for the current sample.
211
+
212
+ Returns
213
+ -------
214
+ np.ndarray
215
+ The labels in the sample containing np.int32 elements.
216
+ """
217
+ return np.array([])
218
+
219
+ def boxes(self, index: int) -> np.ndarray:
220
+ """Abstract Method"""
221
+ raise NotImplementedError("Abstract Method")
222
+
223
+ def mask(self, index: int) -> np.ndarray:
224
+ """Abstract Method"""
225
+ raise NotImplementedError("Abstract Method")
226
+
227
+ def segments(self, index: int) -> np.ndarray:
228
+ """Abstract Method"""
229
+ raise NotImplementedError("Absract Method")
230
+
231
+ def name(self, index: int) -> str:
232
+ """Abstract Method"""
233
+ raise NotImplementedError("Abstract Method")
234
+
235
+ def collect_samples(self):
236
+ """Abstract Method"""
237
+ raise NotImplementedError("This is an abstract method.")
238
+
239
+ def build_detection_instance(self, sample: list) -> DetectionInstance:
240
+ """Abstract Method"""
241
+ pass
242
+
243
+ def build_segmentation_instance(
244
+ self, sample: list) -> SegmentationInstance:
245
+ """Abstract Method"""
246
+ pass
247
+
248
+ def build_multitask_instance(self, sample: list) -> MultitaskInstance:
249
+ """Abstract Method"""
250
+ pass