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,234 @@
1
+ from __future__ import annotations
2
+
3
+ import traceback
4
+ from typing import TYPE_CHECKING
5
+
6
+ import threading
7
+ from queue import Queue, Empty
8
+ import time
9
+
10
+ from edgefirst.validator.evaluators.callbacks import PlotsCallback, CallbacksList
11
+
12
+ if TYPE_CHECKING:
13
+ from edgefirst.validator.publishers import StudioPublisher
14
+ from edgefirst.validator.evaluators import Evaluator
15
+
16
+
17
+ class StudioProgress:
18
+ """
19
+ Deploy standard validation from the existing evaluator objects
20
+ but also provide communication to EdgeFirst Studio to report
21
+ the progress and the final metrics and evaluation of the model
22
+ performance.
23
+
24
+ Parameters
25
+ ----------
26
+ evaluator: Evaluator
27
+ This object handles running validation by iterating through
28
+ the dataset samples and run model inference to calculate the
29
+ validation metrics at the end of the process.
30
+ studio_publisher: StudioPublisher
31
+ Handles the state updates to the validation session in EdgeFirst Studio.
32
+ stage: str
33
+ The current stage to update for the progress in Studio.
34
+ """
35
+
36
+ def __init__(
37
+ self,
38
+ evaluator: Evaluator,
39
+ studio_publisher: StudioPublisher,
40
+ stage: str = "validate"
41
+ ):
42
+ self.evaluator = evaluator
43
+ plots_callback = PlotsCallback(studio_publisher=studio_publisher,
44
+ parameters=self.evaluator.parameters,
45
+ stage=stage)
46
+ self.callbacks = CallbacksList([plots_callback])
47
+
48
+ # Non-blocking queue + single background worker thread to execute callbacks
49
+ # Bounded queue prevents unbounded memory growth;
50
+ # if full we fallback to sync execution.
51
+ self._queue: Queue = Queue(maxsize=1000)
52
+ self._stop_event = threading.Event()
53
+ self._last_update = 0
54
+ # NOTE: Increasing this interval results in the functions not being
55
+ # called.
56
+ self._update_interval = 0.0001 # Minimum seconds between updates.
57
+ self._worker = threading.Thread(target=self._worker_loop,
58
+ name="studio-callback-worker",
59
+ daemon=True)
60
+ self._worker.start()
61
+
62
+ def _worker_loop(self):
63
+ """
64
+ Background worker that executes
65
+ callback functions from the queue.
66
+ """
67
+ while True:
68
+ try:
69
+ fn, kwargs = self._queue.get(timeout=0.5)
70
+ except Empty:
71
+ if self._stop_event.is_set() and self._queue.empty():
72
+ break
73
+ continue
74
+ try:
75
+ try:
76
+ fn(**kwargs)
77
+ except Exception:
78
+ traceback.print_exc()
79
+ finally:
80
+ try:
81
+ self._queue.task_done()
82
+ except Exception:
83
+ pass
84
+
85
+ def _dispatch(self, method_name: str, /, **kwargs):
86
+ """
87
+ Enqueue a callback method for asynchronous execution.
88
+ If the queue is full, execute synchronously as a best-effort
89
+ fallback to avoid dropping important updates.
90
+
91
+ Parameters
92
+ ----------
93
+ method_name: str
94
+ The name of the callback method to invoke.
95
+ **kwargs: dict
96
+ The keyword arguments to pass to the callback method.
97
+ """
98
+ fn = getattr(self.callbacks, method_name)
99
+ # Always process begin/end/error events
100
+ critical = method_name in ('on_test_begin',
101
+ 'on_test_end',
102
+ 'on_test_error')
103
+ current_time = time.monotonic()
104
+ if critical or (current_time -
105
+ self._last_update) >= self._update_interval:
106
+ try:
107
+ self._queue.put_nowait((fn, kwargs))
108
+ self._last_update = current_time
109
+ except Exception:
110
+ if critical:
111
+ # Only fall back to sync for critical updates.
112
+ try:
113
+ fn(**kwargs)
114
+ except Exception:
115
+ traceback.print_exc()
116
+
117
+ def _drain_and_stop(self, timeout: float = 10.0):
118
+ """
119
+ Wait for queued tasks to finish
120
+ and stop the background worker.
121
+
122
+ Parameters
123
+ ----------
124
+ timeout: float
125
+ The maximum time to wait for the worker to stop.
126
+ """
127
+ # Wait for tasks to be processed
128
+ try:
129
+ self._queue.join()
130
+ except Exception:
131
+ pass
132
+ self._stop_event.set()
133
+ self._worker.join(timeout=timeout)
134
+
135
+ def group_evaluation(self, epoch: int = 0, reset: bool = True):
136
+ """
137
+ Runs model validation on all samples in the dataset.
138
+
139
+ Parameters
140
+ ----------
141
+ epoch: int
142
+ This is the training epoch number. This
143
+ parameter is internal for ModelPack usage.
144
+ Standalone validation does not use this parameter.
145
+ reset: bool
146
+ This is an optional parameter that controls the reset state.
147
+ By default, it will reset at the end of validation to erase
148
+ the data in the containers.
149
+ """
150
+ save_image = bool(self.evaluator.parameters.validation.visualize or
151
+ self.evaluator.parameters.validation.tensorboard)
152
+
153
+ logs = {
154
+ "total": len(self.evaluator.dataset)
155
+ }
156
+
157
+ i = 0
158
+ try:
159
+ self._dispatch("on_test_begin", logs=logs)
160
+ for instance in self.evaluator.instance_collector():
161
+ i += 1
162
+ self._dispatch("on_test_batch_begin", step=i, logs=logs)
163
+ if self.evaluator.parameters.validation.display >= 0:
164
+ if (self.evaluator.counter <
165
+ self.evaluator.parameters.validation.display):
166
+ save_image = True
167
+ self.evaluator.counter += 1
168
+ else:
169
+ save_image = False
170
+
171
+ self.evaluator.single_evaluation(
172
+ instance, epoch=epoch, save_image=save_image)
173
+ self._dispatch("on_test_batch_end", step=i, logs=logs)
174
+ metrics, plots = self.end(epoch=epoch, reset=reset)
175
+
176
+ if (self.evaluator.parameters.model.common.with_boxes and
177
+ self.evaluator.parameters.model.common.with_masks):
178
+ logs["multitask"] = metrics
179
+ logs["plots"] = plots
180
+ logs["timings"] = metrics.timings
181
+
182
+ elif self.evaluator.parameters.model.common.with_boxes:
183
+ logs["detection"] = metrics
184
+ logs["plots"] = plots
185
+ logs["timings"] = metrics.timings
186
+
187
+ elif self.evaluator.parameters.model.common.with_masks:
188
+ logs["segmentation"] = metrics
189
+ logs["plots"] = plots
190
+ logs["timings"] = metrics.timings
191
+
192
+ self._dispatch("on_test_end", logs=logs)
193
+ # Ensure queued callback tasks complete before returning from
194
+ # evaluation
195
+ self._drain_and_stop()
196
+
197
+ except Exception as e:
198
+ self._dispatch("on_test_error", step=i, error=e, logs=logs)
199
+ # Ensure error callback is processed and worker stopped
200
+ self._drain_and_stop()
201
+ error = traceback.format_exc()
202
+ print(error)
203
+ raise e
204
+
205
+ def end(self, epoch: int = 0, reset: bool = True):
206
+ """
207
+ Calculate final metrics and publish the results into
208
+ EdgeFirst Studio.
209
+
210
+ Parameters
211
+ ----------
212
+ epoch: int
213
+ This is the training epoch number. This
214
+ parameter is internal for ModelPack usage.
215
+ Standalone validation does not use this parameter.
216
+ reset: bool
217
+ This is an optional parameter that controls the reset state.
218
+ By default, it will reset at the end of validation to erase
219
+ the data in the containers.
220
+
221
+ Returns
222
+ -------
223
+ Tuple[Metrics, Metrics]
224
+ This returns the detection and segmentation metrics for
225
+ multitask. Otherwise, for single tasks, only one or
226
+ the other is returned.
227
+ """
228
+ # Ensure any pending callback tasks are flushed
229
+ # before final metrics are posted
230
+ try:
231
+ self._queue.join()
232
+ except Exception:
233
+ pass
234
+ return self.evaluator.end(epoch=epoch, reset=reset)
@@ -0,0 +1,257 @@
1
+ from __future__ import annotations
2
+
3
+ import os
4
+ from copy import deepcopy
5
+ from typing import TYPE_CHECKING, Tuple, List
6
+
7
+ import numpy as np
8
+ import matplotlib.figure
9
+
10
+ from edgefirst.validator.publishers.utils.logger import logger
11
+ from edgefirst.validator.visualize.utils.plots import close_figures
12
+ from edgefirst.validator.datasets.utils.transformations import labels2string
13
+ from edgefirst.validator.publishers import ConsolePublisher, TensorBoardPublisher
14
+
15
+ if TYPE_CHECKING:
16
+ from edgefirst.validator.evaluators import CombinedParameters
17
+ from edgefirst.validator.datasets import Dataset
18
+ from edgefirst.validator.metrics import Metrics
19
+ from edgefirst.validator.runners import Runner
20
+ from edgefirst.validator.metrics import Plots
21
+
22
+
23
+ class Evaluator:
24
+ """
25
+ Abstract class that provides a template for the
26
+ Evaluators (detection or segmentation).
27
+
28
+ Parameters
29
+ ----------
30
+ parameters: CombinedParameters
31
+ This is a container for the model, dataset, and validation parameters
32
+ runner: Runner
33
+ This object provides methods to run inference on the model provided.
34
+ dataset: Dataset
35
+ A type of dataset object responsible for reading different types
36
+ of datasets such as Darknet, TFRecords, or EdgeFirst Datasets.
37
+ """
38
+
39
+ def __init__(
40
+ self,
41
+ parameters: CombinedParameters,
42
+ runner: Runner,
43
+ dataset: Dataset,
44
+ ):
45
+ self.parameters = parameters
46
+ self.runner = runner
47
+ self.dataset = dataset
48
+
49
+ self.console_writer = ConsolePublisher(
50
+ self.parameters.validation.visualize)
51
+ self.tensorboard_writer = None
52
+ if self.parameters.validation.tensorboard:
53
+ self.tensorboard_writer = TensorBoardPublisher(
54
+ self.parameters.validation.tensorboard)
55
+
56
+ self.model_name = os.path.basename(os.path.normpath(
57
+ self.parameters.model.model_path))
58
+ if os.path.isfile(self.parameters.dataset.dataset_path):
59
+ self.dataset_name = os.path.basename(os.path.normpath(
60
+ os.path.dirname(self.parameters.dataset.dataset_path)))
61
+ else:
62
+ self.dataset_name = os.path.basename(os.path.normpath(
63
+ self.parameters.dataset.dataset_path))
64
+
65
+ if self.parameters.validation.tensorboard:
66
+ self.save_path = self.parameters.validation.tensorboard
67
+ elif self.tensorboard_writer:
68
+ self.save_path = self.tensorboard_writer.logdir
69
+ elif self.parameters.validation.visualize:
70
+ self.save_path = self.parameters.validation.visualize
71
+ else:
72
+ self.save_path = None
73
+
74
+ self.metrics = None
75
+ self.confusion_matrix = None
76
+
77
+ # This counter is used to determine the number of images saved.
78
+ self.counter = 0
79
+
80
+ def instance_collector(self):
81
+ """Abstract Method"""
82
+ raise NotImplementedError("This is an abstract method")
83
+
84
+ def evaluate(self, instance: dict):
85
+ """Abstract Method"""
86
+ pass
87
+
88
+ def single_evaluation(self, instance: dict, epoch: int, save_image: bool):
89
+ """
90
+ Run model evaluation on a single image/sample.
91
+
92
+ Parameters
93
+ ----------
94
+ instance: dict
95
+ This contains the ground truth and model prediction instances
96
+ with keys "gt_instance", "dt_instance".
97
+ epoch: int
98
+ This is the training epoch number. This
99
+ parameter is internal for ModelPack usage.
100
+ Standalone validation does not use this parameter.
101
+ save_image: bool
102
+ If set to True, this will save the image
103
+ with drawn bounding box results.
104
+ """
105
+ self.evaluate(instance=instance)
106
+
107
+ if save_image:
108
+ gt_instance = instance.get("gt_instance", None)
109
+ dt_instance = instance.get("dt_instance", None)
110
+
111
+ # Convert labels from integers to string for detection.
112
+ gt_instance.labels = np.array(labels2string(
113
+ gt_instance.labels, self.parameters.dataset.labels))
114
+ dt_instance.labels = np.array(labels2string(
115
+ dt_instance.labels, self.parameters.dataset.labels))
116
+ self.visualize(gt_instance, dt_instance, epoch=epoch)
117
+
118
+ def group_evaluation(self, epoch: int = 0, reset: bool = True):
119
+ """
120
+ Runs model validation on all samples in the dataset.
121
+
122
+ Parameters
123
+ ----------
124
+ epoch: int
125
+ This is the training epoch number. This
126
+ parameter is internal for ModelPack usage.
127
+ Standalone validation does not use this parameter.
128
+ reset: bool
129
+ This is an optional parameter that controls the reset state.
130
+ By default, it will reset at the end of validation to erase
131
+ the data in the containers.
132
+ """
133
+ save_image = bool(self.parameters.validation.visualize or
134
+ self.parameters.validation.tensorboard)
135
+
136
+ for instance in self.instance_collector():
137
+ if self.parameters.validation.display >= 0:
138
+ if self.counter < self.parameters.validation.display:
139
+ save_image = True
140
+ self.counter += 1
141
+ else:
142
+ save_image = False
143
+
144
+ if instance.get("dt_instance", None) is None:
145
+ logger(
146
+ "VisionPack Trial Expired. Please use a licensed version" +
147
+ " for complete validation. Contact support@au-zone.com" +
148
+ " for more information.", code="WARNING")
149
+ break
150
+
151
+ self.single_evaluation(
152
+ instance=instance, epoch=epoch, save_image=save_image)
153
+ return self.end(epoch=epoch, reset=reset)
154
+
155
+ def visualize(self):
156
+ """Absract Method"""
157
+ raise NotImplementedError("This is an abstract method")
158
+
159
+ def end(
160
+ self,
161
+ epoch: int = 0,
162
+ reset: bool = True,
163
+ publish: bool = True
164
+ ) -> Tuple[Metrics, Plots]:
165
+ """
166
+ Computes the final metrics and generates the validation plots
167
+ to save the results in disk or publishes to Tensorboard.
168
+
169
+ Parameters
170
+ ----------
171
+ epoch: int
172
+ This is the training epoch number. This
173
+ parameter is internal for ModelPack usage.
174
+ Standalone validation does not use this parameter.
175
+ reset: bool
176
+ This is an optional parameter that controls the reset state.
177
+ By default, it will reset at the end of validation to erase
178
+ the data in the containers.
179
+ publish: bool
180
+ Specify to publish and print the metrics. Default to True.
181
+
182
+ Returns
183
+ -------
184
+ metrics: Metrics
185
+ This is a container for the detection metrics.
186
+ plots: Plots
187
+ This is a container for the validation data for plotting.
188
+ """
189
+ if hasattr(self.runner, "stop"):
190
+ self.runner.stop()
191
+
192
+ if self.runner:
193
+ self.metrics.metrics.timings = self.runner.timer.to_dict()
194
+ self.metrics.run_metrics()
195
+
196
+ # Plot Operations
197
+ if self.parameters.validation.plots:
198
+ if self.parameters.model.common.with_boxes:
199
+ self.metrics.plots.curve_labels = labels2string(
200
+ self.metrics.plots.curve_labels, self.parameters.dataset.labels)
201
+ if (self.parameters.validation.method in ["ultralytics", "yolov7"]
202
+ and self.confusion_matrix is not None):
203
+ self.metrics.plots.confusion_matrix = self.confusion_matrix.matrix
204
+
205
+ if self.parameters.validation.visualize or self.tensorboard_writer:
206
+ plots = self.get_plots()
207
+
208
+ if self.parameters.validation.visualize:
209
+ self.save_plots(plots)
210
+ elif self.tensorboard_writer:
211
+ self.publish_plots(plots, epoch)
212
+ close_figures(plots)
213
+
214
+ if publish:
215
+ # Metric Operations
216
+ if self.tensorboard_writer:
217
+ self.tensorboard_writer.publish_metrics(
218
+ metrics=self.metrics.metrics,
219
+ parameters=self.parameters,
220
+ step=epoch,
221
+ )
222
+ else:
223
+ table = self.console_writer(metrics=self.metrics.metrics,
224
+ parameters=self.parameters)
225
+ if self.parameters.validation.visualize:
226
+ self.console_writer.save_metrics(table)
227
+
228
+ if self.parameters.validation.csv_out:
229
+ self.console_writer.save_csv_metrics(
230
+ metrics=self.metrics.metrics, parameters=self.parameters)
231
+
232
+ # Prevent the reset from taking effect.
233
+ metrics = deepcopy(self.metrics.metrics)
234
+ plots = deepcopy(self.metrics.plots)
235
+ if reset:
236
+ self.metrics.reset()
237
+ return metrics, plots
238
+
239
+ def stop(self):
240
+ """
241
+ Stops any active running processes.
242
+ """
243
+ if hasattr(self.runner, "stop"):
244
+ self.runner.stop()
245
+
246
+ def get_plots(self) -> List[matplotlib.figure.Figure]:
247
+ """Absract Method"""
248
+ pass
249
+
250
+ def save_plots(self, plots: List[matplotlib.figure.Figure]):
251
+ """Absract Method"""
252
+ pass
253
+
254
+ def publish_plots(
255
+ self, plots: List[matplotlib.figure.Figure], epoch: int = 0):
256
+ """Absract Method"""
257
+ pass