edgefirst-validator 4.1.12__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 (78) hide show
  1. deepview/modelpack/utils/argmax.py +16 -0
  2. edgefirst/validator/__init__.py +1 -0
  3. edgefirst/validator/__main__.py +336 -0
  4. edgefirst/validator/datasets/__init__.py +131 -0
  5. edgefirst/validator/datasets/arrow.py +147 -0
  6. edgefirst/validator/datasets/cache.py +258 -0
  7. edgefirst/validator/datasets/core.py +245 -0
  8. edgefirst/validator/datasets/darknet.py +432 -0
  9. edgefirst/validator/datasets/database.py +1353 -0
  10. edgefirst/validator/datasets/instance/__init__.py +8 -0
  11. edgefirst/validator/datasets/instance/core.py +223 -0
  12. edgefirst/validator/datasets/instance/detection.py +348 -0
  13. edgefirst/validator/datasets/instance/fusion.py +22 -0
  14. edgefirst/validator/datasets/instance/multitask.py +80 -0
  15. edgefirst/validator/datasets/instance/pose.py +51 -0
  16. edgefirst/validator/datasets/instance/segmentation.py +120 -0
  17. edgefirst/validator/datasets/tfrecord.py +243 -0
  18. edgefirst/validator/datasets/utils/fetch.py +856 -0
  19. edgefirst/validator/datasets/utils/readers.py +598 -0
  20. edgefirst/validator/datasets/utils/transformations.py +1878 -0
  21. edgefirst/validator/evaluators/__init__.py +16 -0
  22. edgefirst/validator/evaluators/callbacks/__init__.py +3 -0
  23. edgefirst/validator/evaluators/callbacks/core.py +194 -0
  24. edgefirst/validator/evaluators/callbacks/plots.py +857 -0
  25. edgefirst/validator/evaluators/callbacks/studio.py +128 -0
  26. edgefirst/validator/evaluators/core.py +255 -0
  27. edgefirst/validator/evaluators/detection.py +795 -0
  28. edgefirst/validator/evaluators/multitask.py +294 -0
  29. edgefirst/validator/evaluators/parameters/__init__.py +53 -0
  30. edgefirst/validator/evaluators/parameters/core.py +443 -0
  31. edgefirst/validator/evaluators/parameters/dataset.py +176 -0
  32. edgefirst/validator/evaluators/parameters/model.py +288 -0
  33. edgefirst/validator/evaluators/parameters/validation.py +529 -0
  34. edgefirst/validator/evaluators/pose.py +155 -0
  35. edgefirst/validator/evaluators/segmentation.py +975 -0
  36. edgefirst/validator/evaluators/utils/__init__.py +2 -0
  37. edgefirst/validator/evaluators/utils/classify.py +284 -0
  38. edgefirst/validator/evaluators/utils/match.py +262 -0
  39. edgefirst/validator/metrics/__init__.py +12 -0
  40. edgefirst/validator/metrics/data/__init__.py +9 -0
  41. edgefirst/validator/metrics/data/label.py +727 -0
  42. edgefirst/validator/metrics/data/metrics.py +753 -0
  43. edgefirst/validator/metrics/data/plots.py +447 -0
  44. edgefirst/validator/metrics/data/stats.py +690 -0
  45. edgefirst/validator/metrics/detection.py +588 -0
  46. edgefirst/validator/metrics/pose.py +80 -0
  47. edgefirst/validator/metrics/segmentation.py +171 -0
  48. edgefirst/validator/metrics/utils/math.py +698 -0
  49. edgefirst/validator/publishers/__init__.py +3 -0
  50. edgefirst/validator/publishers/console.py +78 -0
  51. edgefirst/validator/publishers/studio.py +107 -0
  52. edgefirst/validator/publishers/tensorboard.py +118 -0
  53. edgefirst/validator/publishers/utils/logger.py +111 -0
  54. edgefirst/validator/publishers/utils/table.py +434 -0
  55. edgefirst/validator/runners/__init__.py +7 -0
  56. edgefirst/validator/runners/core.py +644 -0
  57. edgefirst/validator/runners/deepviewrt.py +169 -0
  58. edgefirst/validator/runners/hailo.py +281 -0
  59. edgefirst/validator/runners/keras.py +219 -0
  60. edgefirst/validator/runners/offline.py +228 -0
  61. edgefirst/validator/runners/onnx.py +332 -0
  62. edgefirst/validator/runners/processing/decode.py +105 -0
  63. edgefirst/validator/runners/processing/nms.py +824 -0
  64. edgefirst/validator/runners/processing/postprocess.py +520 -0
  65. edgefirst/validator/runners/processing/preprocess.py +134 -0
  66. edgefirst/validator/runners/tensorrt.py +352 -0
  67. edgefirst/validator/runners/tflite.py +272 -0
  68. edgefirst/validator/validate.py +701 -0
  69. edgefirst/validator/visualize/__init__.py +4 -0
  70. edgefirst/validator/visualize/detection.py +946 -0
  71. edgefirst/validator/visualize/pose.py +243 -0
  72. edgefirst/validator/visualize/segmentation.py +377 -0
  73. edgefirst/validator/visualize/utils/plots.py +686 -0
  74. edgefirst_validator-4.1.12.dist-info/METADATA +114 -0
  75. edgefirst_validator-4.1.12.dist-info/RECORD +78 -0
  76. edgefirst_validator-4.1.12.dist-info/WHEEL +5 -0
  77. edgefirst_validator-4.1.12.dist-info/entry_points.txt +2 -0
  78. edgefirst_validator-4.1.12.dist-info/top_level.txt +2 -0
