@camera.ui/camera-ui-coreml 0.0.20 → 0.0.22
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.
- package/config.schema.json +93 -37
- package/package.json +2 -2
- package/requirements.txt +4 -3
- package/src/camera_detector.py +216 -106
- package/src/detector_defaults.py +82 -28
- package/src/main.py +83 -45
- package/src/object_detector.py +98 -60
- package/src/plugin_typings.py +7 -2
package/config.schema.json
CHANGED
|
@@ -1,43 +1,99 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schema": {
|
|
3
|
-
"
|
|
4
|
-
"type": "
|
|
5
|
-
"title": "
|
|
6
|
-
"
|
|
7
|
-
"
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
3
|
+
"items": {
|
|
4
|
+
"type": "object",
|
|
5
|
+
"title": "Remove Models",
|
|
6
|
+
"opened": true,
|
|
7
|
+
"properties": {
|
|
8
|
+
"model": {
|
|
9
|
+
"type": "string",
|
|
10
|
+
"title": "Model",
|
|
11
|
+
"description": "Model to use for object detection",
|
|
12
|
+
"required": true,
|
|
13
|
+
"store": false,
|
|
14
|
+
"defaultValue": "yolov9m_320 - FP16",
|
|
15
|
+
"enum": [
|
|
16
|
+
"yolo3-tinyu - INT8",
|
|
17
|
+
"yolo3-tinyu_320 - INT8",
|
|
18
|
+
"yolo3-tinyu - FP16",
|
|
19
|
+
"yolo3-tinyu_320 - FP16",
|
|
20
|
+
"yolov5nu - INT8",
|
|
21
|
+
"yolov5nu_320 - INT8",
|
|
22
|
+
"yolov5mu - INT8",
|
|
23
|
+
"yolov5mu_320 - INT8",
|
|
24
|
+
"yolov5nu - FP16",
|
|
25
|
+
"yolov5nu_320 - FP16",
|
|
26
|
+
"yolov5mu - FP16",
|
|
27
|
+
"yolov5mu_320 - FP16",
|
|
28
|
+
"yolov8n - INT8",
|
|
29
|
+
"yolov8n_320 - INT8",
|
|
30
|
+
"yolov8s - INT8",
|
|
31
|
+
"yolov8s_320 - INT8",
|
|
32
|
+
"yolov8m - INT8",
|
|
33
|
+
"yolov8m_320 - INT8",
|
|
34
|
+
"yolov8n - FP16",
|
|
35
|
+
"yolov8n_320 - FP16",
|
|
36
|
+
"yolov8s - FP16",
|
|
37
|
+
"yolov8s_320 - FP16",
|
|
38
|
+
"yolov8m - FP16",
|
|
39
|
+
"yolov8m_320 - FP16",
|
|
40
|
+
"yolov9t - INT8",
|
|
41
|
+
"yolov9t_320 - INT8",
|
|
42
|
+
"yolov9s - INT8",
|
|
43
|
+
"yolov9s_320 - INT8",
|
|
44
|
+
"yolov9m - INT8",
|
|
45
|
+
"yolov9m_320 - INT8",
|
|
46
|
+
"yolov9c - INT8",
|
|
47
|
+
"yolov9c_320 - INT8",
|
|
48
|
+
"yolov9t - FP16",
|
|
49
|
+
"yolov9t_320 - FP16",
|
|
50
|
+
"yolov9s - FP16",
|
|
51
|
+
"yolov9s_320 - FP16",
|
|
52
|
+
"yolov9m - FP16",
|
|
53
|
+
"yolov9m_320 - FP16",
|
|
54
|
+
"yolov9c - FP16",
|
|
55
|
+
"yolov9c_320 - FP16",
|
|
56
|
+
"yolov10n - INT8",
|
|
57
|
+
"yolov10n_320 - INT8",
|
|
58
|
+
"yolov10s - INT8",
|
|
59
|
+
"yolov10s_320 - INT8",
|
|
60
|
+
"yolov10m - INT8",
|
|
61
|
+
"yolov10m_320 - INT8",
|
|
62
|
+
"yolov10l - INT8",
|
|
63
|
+
"yolov10l_320 - INT8",
|
|
64
|
+
"yolov10n - FP16",
|
|
65
|
+
"yolov10n_320 - FP16",
|
|
66
|
+
"yolov10s - FP16",
|
|
67
|
+
"yolov10s_320 - FP16",
|
|
68
|
+
"yolov10m - FP16",
|
|
69
|
+
"yolov10m_320 - FP16",
|
|
70
|
+
"yolov10l - FP16",
|
|
71
|
+
"yolov10l_320 - FP16",
|
|
72
|
+
"yolov11n - INT8",
|
|
73
|
+
"yolov11n_320 - INT8",
|
|
74
|
+
"yolov11s - INT8",
|
|
75
|
+
"yolov11s_320 - INT8",
|
|
76
|
+
"yolov11m - INT8",
|
|
77
|
+
"yolov11m_320 - INT8",
|
|
78
|
+
"yolov11l - INT8",
|
|
79
|
+
"yolov11l_320 - INT8",
|
|
80
|
+
"yolov11n - FP16",
|
|
81
|
+
"yolov11n_320 - FP16",
|
|
82
|
+
"yolov11s - FP16",
|
|
83
|
+
"yolov11s_320 - FP16",
|
|
84
|
+
"yolov11m - FP16",
|
|
85
|
+
"yolov11m_320 - FP16",
|
|
86
|
+
"yolov11l - FP16",
|
|
87
|
+
"yolov11l_320 - FP16"
|
|
88
|
+
]
|
|
89
|
+
}
|
|
90
|
+
},
|
|
91
|
+
"buttons": [
|
|
92
|
+
{
|
|
93
|
+
"label": "Remove",
|
|
94
|
+
"onSubmit": "onRemove"
|
|
95
|
+
}
|
|
31
96
|
]
|
|
32
|
-
},
|
|
33
|
-
"precision": {
|
|
34
|
-
"type": "string",
|
|
35
|
-
"title": "Precision",
|
|
36
|
-
"description": "Precision of the model",
|
|
37
|
-
"required": true,
|
|
38
|
-
"store": true,
|
|
39
|
-
"defaultValue": "FP16",
|
|
40
|
-
"enum": ["INT8", "FP16"]
|
|
41
97
|
}
|
|
42
98
|
}
|
|
43
99
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"displayName": "CoreML",
|
|
3
3
|
"name": "@camera.ui/camera-ui-coreml",
|
|
4
|
-
"version": "0.0.
|
|
4
|
+
"version": "0.0.22",
|
|
5
5
|
"description": "camera.ui coreml plugin",
|
|
6
6
|
"author": "seydx (https://github.com/seydx/camera.ui)",
|
|
7
7
|
"main": "./src/main.py",
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
"url": "https://github.com/seydx/camera.ui/issues"
|
|
22
22
|
},
|
|
23
23
|
"engines": {
|
|
24
|
-
"camera.ui": ">=0.0.
|
|
24
|
+
"camera.ui": ">=0.0.34-alpha.2",
|
|
25
25
|
"node": ">=20.17.0"
|
|
26
26
|
},
|
|
27
27
|
"homepage": "https://github.com/seydx/camera.ui#readme",
|
package/requirements.txt
CHANGED
package/src/camera_detector.py
CHANGED
|
@@ -1,162 +1,272 @@
|
|
|
1
|
+
# camera_detector.py
|
|
1
2
|
import asyncio
|
|
2
|
-
from typing import
|
|
3
|
+
from typing import Optional, cast
|
|
3
4
|
from uuid import uuid4
|
|
4
5
|
|
|
5
6
|
from camera_ui_python_types import (
|
|
6
7
|
CameraDevice,
|
|
8
|
+
CameraPropertyObservableObject,
|
|
7
9
|
CameraStorage,
|
|
8
10
|
Detection,
|
|
9
11
|
LoggerService,
|
|
10
|
-
MotionState,
|
|
11
12
|
ObjectClass,
|
|
12
13
|
PluginAPI,
|
|
13
14
|
)
|
|
15
|
+
|
|
16
|
+
from detector_defaults import available_models, default_model
|
|
14
17
|
from object_detector import ObjectDetector
|
|
15
18
|
from plugin_typings import CameraStorageValues
|
|
16
19
|
|
|
17
|
-
background_tasks = set[asyncio.Task[Any]]()
|
|
18
|
-
|
|
19
20
|
|
|
20
21
|
class CameraDetector:
|
|
21
22
|
def __init__(
|
|
22
23
|
self,
|
|
23
24
|
camera: CameraDevice,
|
|
24
|
-
object_detector: ObjectDetector,
|
|
25
25
|
api: PluginAPI,
|
|
26
26
|
logger: LoggerService,
|
|
27
|
-
has_motion: list[str],
|
|
28
27
|
) -> None:
|
|
29
28
|
super().__init__()
|
|
30
29
|
|
|
31
30
|
self.camera = camera
|
|
32
31
|
self.api = api
|
|
33
|
-
self.
|
|
34
|
-
|
|
32
|
+
self.camera_logger = camera.logger
|
|
33
|
+
|
|
34
|
+
self.object_detector = ObjectDetector(self.api, self.camera_logger)
|
|
35
35
|
|
|
36
|
-
self.has_motion = has_motion
|
|
37
36
|
self.started = False
|
|
38
37
|
self.closed = False
|
|
39
|
-
self.
|
|
38
|
+
self.restarting = False
|
|
39
|
+
|
|
40
|
+
self.initialized = False
|
|
41
|
+
|
|
42
|
+
self.current_generator_type: Optional[bool] = None # True for extended, False for normal
|
|
43
|
+
self.detection_task: Optional[asyncio.Task[None]] = None
|
|
44
|
+
|
|
45
|
+
# Keep track of camera properties
|
|
46
|
+
self.connected = False
|
|
47
|
+
self.has_motion_detector = False
|
|
48
|
+
self.extended_motion_detector = False
|
|
49
|
+
|
|
50
|
+
# Debounce task
|
|
51
|
+
self.debounce_task: Optional[asyncio.Task[None]] = None
|
|
40
52
|
|
|
41
53
|
self.camera_storage: CameraStorage[CameraStorageValues] = self.__create_camera_storage()
|
|
42
|
-
self.camera.on_connected.subscribe(lambda connected: self.start() if connected else self.close())
|
|
43
|
-
self.camera.on_motion_detected.subscribe(self.__on_motion_detected)
|
|
44
54
|
|
|
45
|
-
def
|
|
55
|
+
async def initialize(self) -> None:
|
|
56
|
+
if self.initialized:
|
|
57
|
+
return
|
|
58
|
+
|
|
59
|
+
self.initialized = True
|
|
60
|
+
|
|
61
|
+
# Initialize object detector
|
|
62
|
+
try:
|
|
63
|
+
model_name = self.camera_storage.values["model"]
|
|
64
|
+
self.camera_logger.log(f"Initialising Object Detection with model: {model_name}")
|
|
65
|
+
await self.object_detector.initialize(model_name)
|
|
66
|
+
except Exception as error:
|
|
67
|
+
self.camera_logger.error("Error initializing Object Detection", error)
|
|
68
|
+
return
|
|
69
|
+
|
|
70
|
+
if self.closed:
|
|
71
|
+
return
|
|
72
|
+
|
|
73
|
+
# Initialize property values
|
|
74
|
+
self.connected = self.camera.connected
|
|
75
|
+
self.has_motion_detector = self.camera.has_motion_detector
|
|
76
|
+
self.extended_motion_detector = self.camera.extended_motion_detector
|
|
77
|
+
|
|
78
|
+
# Subscribe to changes
|
|
79
|
+
self.camera.on_connected.subscribe(self.on_camera_connected)
|
|
80
|
+
self.camera.on_property_change("hasMotionDetector").subscribe(self.on_has_motion_detector_changed)
|
|
81
|
+
self.camera.on_property_change("extendedMotionDetector").subscribe(
|
|
82
|
+
self.on_extended_motion_detector_changed
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
# Start detection if initial conditions are met
|
|
86
|
+
await self.check_and_start_or_stop_detection()
|
|
87
|
+
|
|
88
|
+
def schedule_check(self):
|
|
89
|
+
if self.debounce_task and not self.debounce_task.done():
|
|
90
|
+
return # Debounce task already scheduled
|
|
91
|
+
|
|
92
|
+
self.debounce_task = asyncio.create_task(self.debounce_check())
|
|
93
|
+
|
|
94
|
+
async def debounce_check(self):
|
|
95
|
+
await asyncio.sleep(0.1) # Debounce delay
|
|
96
|
+
await self.check_and_start_or_stop_detection()
|
|
97
|
+
|
|
98
|
+
def on_camera_connected(self, connected: bool) -> None:
|
|
99
|
+
self.connected = connected
|
|
100
|
+
self.schedule_check()
|
|
101
|
+
|
|
102
|
+
def on_has_motion_detector_changed(self, state: CameraPropertyObservableObject) -> None:
|
|
103
|
+
self.has_motion_detector: bool = state["new_state"]
|
|
104
|
+
self.schedule_check()
|
|
105
|
+
|
|
106
|
+
def on_extended_motion_detector_changed(self, state: CameraPropertyObservableObject) -> None:
|
|
107
|
+
self.extended_motion_detector: bool = state["new_state"]
|
|
108
|
+
self.schedule_check()
|
|
109
|
+
|
|
110
|
+
async def check_and_start_or_stop_detection(self):
|
|
111
|
+
if self.connected and self.has_motion_detector:
|
|
112
|
+
if not self.started:
|
|
113
|
+
await self.start()
|
|
114
|
+
else:
|
|
115
|
+
# Restart detection if the generator type has changed
|
|
116
|
+
if self.current_generator_type != self.extended_motion_detector:
|
|
117
|
+
self.close()
|
|
118
|
+
await self.start()
|
|
119
|
+
else:
|
|
120
|
+
if self.started:
|
|
121
|
+
self.close()
|
|
122
|
+
|
|
123
|
+
async def start(self):
|
|
46
124
|
if not self.started:
|
|
47
125
|
self.started = True
|
|
48
126
|
self.closed = False
|
|
49
127
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
self.
|
|
54
|
-
)
|
|
128
|
+
if self.extended_motion_detector:
|
|
129
|
+
self.camera_logger.log("Starting Object Detection with Motion Frames")
|
|
130
|
+
else:
|
|
131
|
+
self.camera_logger.log("Starting Object Detection")
|
|
55
132
|
|
|
56
|
-
|
|
57
|
-
self.frame_generation_task.add_done_callback(
|
|
58
|
-
lambda _: background_tasks.remove(self.frame_generation_task)
|
|
59
|
-
if self.frame_generation_task in background_tasks
|
|
60
|
-
else None
|
|
61
|
-
)
|
|
133
|
+
self.detection_task = asyncio.create_task(self.__start_detection())
|
|
62
134
|
|
|
63
135
|
def close(self):
|
|
64
|
-
if
|
|
136
|
+
if not self.closed:
|
|
65
137
|
self.started = False
|
|
66
138
|
self.closed = True
|
|
67
139
|
|
|
68
|
-
self.
|
|
140
|
+
self.camera_logger.log("Stopping Object Detection")
|
|
141
|
+
|
|
142
|
+
if self.detection_task:
|
|
143
|
+
self.detection_task.cancel()
|
|
144
|
+
self.detection_task = None
|
|
145
|
+
|
|
146
|
+
if self.debounce_task:
|
|
147
|
+
self.debounce_task.cancel()
|
|
148
|
+
self.debounce_task = None
|
|
69
149
|
|
|
70
|
-
|
|
71
|
-
self.frame_generation_task.cancel()
|
|
72
|
-
self.frame_generation_task = None
|
|
150
|
+
self.object_detector.close()
|
|
73
151
|
|
|
74
|
-
|
|
75
|
-
|
|
152
|
+
async def __start_detection(self) -> None:
|
|
153
|
+
try:
|
|
154
|
+
await self.__detect()
|
|
155
|
+
except asyncio.CancelledError:
|
|
156
|
+
pass
|
|
157
|
+
except Exception as error:
|
|
158
|
+
self.camera_logger.error("Error generating frames", error)
|
|
159
|
+
await self.__restart()
|
|
76
160
|
|
|
77
|
-
def
|
|
78
|
-
self.
|
|
161
|
+
async def __restart(self) -> None:
|
|
162
|
+
if self.restarting or self.closed:
|
|
163
|
+
return
|
|
79
164
|
|
|
80
|
-
self.
|
|
165
|
+
self.restarting = True
|
|
81
166
|
|
|
82
|
-
|
|
83
|
-
|
|
167
|
+
self.camera_logger.log("Restarting Object Detection in 2s...")
|
|
168
|
+
await asyncio.sleep(2)
|
|
169
|
+
|
|
170
|
+
self.restarting = False
|
|
171
|
+
|
|
172
|
+
if self.connected and not self.closed and self.has_motion_detector:
|
|
173
|
+
await self.__start_detection()
|
|
84
174
|
|
|
85
175
|
async def __detect(self):
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
"width": self.object_detector.input_width,
|
|
97
|
-
"height": self.object_detector.input_height,
|
|
98
|
-
},
|
|
99
|
-
}
|
|
100
|
-
)
|
|
101
|
-
|
|
102
|
-
detections: list[Detection] = []
|
|
103
|
-
|
|
104
|
-
class_ids, scores, boxes = await self.object_detector.detect(frame_image["image"])
|
|
105
|
-
|
|
106
|
-
for class_id, score, box in zip(class_ids, scores, boxes):
|
|
107
|
-
x1, y1, x2, y2 = box
|
|
108
|
-
|
|
109
|
-
detection: Detection = {
|
|
110
|
-
"id": str(uuid4()),
|
|
111
|
-
"label": cast(ObjectClass, self.object_detector.labels[class_id]),
|
|
112
|
-
"confidence": score,
|
|
113
|
-
"boundingBox": (float(x1), float(y1), float(x2), float(y2)),
|
|
114
|
-
"inputWidth": self.object_detector.input_width,
|
|
115
|
-
"inputHeight": self.object_detector.input_height,
|
|
116
|
-
"origWidth": frame.metadata["origWidth"],
|
|
117
|
-
"origHeight": frame.metadata["origHeight"],
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
detections.append(detection)
|
|
121
|
-
|
|
122
|
-
await self.camera.update_state("object", {"detections": detections})
|
|
123
|
-
except asyncio.CancelledError:
|
|
176
|
+
# Choose the generator based on extendedMotionDetector
|
|
177
|
+
if self.extended_motion_detector:
|
|
178
|
+
generator = self.camera.get_motion_frames()
|
|
179
|
+
else:
|
|
180
|
+
generator = self.camera.get_frames()
|
|
181
|
+
|
|
182
|
+
self.current_generator_type = self.extended_motion_detector
|
|
183
|
+
|
|
184
|
+
async for frame in generator:
|
|
185
|
+
if self.closed:
|
|
124
186
|
break
|
|
125
|
-
except Exception as error:
|
|
126
|
-
self.logger.error(self.camera.name, "Error generating motion frames", error)
|
|
127
|
-
self.logger.log(self.camera.name, "Restarting object detection in 5 seconds...")
|
|
128
|
-
await asyncio.sleep(5)
|
|
129
|
-
continue
|
|
130
187
|
|
|
131
|
-
|
|
132
|
-
|
|
188
|
+
# Check if properties have changed
|
|
189
|
+
if (
|
|
190
|
+
self.connected != self.camera.connected
|
|
191
|
+
or self.has_motion_detector != self.camera.has_motion_detector
|
|
192
|
+
or self.extended_motion_detector != self.camera.extended_motion_detector
|
|
193
|
+
):
|
|
194
|
+
await self.__restart()
|
|
195
|
+
break
|
|
133
196
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
197
|
+
frame_image = await frame.to_image(
|
|
198
|
+
{
|
|
199
|
+
"format": {"to": "rgb"},
|
|
200
|
+
"resize": {
|
|
201
|
+
"width": self.object_detector.input_width,
|
|
202
|
+
"height": self.object_detector.input_height,
|
|
203
|
+
},
|
|
204
|
+
}
|
|
205
|
+
)
|
|
139
206
|
|
|
140
|
-
|
|
141
|
-
camera_storage: Optional[CameraStorage[CameraStorageValues]] = (
|
|
142
|
-
self.api.storage_controller.get_camera_storage(self.camera.id)
|
|
143
|
-
)
|
|
207
|
+
detections: list[Detection] = []
|
|
144
208
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
"
|
|
154
|
-
"
|
|
155
|
-
"
|
|
156
|
-
"
|
|
157
|
-
"
|
|
158
|
-
"
|
|
159
|
-
"onGet": lambda: self.has_motion,
|
|
209
|
+
class_ids, scores, boxes = await self.object_detector.detect(frame_image["image"])
|
|
210
|
+
|
|
211
|
+
for class_id, score, box in zip(class_ids, scores, boxes):
|
|
212
|
+
x1, y1, x2, y2 = box
|
|
213
|
+
|
|
214
|
+
detection: Detection = {
|
|
215
|
+
"id": str(uuid4()),
|
|
216
|
+
"label": cast(ObjectClass, self.object_detector.labels[class_id]),
|
|
217
|
+
"confidence": score,
|
|
218
|
+
"boundingBox": (float(x1), float(y1), float(x2), float(y2)),
|
|
219
|
+
"inputWidth": self.object_detector.input_width,
|
|
220
|
+
"inputHeight": self.object_detector.input_height,
|
|
221
|
+
"origWidth": frame.metadata["origWidth"],
|
|
222
|
+
"origHeight": frame.metadata["origHeight"],
|
|
160
223
|
}
|
|
161
|
-
|
|
224
|
+
|
|
225
|
+
detections.append(detection)
|
|
226
|
+
|
|
227
|
+
await self.camera.update_state("object", {"detections": detections})
|
|
228
|
+
|
|
229
|
+
async def __default_settings(self) -> None:
|
|
230
|
+
if self.camera_storage.values["model"] != default_model:
|
|
231
|
+
await self.camera_storage.setValue("model", default_model)
|
|
232
|
+
|
|
233
|
+
async def __change_model(self, new_model: str, old_model: str) -> None:
|
|
234
|
+
if self.object_detector.downloading:
|
|
235
|
+
self.camera_logger.log("Model is currently downloading, please wait")
|
|
236
|
+
await self.camera_storage.setValue("model", old_model)
|
|
237
|
+
return
|
|
238
|
+
|
|
239
|
+
self.camera_logger.log(f"Model changed from {old_model} to {new_model}")
|
|
240
|
+
|
|
241
|
+
await self.object_detector.reset()
|
|
242
|
+
await self.object_detector.initialize(new_model)
|
|
243
|
+
|
|
244
|
+
def __create_camera_storage(self) -> CameraStorage[CameraStorageValues]:
|
|
245
|
+
camera_storage: CameraStorage[CameraStorageValues] = (
|
|
246
|
+
self.api.storage_controller.create_camera_storage(
|
|
247
|
+
self,
|
|
248
|
+
self.camera.id,
|
|
249
|
+
{
|
|
250
|
+
"model": {
|
|
251
|
+
"type": "string",
|
|
252
|
+
"key": "model",
|
|
253
|
+
"title": "Model",
|
|
254
|
+
"description": "Model for object detection",
|
|
255
|
+
"store": True,
|
|
256
|
+
"defaultValue": default_model,
|
|
257
|
+
"enum": available_models,
|
|
258
|
+
"required": True,
|
|
259
|
+
"onSet": lambda new, old: self.__change_model(new, old),
|
|
260
|
+
},
|
|
261
|
+
"reset": {
|
|
262
|
+
"type": "button",
|
|
263
|
+
"title": "Default Settings",
|
|
264
|
+
"key": "default",
|
|
265
|
+
"description": "Reset settings to default",
|
|
266
|
+
"onSet": lambda new, old: self.__default_settings(),
|
|
267
|
+
},
|
|
268
|
+
},
|
|
269
|
+
)
|
|
162
270
|
)
|
|
271
|
+
|
|
272
|
+
return camera_storage
|
package/src/detector_defaults.py
CHANGED
|
@@ -1,32 +1,86 @@
|
|
|
1
1
|
available_models = [
|
|
2
|
-
|
|
3
|
-
"yolo3-
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
"
|
|
7
|
-
|
|
8
|
-
"
|
|
9
|
-
"
|
|
10
|
-
"
|
|
11
|
-
"
|
|
12
|
-
"
|
|
13
|
-
"
|
|
14
|
-
"
|
|
15
|
-
"
|
|
16
|
-
|
|
17
|
-
"
|
|
18
|
-
"
|
|
19
|
-
"
|
|
20
|
-
"
|
|
21
|
-
"
|
|
22
|
-
"
|
|
23
|
-
"
|
|
24
|
-
"
|
|
25
|
-
"
|
|
26
|
-
"
|
|
27
|
-
"
|
|
28
|
-
"
|
|
29
|
-
|
|
2
|
+
# YOLOv3
|
|
3
|
+
"yolo3-tinyu - INT8",
|
|
4
|
+
"yolo3-tinyu_320 - INT8",
|
|
5
|
+
"yolo3-tinyu - FP16",
|
|
6
|
+
"yolo3-tinyu_320 - FP16",
|
|
7
|
+
# YOLOv5
|
|
8
|
+
"yolov5nu - INT8",
|
|
9
|
+
"yolov5nu_320 - INT8",
|
|
10
|
+
"yolov5mu - INT8",
|
|
11
|
+
"yolov5mu_320 - INT8",
|
|
12
|
+
"yolov5nu - FP16",
|
|
13
|
+
"yolov5nu_320 - FP16",
|
|
14
|
+
"yolov5mu - FP16",
|
|
15
|
+
"yolov5mu_320 - FP16",
|
|
16
|
+
# YOLOv8
|
|
17
|
+
"yolov8n - INT8",
|
|
18
|
+
"yolov8n_320 - INT8",
|
|
19
|
+
"yolov8s - INT8",
|
|
20
|
+
"yolov8s_320 - INT8",
|
|
21
|
+
"yolov8m - INT8",
|
|
22
|
+
"yolov8m_320 - INT8",
|
|
23
|
+
"yolov8n - FP16",
|
|
24
|
+
"yolov8n_320 - FP16",
|
|
25
|
+
"yolov8s - FP16",
|
|
26
|
+
"yolov8s_320 - FP16",
|
|
27
|
+
"yolov8m - FP16",
|
|
28
|
+
"yolov8m_320 - FP16",
|
|
29
|
+
# YOLOv9
|
|
30
|
+
"yolov9t - INT8",
|
|
31
|
+
"yolov9t_320 - INT8",
|
|
32
|
+
"yolov9s - INT8",
|
|
33
|
+
"yolov9s_320 - INT8",
|
|
34
|
+
"yolov9m - INT8",
|
|
35
|
+
"yolov9m_320 - INT8",
|
|
36
|
+
"yolov9c - INT8",
|
|
37
|
+
"yolov9c_320 - INT8",
|
|
38
|
+
"yolov9t - FP16",
|
|
39
|
+
"yolov9t_320 - FP16",
|
|
40
|
+
"yolov9s - FP16",
|
|
41
|
+
"yolov9s_320 - FP16",
|
|
42
|
+
"yolov9m - FP16",
|
|
43
|
+
"yolov9m_320 - FP16",
|
|
44
|
+
"yolov9c - FP16",
|
|
45
|
+
"yolov9c_320 - FP16",
|
|
46
|
+
# YOLOv10
|
|
47
|
+
"yolov10n - INT8",
|
|
48
|
+
"yolov10n_320 - INT8",
|
|
49
|
+
"yolov10s - INT8",
|
|
50
|
+
"yolov10s_320 - INT8",
|
|
51
|
+
"yolov10m - INT8",
|
|
52
|
+
"yolov10m_320 - INT8",
|
|
53
|
+
"yolov10l - INT8",
|
|
54
|
+
"yolov10l_320 - INT8",
|
|
55
|
+
"yolov10n - FP16",
|
|
56
|
+
"yolov10n_320 - FP16",
|
|
57
|
+
"yolov10s - FP16",
|
|
58
|
+
"yolov10s_320 - FP16",
|
|
59
|
+
"yolov10m - FP16",
|
|
60
|
+
"yolov10m_320 - FP16",
|
|
61
|
+
"yolov10l - FP16",
|
|
62
|
+
"yolov10l_320 - FP16",
|
|
63
|
+
# YOLOv11
|
|
64
|
+
"yolov11n - INT8",
|
|
65
|
+
"yolov11n_320 - INT8",
|
|
66
|
+
"yolov11s - INT8",
|
|
67
|
+
"yolov11s_320 - INT8",
|
|
68
|
+
"yolov11m - INT8",
|
|
69
|
+
"yolov11m_320 - INT8",
|
|
70
|
+
"yolov11l - INT8",
|
|
71
|
+
"yolov11l_320 - INT8",
|
|
72
|
+
"yolov11n - FP16",
|
|
73
|
+
"yolov11n_320 - FP16",
|
|
74
|
+
"yolov11s - FP16",
|
|
75
|
+
"yolov11s_320 - FP16",
|
|
76
|
+
"yolov11m - FP16",
|
|
77
|
+
"yolov11m_320 - FP16",
|
|
78
|
+
"yolov11l - FP16",
|
|
79
|
+
"yolov11l_320 - FP16",
|
|
30
80
|
]
|
|
31
81
|
|
|
32
82
|
available_precision = ["INT8", "FP16"]
|
|
83
|
+
|
|
84
|
+
default_model = "yolov9m_320 - FP16"
|
|
85
|
+
|
|
86
|
+
model_version = "v1"
|
package/src/main.py
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
# main.py
|
|
2
|
+
import asyncio
|
|
3
|
+
import os
|
|
1
4
|
from typing import Any, Union
|
|
2
5
|
|
|
3
|
-
from camera_detector import CameraDetector
|
|
4
6
|
from camera_ui_python_types import (
|
|
5
7
|
CameraDevice,
|
|
6
8
|
CameraExtension,
|
|
@@ -12,8 +14,11 @@ from camera_ui_python_types import (
|
|
|
12
14
|
PluginAPI,
|
|
13
15
|
RootSchema,
|
|
14
16
|
)
|
|
15
|
-
|
|
17
|
+
|
|
18
|
+
from camera_detector import CameraDetector
|
|
19
|
+
from detector_defaults import available_models, default_model, model_version
|
|
16
20
|
from object_detector import ObjectDetector
|
|
21
|
+
from plugin_typings import FormPayload
|
|
17
22
|
|
|
18
23
|
|
|
19
24
|
class CoreML(ObjectDetectionPlugin):
|
|
@@ -22,10 +27,6 @@ class CoreML(ObjectDetectionPlugin):
|
|
|
22
27
|
self.logger = logger
|
|
23
28
|
self.camera_devices: dict[str, CameraDevice] = {}
|
|
24
29
|
self.detectors: dict[str, CameraDetector] = {}
|
|
25
|
-
self.object_detector = ObjectDetector(self.api, self.logger)
|
|
26
|
-
self.single_image_detectors: dict[str, ObjectDetector] = {}
|
|
27
|
-
|
|
28
|
-
self.has_motion: list[str] = []
|
|
29
30
|
|
|
30
31
|
self.api.on("finishLaunching", self.start)
|
|
31
32
|
self.api.on("shutdown", self.stop)
|
|
@@ -33,45 +34,90 @@ class CoreML(ObjectDetectionPlugin):
|
|
|
33
34
|
self.api.device_manager.on("cameraSelected", self.camera_selected)
|
|
34
35
|
self.api.device_manager.on("cameraDeselected", self.camera_deselected)
|
|
35
36
|
|
|
37
|
+
self.object_detectors: dict[str, ObjectDetector] = {}
|
|
38
|
+
|
|
36
39
|
async def camera_selected(self, camera: CameraDevice, extension: CameraExtension) -> None:
|
|
37
|
-
self.logger.log(f"
|
|
40
|
+
self.logger.log(f"Camera added: {camera.name}")
|
|
38
41
|
self.camera_devices[camera.id] = camera
|
|
39
|
-
self.create_detector(camera)
|
|
42
|
+
await self.create_detector(camera)
|
|
40
43
|
|
|
41
|
-
def camera_deselected(self, id: str, extension: CameraExtension) -> None:
|
|
44
|
+
async def camera_deselected(self, id: str, extension: CameraExtension) -> None:
|
|
42
45
|
camera = self.camera_devices.get(id)
|
|
43
46
|
|
|
44
47
|
if camera:
|
|
45
|
-
self.logger.log(f"Camera removed
|
|
46
|
-
self.remove_detector(
|
|
48
|
+
self.logger.log(f"Camera removed: {camera.name}")
|
|
49
|
+
self.remove_detector(camera)
|
|
47
50
|
del self.camera_devices[id]
|
|
48
51
|
|
|
49
|
-
def start(self) -> None:
|
|
50
|
-
self.logger.log("Plugin
|
|
51
|
-
|
|
52
|
-
self.object_detector.initialize()
|
|
52
|
+
async def start(self) -> None:
|
|
53
|
+
self.logger.log("Plugin started")
|
|
54
|
+
await asyncio.gather(*[self.create_detector(camera) for camera in self.camera_devices.values()])
|
|
53
55
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
def stop(self) -> None:
|
|
58
|
-
self.logger.log("Plugin Stopped")
|
|
56
|
+
async def stop(self) -> None:
|
|
57
|
+
self.logger.log("Plugin stopped")
|
|
59
58
|
|
|
60
59
|
detectors_copy = list(self.detectors.values())
|
|
61
60
|
|
|
62
61
|
for detector in detectors_copy:
|
|
63
|
-
|
|
62
|
+
detector.close()
|
|
63
|
+
del self.detectors[detector.camera.id]
|
|
64
|
+
|
|
65
|
+
async def onFormSubmit(self, action_id: str, payload: FormPayload) -> Union[FormSubmitResponse, None]:
|
|
66
|
+
if action_id == "onRemove":
|
|
67
|
+
response: FormSubmitResponse = {
|
|
68
|
+
"schema": {
|
|
69
|
+
"config": {
|
|
70
|
+
"type": "object",
|
|
71
|
+
"title": "Confirm",
|
|
72
|
+
"required": True,
|
|
73
|
+
"opened": True,
|
|
74
|
+
"properties": {
|
|
75
|
+
"confirm": {
|
|
76
|
+
"type": "string",
|
|
77
|
+
"description": "Please type 'REMOVE' to confirm",
|
|
78
|
+
"required": True,
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
"buttons": [
|
|
82
|
+
{
|
|
83
|
+
"label": "Remove",
|
|
84
|
+
"onSubmit": "onConfirm",
|
|
85
|
+
},
|
|
86
|
+
],
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return response
|
|
92
|
+
elif action_id == "onConfirm":
|
|
93
|
+
if payload.get("confirm") == "REMOVE":
|
|
94
|
+
model, precision = payload["model"].split(" - ")
|
|
95
|
+
filename = f"coreml_{model}/{precision}"
|
|
96
|
+
|
|
97
|
+
model_storage = os.path.join(f"{self.api.storage_path}/models/{model_version}")
|
|
98
|
+
dir = os.path.join(model_storage, filename)
|
|
99
|
+
|
|
100
|
+
if os.path.exists(dir):
|
|
101
|
+
self.logger.attention(f"Removing model: {model} - {precision}")
|
|
102
|
+
os.rmdir(dir)
|
|
103
|
+
else:
|
|
104
|
+
self.logger.debug(f"Model not found: {model} - {precision}, skipping removal")
|
|
64
105
|
|
|
65
|
-
|
|
66
|
-
|
|
106
|
+
return {
|
|
107
|
+
"toast": {
|
|
108
|
+
"message": "All models removed!",
|
|
109
|
+
"type": "success",
|
|
110
|
+
}
|
|
111
|
+
}
|
|
67
112
|
|
|
68
113
|
def configureCameras(self, cameras: list[CameraDevice]) -> None:
|
|
69
114
|
for camera in cameras:
|
|
70
115
|
self.camera_devices[camera.id] = camera
|
|
71
116
|
|
|
72
|
-
def create_detector(self, camera: CameraDevice) -> None:
|
|
73
|
-
detector = CameraDetector(camera, self.
|
|
117
|
+
async def create_detector(self, camera: CameraDevice) -> None:
|
|
118
|
+
detector = CameraDetector(camera, self.api, self.logger)
|
|
74
119
|
self.detectors[camera.id] = detector
|
|
120
|
+
await detector.initialize()
|
|
75
121
|
|
|
76
122
|
def remove_detector(self, camera: CameraDevice) -> None:
|
|
77
123
|
detector = self.detectors[camera.id]
|
|
@@ -86,38 +132,30 @@ class CoreML(ObjectDetectionPlugin):
|
|
|
86
132
|
"models": {
|
|
87
133
|
"type": "string",
|
|
88
134
|
"title": "Model",
|
|
89
|
-
"description": "Model
|
|
135
|
+
"description": "Model for object detection",
|
|
90
136
|
"required": True,
|
|
91
|
-
"defaultValue":
|
|
137
|
+
"defaultValue": default_model,
|
|
92
138
|
"enum": available_models,
|
|
93
|
-
}
|
|
94
|
-
"precision": {
|
|
95
|
-
"type": "string",
|
|
96
|
-
"title": "Precision",
|
|
97
|
-
"description": "Precision of the model",
|
|
98
|
-
"required": True,
|
|
99
|
-
"defaultValue": "FP16",
|
|
100
|
-
"enum": available_precision,
|
|
101
|
-
},
|
|
139
|
+
}
|
|
102
140
|
}
|
|
103
141
|
}
|
|
104
142
|
|
|
105
143
|
async def detectObjects(
|
|
106
144
|
self, image_path: str, metadata: ImageMetadata, config: dict[str, Any]
|
|
107
145
|
) -> ObjectDetectionPluginResponse:
|
|
108
|
-
|
|
109
|
-
|
|
146
|
+
model_name: str = config.get("models", default_model)
|
|
147
|
+
|
|
148
|
+
object_detector = self.object_detectors.get(model_name)
|
|
110
149
|
|
|
111
|
-
|
|
112
|
-
|
|
150
|
+
if not object_detector:
|
|
151
|
+
object_detector = ObjectDetector(self.api, self.logger)
|
|
152
|
+
self.object_detectors[model_name] = object_detector
|
|
113
153
|
|
|
114
|
-
|
|
115
|
-
single_image_detector = ObjectDetector(self.api, self.logger)
|
|
116
|
-
self.single_image_detectors[identifier] = single_image_detector
|
|
154
|
+
await object_detector.initialize(model_name)
|
|
117
155
|
|
|
118
|
-
|
|
156
|
+
detections = await object_detector.detect_single(image_path, metadata)
|
|
119
157
|
|
|
120
|
-
|
|
158
|
+
print("Detections: ", len(detections))
|
|
121
159
|
|
|
122
160
|
return {
|
|
123
161
|
"detections": detections,
|
package/src/object_detector.py
CHANGED
|
@@ -1,15 +1,25 @@
|
|
|
1
|
+
# object_detector.py
|
|
1
2
|
import asyncio
|
|
2
3
|
import os
|
|
4
|
+
from asyncio import Lock
|
|
3
5
|
from concurrent.futures import ThreadPoolExecutor
|
|
4
6
|
from typing import Any, Optional, cast
|
|
5
7
|
from uuid import uuid4
|
|
6
8
|
|
|
9
|
+
import aiohttp
|
|
7
10
|
import coremltools as ct
|
|
8
11
|
import numpy as np
|
|
9
|
-
import
|
|
10
|
-
|
|
11
|
-
|
|
12
|
+
from camera_ui_python_types import (
|
|
13
|
+
CameraDevice,
|
|
14
|
+
Detection,
|
|
15
|
+
ImageMetadata,
|
|
16
|
+
LoggerService,
|
|
17
|
+
ObjectClass,
|
|
18
|
+
PluginAPI,
|
|
19
|
+
)
|
|
12
20
|
from PIL import Image
|
|
21
|
+
|
|
22
|
+
from detector_defaults import available_models, available_precision, default_model, model_version
|
|
13
23
|
from utils import parse_labels
|
|
14
24
|
|
|
15
25
|
|
|
@@ -18,12 +28,11 @@ class ObjectDetector:
|
|
|
18
28
|
self,
|
|
19
29
|
api: PluginAPI,
|
|
20
30
|
logger: LoggerService,
|
|
31
|
+
camera: Optional[CameraDevice] = None,
|
|
21
32
|
):
|
|
22
33
|
self.api = api
|
|
23
34
|
self.logger = logger
|
|
24
|
-
self.config = self.api.config_service.all()
|
|
25
35
|
|
|
26
|
-
model_version = "v1"
|
|
27
36
|
self.model_path = os.path.join(f"{self.api.storage_path}/models/{model_version}")
|
|
28
37
|
|
|
29
38
|
self.initialized = False
|
|
@@ -33,6 +42,15 @@ class ObjectDetector:
|
|
|
33
42
|
self.selected_model = None
|
|
34
43
|
self.selected_precision = None
|
|
35
44
|
|
|
45
|
+
self.closed = False
|
|
46
|
+
self.__downloading = False
|
|
47
|
+
|
|
48
|
+
self.lock = Lock()
|
|
49
|
+
|
|
50
|
+
@property
|
|
51
|
+
def downloading(self) -> bool:
|
|
52
|
+
return self.__downloading
|
|
53
|
+
|
|
36
54
|
@property
|
|
37
55
|
def input_width(self) -> int:
|
|
38
56
|
return self.input_size[0]
|
|
@@ -41,52 +59,82 @@ class ObjectDetector:
|
|
|
41
59
|
def input_height(self) -> int:
|
|
42
60
|
return self.input_size[1]
|
|
43
61
|
|
|
44
|
-
def initialize(self,
|
|
45
|
-
|
|
46
|
-
self.
|
|
47
|
-
|
|
48
|
-
self.logger.log("Initializing CoreML Object Detector...")
|
|
62
|
+
async def initialize(self, model_name: str) -> None:
|
|
63
|
+
async with self.lock:
|
|
64
|
+
if self.__downloading:
|
|
65
|
+
raise Exception("Already downloading a model")
|
|
49
66
|
|
|
50
|
-
|
|
51
|
-
|
|
67
|
+
if not self.initialized:
|
|
68
|
+
self.initialized = True
|
|
69
|
+
self.__downloading = True
|
|
52
70
|
|
|
53
|
-
|
|
54
|
-
self.selected_model = "yolov9m_320"
|
|
71
|
+
self.selected_model, self.selected_precision = model_name.split(" - ")
|
|
55
72
|
|
|
56
|
-
|
|
57
|
-
|
|
73
|
+
if self.selected_model not in available_models:
|
|
74
|
+
self.selected_model = default_model.split(" - ")[0]
|
|
58
75
|
|
|
59
|
-
|
|
60
|
-
|
|
76
|
+
if self.selected_precision not in available_precision:
|
|
77
|
+
self.selected_precision = default_model.split(" - ")[1]
|
|
61
78
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
]
|
|
79
|
+
yolo_model_name = self.selected_model.split("_")[0]
|
|
80
|
+
ml_package = (
|
|
81
|
+
f"coreml_{self.selected_model}/{self.selected_precision}/{yolo_model_name}.mlpackage"
|
|
82
|
+
)
|
|
67
83
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
f"
|
|
84
|
+
files = [
|
|
85
|
+
f"{ml_package}/Data/com.apple.CoreML/weights/weight.bin",
|
|
86
|
+
f"{ml_package}/Data/com.apple.CoreML/model.mlmodel",
|
|
87
|
+
f"{ml_package}/Manifest.json",
|
|
88
|
+
]
|
|
89
|
+
|
|
90
|
+
for f in files:
|
|
91
|
+
url = f"https://raw.githubusercontent.com/seydx/models/main/models/coreml/{f}"
|
|
92
|
+
await self.__downloadFile(url, f)
|
|
93
|
+
if self.closed:
|
|
94
|
+
return
|
|
95
|
+
|
|
96
|
+
# Proceed with model loading and initialization
|
|
97
|
+
loop = asyncio.get_event_loop()
|
|
98
|
+
self.model = await loop.run_in_executor(
|
|
99
|
+
None, ct.models.MLModel, os.path.join(self.model_path, ml_package)
|
|
71
100
|
)
|
|
72
|
-
|
|
101
|
+
self.input_name: str = self.model.get_spec().description.input[0].name # type: ignore
|
|
73
102
|
|
|
74
|
-
|
|
75
|
-
|
|
103
|
+
modelspec: Any = self.model.get_spec()
|
|
104
|
+
inputdesc = modelspec.description.input[0] # type: ignore
|
|
105
|
+
inputheight: Any = inputdesc.type.imageType.height
|
|
106
|
+
inputwidth: Any = inputdesc.type.imageType.width
|
|
76
107
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
inputheight: Any = inputdesc.type.imageType.height
|
|
80
|
-
inputwidth: Any = inputdesc.type.imageType.width
|
|
108
|
+
self.input_size: tuple[int, int] = (inputwidth, inputheight)
|
|
109
|
+
self.labels = parse_labels(modelspec.description.metadata.userDefined) # type: ignore
|
|
81
110
|
|
|
82
|
-
|
|
83
|
-
self.labels = parse_labels(modelspec.description.metadata.userDefined) # type: ignore
|
|
111
|
+
self.__downloading = False
|
|
84
112
|
|
|
85
|
-
|
|
113
|
+
async def reset(self) -> None:
|
|
114
|
+
async with self.lock:
|
|
115
|
+
self.initialized = False
|
|
116
|
+
if self.executor:
|
|
117
|
+
self.executor.shutdown(wait=False)
|
|
118
|
+
self.executor = ThreadPoolExecutor(max_workers=8)
|
|
119
|
+
|
|
120
|
+
def close(self) -> None:
|
|
121
|
+
self.closed = True
|
|
122
|
+
asyncio.create_task(self.reset())
|
|
123
|
+
if self.executor:
|
|
124
|
+
self.executor.shutdown(wait=False)
|
|
125
|
+
self.executor = None
|
|
126
|
+
|
|
127
|
+
def cleanup_model(self, model_name: str) -> None:
|
|
128
|
+
model, precision = model_name.split(" - ")
|
|
129
|
+
filename = f"coreml_{model}/{precision}"
|
|
130
|
+
dir = os.path.join(self.model_path, filename)
|
|
131
|
+
|
|
132
|
+
if os.path.exists(dir):
|
|
133
|
+
os.rmdir(dir)
|
|
86
134
|
|
|
87
135
|
async def detect(self, image: Image.Image) -> tuple[list[int], list[float], np.ndarray[Any, Any]]:
|
|
88
136
|
if not self.initialized:
|
|
89
|
-
|
|
137
|
+
return [], [], np.array([])
|
|
90
138
|
|
|
91
139
|
out_dict = await asyncio.get_event_loop().run_in_executor(self.executor, self.__model_predict, image)
|
|
92
140
|
|
|
@@ -115,7 +163,6 @@ class ObjectDetector:
|
|
|
115
163
|
ymax = np.minimum(self.input_height, y_center + height / 2)
|
|
116
164
|
|
|
117
165
|
boxes = np.vstack([xmin, ymin, xmax, ymax]).T
|
|
118
|
-
# dets = np.hstack([boxes, scores.reshape(-1, 1), class_ids.reshape(-1, 1)])
|
|
119
166
|
|
|
120
167
|
return class_ids.tolist(), scores.tolist(), boxes
|
|
121
168
|
|
|
@@ -153,38 +200,29 @@ class ObjectDetector:
|
|
|
153
200
|
def __model_predict(self, image: Image.Image) -> Any:
|
|
154
201
|
return self.model.predict({self.input_name: image}) # type: ignore
|
|
155
202
|
|
|
156
|
-
def __downloadFile(self, url: str, filename: str) ->
|
|
157
|
-
|
|
158
|
-
fullpath = os.path.join(self.model_path, filename)
|
|
203
|
+
async def __downloadFile(self, url: str, filename: str) -> None:
|
|
204
|
+
fullpath = os.path.join(self.model_path, filename)
|
|
159
205
|
|
|
160
|
-
|
|
161
|
-
|
|
206
|
+
if os.path.isfile(fullpath):
|
|
207
|
+
return
|
|
162
208
|
|
|
163
|
-
|
|
209
|
+
tmp = fullpath + ".tmp"
|
|
164
210
|
|
|
165
|
-
|
|
211
|
+
os.makedirs(os.path.dirname(fullpath), exist_ok=True)
|
|
166
212
|
|
|
167
|
-
|
|
213
|
+
self.logger.debug("Downloading", url)
|
|
168
214
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
if response.status_code < 200 or response.status_code >= 300:
|
|
174
|
-
raise Exception(f"Error downloading {url}: {response.status_code}")
|
|
215
|
+
async with aiohttp.ClientSession() as session, session.get(url) as response:
|
|
216
|
+
if response.status < 200 or response.status >= 300:
|
|
217
|
+
raise Exception(f"Error downloading {url}: {response.status}")
|
|
175
218
|
|
|
176
219
|
read = 0
|
|
177
220
|
|
|
178
221
|
with open(tmp, "wb") as f:
|
|
179
|
-
for chunk in response.
|
|
222
|
+
async for chunk in response.content.iter_chunked(1024 * 1024):
|
|
180
223
|
if chunk:
|
|
181
224
|
read += len(chunk)
|
|
182
|
-
self.logger.debug("Downloaded", read, "
|
|
225
|
+
self.logger.debug("Downloaded", read, "Bytes")
|
|
183
226
|
f.write(chunk)
|
|
184
227
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
return fullpath
|
|
188
|
-
except Exception as e:
|
|
189
|
-
self.logger.error("Error downloading", url, e)
|
|
190
|
-
raise
|
|
228
|
+
os.rename(tmp, fullpath)
|
package/src/plugin_typings.py
CHANGED