@@ -0,0 +1,258 @@
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
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 EdgeFirstDatabase
19
+ from edgefirst.validator.publishers.utils.logger import logger
20
+
21
+ if TYPE_CHECKING:
22
+ from edgefirst.validator.evaluators import DatasetParameters
23
+
24
+
25
+ class StudioCache:
26
+ def __init__(
27
+ self,
28
+ parameters: DatasetParameters,
29
+ client: Client,
30
+ session_id: str = None,
31
+ val_group: str = "val"
32
+ ):
33
+ """
34
+ Communicate with EdgeFirst Studio for
35
+ fetching and caching the dataset and post the progress.
36
+
37
+ Parameters
38
+ ----------
39
+ parameters: DatasetParameters
40
+ This contains dataset parameters set from the command line.
41
+ client: Client
42
+ EdgeFirst Client object.
43
+ session_id: str
44
+ This is the validation session ID in EdgeFirst Studio for
45
+ posting validation metrics.
46
+ val_group: str
47
+ The dataset validation group set in EdgeFirst Studio.
48
+ """
49
+
50
+ self.parameters = parameters
51
+ self.client = client
52
+ self.edgefirst_dataset = None
53
+
54
+ self.val_session = self.client.validation_session(session_id)
55
+ self.val_task_id = self.val_session.task.id
56
+ train_session_id = self.val_session.training_session_id
57
+ self.train_session = self.client.training_session(train_session_id)
58
+
59
+ self.val_group = val_group
60
+
61
+ def download(self, dataset: str):
62
+ """
63
+ Download the dataset from EdgeFirst Studio into the device.
64
+
65
+ Parameters
66
+ ----------
67
+ dataset: str
68
+ The path to directory to save the dataset.
69
+
70
+ Returns
71
+ -------
72
+ pl.DataFrame
73
+ This is the polars dataframe of the annotations.
74
+ """
75
+ annotation_types = [AnnotationType.Box2d, AnnotationType.Mask]
76
+ os.makedirs(dataset, exist_ok=True)
77
+
78
+ dataset_id = self.val_session.dataset_id
79
+ annotation_id = self.val_session.annotation_set_id
80
+
81
+ # Download Images
82
+ with tqdm.tqdm(
83
+ total=0,
84
+ desc=f"Downloading Images from Dataset ID: ds-{dataset_id.value:x}"
85
+ ) as bar:
86
+ def progress(current, total):
87
+ if total != bar.total:
88
+ bar.reset(total)
89
+ bar.update(current - bar.n)
90
+ self.client.update_stage(
91
+ self.val_session.task.id,
92
+ stage="fetch_img",
93
+ status="Running",
94
+ description="Downloading Images",
95
+ percentage=int(100 * current / total)
96
+ )
97
+
98
+ self.client.download_dataset(
99
+ dataset_id=dataset_id,
100
+ groups=[self.val_group],
101
+ types=[FileType.Image],
102
+ output=os.path.join(dataset, "images"),
103
+ progress=progress,
104
+ )
105
+
106
+ total_images = len(glob.glob(os.path.join(dataset, "images", "*"),
107
+ recursive=True))
108
+ logger(f"Downloaded a total of {total_images} images.", code="INFO")
109
+
110
+ # Download Annotations
111
+ with tqdm.tqdm(
112
+ total=0,
113
+ desc=f"Downloading Annotations from Annotation ID: as-{annotation_id.value:x}"
114
+ ) as bar:
115
+ def progress(current, total):
116
+ if total != bar.total:
117
+ bar.reset(total)
118
+ bar.update(current - bar.n)
119
+ self.client.update_stage(
120
+ self.val_session.task.id,
121
+ stage="fetch_as",
122
+ status="Running",
123
+ description="Downloading Annotations",
124
+ percentage=int(100 * current / total)
125
+ )
126
+
127
+ dataframe = self.client.annotations_dataframe(
128
+ annotation_set_id=annotation_id,
129
+ groups=[self.val_group],
130
+ annotation_types=annotation_types,
131
+ progress=progress
132
+ )
133
+ dataframe.write_ipc(os.path.join(dataset, "dataset.arrow"))
134
+
135
+ logger(f"Downloaded a total of {dataframe.shape[0]} annotations.",
136
+ code="INFO")
137
+
138
+ return dataframe
139
+
140
+ def cache(self, dataset: str, cache: str = "cache/val.db"):
141
+ """
142
+ Cache the dataset provided into an LMDB file.
143
+
144
+ Parameters
145
+ ----------
146
+ dataset: str
147
+ The path to the dataset directory.
148
+ cache: str
149
+ The path to the cache file to save the cache.
150
+ """
151
+ group_name = os.path.basename(cache)
152
+ logger(f"Caching dataset to {group_name}", code="INFO")
153
+
154
+ os.makedirs(os.path.dirname(cache), exist_ok=True)
155
+
156
+ self.edgefirst_dataset = EdgeFirstDatabase(
157
+ source=dataset,
158
+ parameters=self.parameters,
159
+ )
160
+ ds_iterator = tqdm.tqdm(self.edgefirst_dataset)
161
+
162
+ # Remove existing cache file.
163
+ if os.path.exists(cache):
164
+ os.remove(cache)
165
+
166
+ dbenv = lmdb.open(
167
+ cache,
168
+ map_size=1024 ** 4,
169
+ max_dbs=10,
170
+ subdir=False,
171
+ lock=False
172
+ )
173
+
174
+ names_db = dbenv.open_db(b'names')
175
+ camera_db = dbenv.open_db(b'camera')
176
+ labels_db = dbenv.open_db(b'labels')
177
+
178
+ boxes_db = None
179
+ if self.parameters.common.with_boxes:
180
+ boxes_db = dbenv.open_db(b'box2d')
181
+
182
+ masks_db = None
183
+ if self.parameters.common.with_masks:
184
+ masks_db = dbenv.open_db(b'masks')
185
+
186
+ with dbenv.begin(write=True) as txn:
187
+ txn.put(
188
+ b'labels',
189
+ json.dumps(self.parameters.labels).encode(),
190
+ db=labels_db
191
+ )
192
+
193
+ for i, instance in enumerate(ds_iterator):
194
+ with dbenv.begin(write=True) as txn:
195
+ boxes = None
196
+ masks = None
197
+
198
+ if self.parameters.common.with_boxes and self.parameters.common.with_masks:
199
+ name, image_data, boxes, (masks, masks_shape) = instance
200
+ elif self.parameters.common.with_boxes:
201
+ name, image_data, boxes = instance
202
+ elif self.parameters.common.with_masks:
203
+ name, image_data, (masks, masks_shape) = instance
204
+
205
+ image, shapes, ratio, image_shape = image_data
206
+
207
+ if boxes is not None:
208
+ txn.put(name.encode(), boxes.tobytes(), db=boxes_db)
209
+
210
+ if masks is not None:
211
+ masks = masks.astype(np.uint8).tobytes()
212
+ txn.put(name.encode(), masks, db=masks_db)
213
+
214
+ txn.put(name.encode(), None, db=names_db)
215
+
216
+ if image is not None:
217
+ txn.put(name.encode(), image.tobytes(), db=camera_db)
218
+
219
+ if shapes is not None:
220
+ txn.put(f'{name}/shapes'.encode(),
221
+ json.dumps(shapes).encode(),
222
+ db=camera_db)
223
+
224
+ if ratio is not None:
225
+ txn.put(f'{name}/ratio'.encode(),
226
+ np.array([ratio], dtype=np.float32).tobytes(),
227
+ db=camera_db)
228
+
229
+ # These are the original image dimensions.
230
+ if image_shape is not None:
231
+ txn.put(f'{name}/shape'.encode(),
232
+ np.array(image_shape, dtype=np.int32).tobytes(),
233
+ db=camera_db)
234
+
235
+ # These are the shape of the mask.
236
+ if masks_shape is not None:
237
+ txn.put(f'{name}/mask_shape'.encode(),
238
+ np.array(masks_shape, dtype=np.int32).tobytes(),
239
+ db=masks_db)
240
+
241
+ self.client.update_stage(
242
+ self.val_session.task.id,
243
+ stage="cache",
244
+ status="Running",
245
+ description=f"Caching: {group_name}",
246
+ percentage=int(100 * i / len(ds_iterator))
247
+ )
248
+
249
+ # Close the database.
250
+ dbenv.close()
251
+
252
+ self.client.update_stage(
253
+ self.val_session.task.id,
254
+ stage="cache",
255
+ status="Running",
256
+ description=f"Caching: {group_name}",
257
+ percentage=100
258
+ )
@@ -0,0 +1,245 @@
1
+ """
2
+ Common parent dataset implementations.
3
+ """
4
+
5
+ from __future__ import annotations
6
+
7
+ import io
8
+ import os
9
+ from typing import TYPE_CHECKING, Union
10
+
11
+ import numpy as np
12
+ from PIL import Image
13
+
14
+ from edgefirst.validator.datasets.instance import Instance
15
+ from edgefirst.validator.datasets.utils.transformations import (yolo2xyxy,
16
+ xywh2xyxy,
17
+ normalize,
18
+ denormalize_polygon)
19
+
20
+ if TYPE_CHECKING:
21
+ from deepview.datasets.generators.detection import BaseObjectDetectionGenerator # type: ignore
22
+ from deepview.datasets.readers.darknet import DarknetDetectionReader # type: ignore
23
+ from edgefirst.validator.evaluators import DatasetParameters
24
+
25
+
26
+ class Dataset:
27
+ """
28
+ Abstract dataset class for providing template methods in the dataset.
29
+
30
+ Parameters
31
+ ----------
32
+ source: str
33
+ The path to the source dataset.
34
+ parameters: DatasetParameters
35
+ This contains dataset parameters set from the command line.
36
+
37
+ Raises
38
+ ------
39
+ ValueError
40
+ Raised if the provided parameters in certain methods
41
+ does not conform to the specified data type.
42
+ """
43
+
44
+ def __init__(
45
+ self,
46
+ source: str,
47
+ parameters: DatasetParameters
48
+ ):
49
+ self.source = source
50
+ self.parameters = parameters
51
+
52
+ self.transformer = None
53
+ if self.parameters.box_format == 'yolo':
54
+ self.transformer = yolo2xyxy
55
+ elif self.parameters.box_format == 'coco':
56
+ self.transformer = xywh2xyxy
57
+ else:
58
+ self.transformer = None
59
+
60
+ self.normalizer = None
61
+ self.denormalizer = None
62
+ if self.parameters.normalized:
63
+ if self.parameters.common.with_masks:
64
+ self.denormalizer = denormalize_polygon
65
+ else:
66
+ if self.parameters.common.with_boxes:
67
+ self.normalizer = normalize
68
+
69
+ self.read_timings = list()
70
+ self.load_timings = list()
71
+
72
+ def build_dataset(self):
73
+ """Abstract Method"""
74
+ raise NotImplementedError("This is an abstract method.")
75
+
76
+ def read_sample(self, instance):
77
+ """Abstract Method"""
78
+ raise NotImplementedError("This is an abstract method.")
79
+
80
+ def read_all_samples(
81
+ self,
82
+ info: str = "Validation Progress",
83
+ silent: bool = False
84
+ ):
85
+ """
86
+ Reads all the samples in the dataset.
87
+
88
+ Parameters
89
+ ----------
90
+ info: str
91
+ The description of why image instances are being read.
92
+ By default it is to run validation, hence "Validation Progress".
93
+ silent: bool
94
+ If set to true, prevent validation logging.
95
+
96
+ Yields
97
+ -------
98
+ Instance
99
+ Yields one sample of the ground truth
100
+ instance which contains information on the image
101
+ as a numpy array, boxes, labels, and image path.
102
+ """
103
+ if silent:
104
+ samples = self.build_dataset()
105
+ for sample in samples:
106
+ yield self.read_sample(sample)
107
+ else:
108
+ try:
109
+ from tqdm import tqdm
110
+ except ImportError:
111
+ pass
112
+
113
+ try:
114
+ samples = tqdm(self.build_dataset(), colour="green")
115
+ samples.set_description(info)
116
+ for sample in samples:
117
+ yield self.read_sample(sample)
118
+ except NameError:
119
+ samples = self.build_dataset()
120
+ num_samples = len(samples)
121
+ for index in range(num_samples):
122
+ print("\t - [INFO]: Computing metrics for image: " +
123
+ "%i of %i [%2.f %s]" %
124
+ (index + 1,
125
+ num_samples,
126
+ 100 * ((index + 1) / float(num_samples)),
127
+ '%'), end='\r')
128
+ yield self.read_sample(samples[index])
129
+
130
+ def timings(self):
131
+ """
132
+ Returns a summary of all the timings:
133
+ (mean, avg, max) of the preprocessing time.
134
+
135
+ Returns
136
+ -------
137
+ timings in ms: dict
138
+
139
+ .. code-block:: python
140
+
141
+ {
142
+ 'min_input_time': minimum time to load an image,
143
+ 'max_input_time': maximum time to load an image,
144
+ 'avg_input_time': average time to load an image,
145
+ }
146
+ """
147
+ return {
148
+ 'min_input_time': (np.min(self.load_timings)
149
+ if len(self.load_timings) else 0),
150
+ 'max_input_time': (np.max(self.load_timings)
151
+ if len(self.load_timings) else 0),
152
+ 'avg_input_time': (np.mean(self.load_timings)
153
+ if len(self.load_timings) else 0),
154
+ }
155
+
156
+
157
+ class BaseDataset(Dataset):
158
+ """
159
+ This class utilizes deepview-datasets methods for iterating through
160
+ the images and annotations.
161
+
162
+ Parameters
163
+ ----------
164
+ source: str
165
+ The path to the dataset.
166
+ iterator: Iterator
167
+ Object in deepview-datasets for iterating through
168
+ the images or annotations. This can either be a generator if
169
+ a YAML file was passed, or a Reader if a directory was passed.
170
+ """
171
+
172
+ def __init__(
173
+ self,
174
+ source: str,
175
+ iterator: Union[BaseObjectDetectionGenerator, DarknetDetectionReader]
176
+ ):
177
+ super(BaseDataset, self).__init__(source)
178
+ self.iterator = iterator
179
+
180
+ if isinstance(self.iterator, BaseObjectDetectionGenerator):
181
+ self.storage = self.iterator.reader.storage
182
+ self.labels = self.iterator.reader.classes
183
+ else:
184
+ self.storage = self.iterator.storage
185
+ self.labels = self.iterator.classes
186
+
187
+ def build_dataset(
188
+ self) -> Union[BaseObjectDetectionGenerator, DarknetDetectionReader]:
189
+ """
190
+ Returns the iterator object which already contains all the images
191
+ and annotations read in the dataset.
192
+
193
+ Returns
194
+ -------
195
+ Union[BaseObjectDetectionGenerator, DarknetDetectionReader]
196
+ BaseObjectDetectionGenerator
197
+ A generator if a YAML file was passed.
198
+ DarknetDetectionReader
199
+ Reader if a directory was passed.
200
+ """
201
+ return self.iterator
202
+
203
+ def read_sample(self, sample: tuple) -> Instance:
204
+ """
205
+ Returns the ground truth instance object which is needed to be read
206
+ by validator.
207
+
208
+ Parameters
209
+ ----------
210
+ sample: tuple
211
+ This contains the (image, boxes) in one sample.
212
+
213
+ Returns
214
+ -------
215
+ Instance
216
+ An object that contains the image, boxes, labels, etc.
217
+ """
218
+ from edgefirst.validator.datasets.utils.transformations import yolo2xyxy
219
+
220
+ image, boxes = sample
221
+ if len(image.shape) < 2:
222
+ image = Image.open(io.BytesIO(image)).convert('RGB')
223
+ image = np.asarray(image, dtype=np.uint8)
224
+ height, width, _ = image.shape
225
+
226
+ if isinstance(self.iterator, BaseObjectDetectionGenerator):
227
+ image_path = self.iterator.reader.get_instance_id()
228
+ else:
229
+ image_path = self.iterator.get_instance_id()
230
+
231
+ # Add file extension to allow image saving in disk.
232
+ if os.path.splitext(image_path)[-1] == "":
233
+ image_path += ".png"
234
+
235
+ instance = Instance(image_path)
236
+ instance.height = height
237
+ instance.width = width
238
+ instance.image = image
239
+
240
+ boxes = boxes[np.sum(boxes, axis=-1) != 0]
241
+ instance.boxes = yolo2xyxy(boxes[..., 0:4])
242
+
243
+ labels = boxes[..., 4:5]
244
+ instance.labels = labels
245
+ return instance