@camera.ui/camera-ui-coreml 0.0.21 → 0.0.23

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/bundle.zip ADDED
Binary file
package/package.json CHANGED
@@ -1,28 +1,29 @@
1
1
  {
2
2
  "displayName": "CoreML",
3
3
  "name": "@camera.ui/camera-ui-coreml",
4
- "version": "0.0.21",
4
+ "version": "0.0.23",
5
5
  "description": "camera.ui coreml plugin",
6
6
  "author": "seydx (https://github.com/seydx/camera.ui)",
7
- "main": "./src/main.py",
8
- "type": "module",
7
+ "main": "./dist/main.py",
8
+ "type": "commonjs",
9
9
  "scripts": {
10
+ "bundle": "npm run format && npm run lint && cui bundle",
10
11
  "format": "ruff format",
11
12
  "install-updates": "npm i --save",
12
- "lint": "ruff check --fix",
13
- "update": "updates --update ./",
14
- "prepublishOnly": "npm i --package-lock-only && npm run format && npm run lint"
15
- },
16
- "dependencies": {},
17
- "devDependencies": {
18
- "updates": "^16.4.0"
13
+ "lint": "ruff check",
14
+ "lint:fix": "ruff check --fix",
15
+ "prepublishOnly": "node -e \"if(!process.env.SAFE_PUBLISH){console.error('Error: Please use @camera.ui/cli to publish the plugin:\\n npm run publish:alpha\\n npm run publish:beta\\n npm run publish:latest\\n');process.exit(1)}\"",
16
+ "publish:alpha": "cui publish --alpha",
17
+ "publish:beta": "cui publish --beta",
18
+ "publish:latest": "cui publish --latest",
19
+ "update": "updates --update ./"
19
20
  },
20
21
  "bugs": {
21
22
  "url": "https://github.com/seydx/camera.ui/issues"
22
23
  },
23
24
  "engines": {
24
- "camera.ui": ">=0.0.34-alpha.1",
25
- "node": ">=20.17.0"
25
+ "camera.ui": ">=0.0.34-alpha.2",
26
+ "node": ">=20.18.0"
26
27
  },
27
28
  "homepage": "https://github.com/seydx/camera.ui#readme",
28
29
  "keywords": [
@@ -40,6 +41,7 @@
40
41
  "camera.ui": {
41
42
  "pythonVersion": "3.11",
42
43
  "extension": "objectDetection",
43
- "dependencies": []
44
+ "dependencies": [],
45
+ "bundled": true
44
46
  }
45
- }
47
+ }
package/CHANGELOG.md DELETED
@@ -1,8 +0,0 @@
1
- All notable changes to this project will be documented in this file.
2
-
3
- The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
4
- and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
5
-
6
- ## [X.X.X] - ???
7
-
8
- - Initial Release
package/CONTRIBUTING.md DELETED
@@ -1 +0,0 @@
1
- # Contributing
package/LICENSE.md DELETED
@@ -1,22 +0,0 @@
1
- MIT License
2
-
3
- Copyright (c) 2023-2024 seydx <dev@seydx.com>
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining
6
- a copy of this software and associated documentation files (the
7
- "Software"), to deal in the Software without restriction, including
8
- without limitation the rights to use, copy, modify, merge, publish,
9
- distribute, sublicense, and/or sell copies of the Software, and to
10
- permit persons to whom the Software is furnished to do so, subject to
11
- the following conditions:
12
-
13
- The above copyright notice and this permission notice shall be
14
- included in all copies or substantial portions of the Software.
15
-
16
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
- EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
- NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
- LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
- OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
- WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/README.md DELETED
@@ -1 +0,0 @@
1
- # @camera.ui/camera-ui-coreml
@@ -1,43 +0,0 @@
1
- {
2
- "schema": {
3
- "model": {
4
- "type": "string",
5
- "title": "Model",
6
- "description": "Model to use for object detection",
7
- "required": true,
8
- "store": true,
9
- "defaultValue": "yolov9m_320",
10
- "enum": [
11
- "yolo3-tinyu",
12
- "yolo3-tinyu_320",
13
- "yolov5nu",
14
- "yolov5nu_320",
15
- "yolov5mu",
16
- "yolov5mu_320",
17
- "yolov8n",
18
- "yolov8n_320",
19
- "yolov8s",
20
- "yolov8s_320",
21
- "yolov8m",
22
- "yolov8m_320",
23
- "yolov9t",
24
- "yolov9t_320",
25
- "yolov9s",
26
- "yolov9s_320",
27
- "yolov9m",
28
- "yolov9m_320",
29
- "yolov9c",
30
- "yolov9c_320"
31
- ]
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
- }
42
- }
43
- }
package/requirements.txt DELETED
@@ -1,4 +0,0 @@
1
- coremltools==7.2
2
- camera-ui-python-types==0.1.93
3
- requests==2.32.3
4
- Pillow==10.4.0
@@ -1,162 +0,0 @@
1
- import asyncio
2
- from typing import Any, Optional, cast
3
- from uuid import uuid4
4
-
5
- from camera_ui_python_types import (
6
- CameraDevice,
7
- CameraStorage,
8
- Detection,
9
- LoggerService,
10
- MotionState,
11
- ObjectClass,
12
- PluginAPI,
13
- )
14
- from object_detector import ObjectDetector
15
- from plugin_typings import CameraStorageValues
16
-
17
- background_tasks = set[asyncio.Task[Any]]()
18
-
19
-
20
- class CameraDetector:
21
- def __init__(
22
- self,
23
- camera: CameraDevice,
24
- object_detector: ObjectDetector,
25
- api: PluginAPI,
26
- logger: LoggerService,
27
- has_motion: list[str],
28
- ) -> None:
29
- super().__init__()
30
-
31
- self.camera = camera
32
- self.api = api
33
- self.logger = logger
34
- self.object_detector = object_detector
35
-
36
- self.has_motion = has_motion
37
- self.started = False
38
- self.closed = False
39
- self.frame_generation_task: Optional[asyncio.Task[Any]] = None
40
-
41
- 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
-
45
- def start(self):
46
- if not self.started:
47
- self.started = True
48
- self.closed = False
49
-
50
- self.logger.log(f"Starting object detection for camera {self.camera.name}...")
51
-
52
- self.frame_generation_task = asyncio.create_task(
53
- self.__detect(), name=f"ObjectDetection-{self.camera.name}"
54
- )
55
-
56
- background_tasks.add(self.frame_generation_task)
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
- )
62
-
63
- def close(self):
64
- if self.started and not self.closed:
65
- self.started = False
66
- self.closed = True
67
-
68
- self.logger.log(f"Stopping object detection for camera {self.camera.name}...")
69
-
70
- if self.frame_generation_task is not None:
71
- self.frame_generation_task.cancel()
72
- self.frame_generation_task = None
73
-
74
- if self.camera.name in self.has_motion:
75
- self.has_motion.remove(self.camera.name)
76
-
77
- def restart(self):
78
- self.logger.log(f"Restarting object detection for camera {self.camera.name}...")
79
-
80
- self.close()
81
-
82
- if self.camera.connected:
83
- self.start()
84
-
85
- async def __detect(self):
86
- while not self.closed:
87
- try:
88
- async for frame in self.camera.get_motion_frames():
89
- if self.closed:
90
- break
91
-
92
- frame_image = await frame.to_image(
93
- {
94
- "format": {"to": "rgb"},
95
- "resize": {
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:
124
- 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
-
131
- def __on_motion_detected(self, state: MotionState) -> None:
132
- motion_detected = bool(state.get("state", False) or len(state["detections"]) > 0)
133
-
134
- if motion_detected:
135
- if self.camera.name not in self.has_motion:
136
- self.has_motion.append(self.camera.name)
137
- elif self.camera.name in self.has_motion:
138
- self.has_motion.remove(self.camera.name)
139
-
140
- def __create_camera_storage(self) -> CameraStorage[CameraStorageValues]:
141
- camera_storage: Optional[CameraStorage[CameraStorageValues]] = (
142
- self.api.storage_controller.get_camera_storage(self.camera.id)
143
- )
144
-
145
- if camera_storage is not None:
146
- return camera_storage
147
-
148
- return self.api.storage_controller.create_camera_storage(
149
- self,
150
- self.camera.id,
151
- {
152
- "active_object_detections": {
153
- "type": "string",
154
- "key": "active_object_detections",
155
- "title": "Active Object Detections",
156
- "description": "Active object detections",
157
- "readonly": True,
158
- "enum": [],
159
- "onGet": lambda: self.has_motion,
160
- }
161
- },
162
- )
@@ -1,32 +0,0 @@
1
- available_models = [
2
- "yolo3-tinyu",
3
- "yolo3-tinyu_320",
4
- "yolov5nu",
5
- "yolov5nu_320",
6
- "yolov5mu",
7
- "yolov5mu_320",
8
- "yolov8n",
9
- "yolov8n_320",
10
- "yolov8s",
11
- "yolov8s_320",
12
- "yolov8m",
13
- "yolov8m_320",
14
- "yolov9t",
15
- "yolov9t_320",
16
- "yolov9s",
17
- "yolov9s_320",
18
- "yolov9m",
19
- "yolov9m_320",
20
- "yolov9c",
21
- "yolov9c_320",
22
- "yolov10n",
23
- "yolov10n_320",
24
- "yolov10s",
25
- "yolov10s_320",
26
- "yolov10m",
27
- "yolov10m_320",
28
- "yolov10l",
29
- "yolov10l_320",
30
- ]
31
-
32
- available_precision = ["INT8", "FP16"]
package/src/main.py DELETED
@@ -1,128 +0,0 @@
1
- from typing import Any, Union
2
-
3
- from camera_detector import CameraDetector
4
- from camera_ui_python_types import (
5
- CameraDevice,
6
- CameraExtension,
7
- FormSubmitResponse,
8
- ImageMetadata,
9
- LoggerService,
10
- ObjectDetectionPlugin,
11
- ObjectDetectionPluginResponse,
12
- PluginAPI,
13
- RootSchema,
14
- )
15
- from detector_defaults import available_models, available_precision
16
- from object_detector import ObjectDetector
17
-
18
-
19
- class CoreML(ObjectDetectionPlugin):
20
- def __init__(self, logger: LoggerService, api: PluginAPI):
21
- self.api = api
22
- self.logger = logger
23
- self.camera_devices: dict[str, CameraDevice] = {}
24
- 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
- self.api.on("finishLaunching", self.start)
31
- self.api.on("shutdown", self.stop)
32
-
33
- self.api.device_manager.on("cameraSelected", self.camera_selected)
34
- self.api.device_manager.on("cameraDeselected", self.camera_deselected)
35
-
36
- async def camera_selected(self, camera: CameraDevice, extension: CameraExtension) -> None:
37
- self.logger.log(f"New camera added ({extension}): {camera.name}")
38
- self.camera_devices[camera.id] = camera
39
- self.create_detector(camera)
40
-
41
- def camera_deselected(self, id: str, extension: CameraExtension) -> None:
42
- camera = self.camera_devices.get(id)
43
-
44
- if camera:
45
- self.logger.log(f"Camera removed ({extension}): {camera.name}")
46
- self.remove_detector(self.camera_devices[id])
47
- del self.camera_devices[id]
48
-
49
- def start(self) -> None:
50
- self.logger.log("Plugin Started")
51
-
52
- self.object_detector.initialize()
53
-
54
- for camera_device in self.camera_devices.values():
55
- self.create_detector(camera_device)
56
-
57
- def stop(self) -> None:
58
- self.logger.log("Plugin Stopped")
59
-
60
- detectors_copy = list(self.detectors.values())
61
-
62
- for detector in detectors_copy:
63
- self.remove_detector(detector.camera)
64
-
65
- async def onFormSubmit(self, action_id: str, payload: Any) -> Union[FormSubmitResponse, None]:
66
- print("Form submitted", action_id, payload)
67
-
68
- def configureCameras(self, cameras: list[CameraDevice]) -> None:
69
- for camera in cameras:
70
- self.camera_devices[camera.id] = camera
71
-
72
- def create_detector(self, camera: CameraDevice) -> None:
73
- detector = CameraDetector(camera, self.object_detector, self.api, self.logger, self.has_motion)
74
- self.detectors[camera.id] = detector
75
-
76
- def remove_detector(self, camera: CameraDevice) -> None:
77
- detector = self.detectors[camera.id]
78
-
79
- if detector:
80
- detector.close()
81
- del self.detectors[camera.id]
82
-
83
- def interfaceSchema(self) -> RootSchema | None:
84
- return {
85
- "schema": {
86
- "models": {
87
- "type": "string",
88
- "title": "Model",
89
- "description": "Model to use for object detection",
90
- "required": True,
91
- "defaultValue": "yolov9m_320",
92
- "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
- },
102
- }
103
- }
104
-
105
- async def detectObjects(
106
- self, image_path: str, metadata: ImageMetadata, config: dict[str, Any]
107
- ) -> ObjectDetectionPluginResponse:
108
- model = config.get("models")
109
- precision = config.get("precision")
110
-
111
- identifier = f"{model}_{precision}"
112
- single_image_detector = self.single_image_detectors.get(identifier)
113
-
114
- if not single_image_detector:
115
- single_image_detector = ObjectDetector(self.api, self.logger)
116
- self.single_image_detectors[identifier] = single_image_detector
117
-
118
- single_image_detector.initialize(model, precision)
119
-
120
- detections = await single_image_detector.detect_single(image_path, metadata)
121
-
122
- return {
123
- "detections": detections,
124
- }
125
-
126
-
127
- def __main__():
128
- return CoreML
@@ -1,190 +0,0 @@
1
- import asyncio
2
- import os
3
- from concurrent.futures import ThreadPoolExecutor
4
- from typing import Any, Optional, cast
5
- from uuid import uuid4
6
-
7
- import coremltools as ct
8
- import numpy as np
9
- import requests
10
- from camera_ui_python_types import Detection, ImageMetadata, LoggerService, ObjectClass, PluginAPI
11
- from detector_defaults import available_models, available_precision
12
- from PIL import Image
13
- from utils import parse_labels
14
-
15
-
16
- class ObjectDetector:
17
- def __init__(
18
- self,
19
- api: PluginAPI,
20
- logger: LoggerService,
21
- ):
22
- self.api = api
23
- self.logger = logger
24
- self.config = self.api.config_service.all()
25
-
26
- model_version = "v1"
27
- self.model_path = os.path.join(f"{self.api.storage_path}/models/{model_version}")
28
-
29
- self.initialized = False
30
- self.minThreshold = 0.2
31
- self.executor = ThreadPoolExecutor(max_workers=8)
32
-
33
- self.selected_model = None
34
- self.selected_precision = None
35
-
36
- @property
37
- def input_width(self) -> int:
38
- return self.input_size[0]
39
-
40
- @property
41
- def input_height(self) -> int:
42
- return self.input_size[1]
43
-
44
- def initialize(self, model: Optional[str] = None, precision: Optional[str] = None) -> None:
45
- if not self.initialized:
46
- self.initialized = True
47
-
48
- self.logger.log("Initializing CoreML Object Detector...")
49
-
50
- self.selected_model = model if model else self.config.get("model", "yolov9m_320")
51
- self.selected_precision = precision if precision else self.config.get("precision", "FP16")
52
-
53
- if self.selected_model not in available_models:
54
- self.selected_model = "yolov9m_320"
55
-
56
- if self.selected_precision not in available_precision:
57
- self.selected_precision = "FP16"
58
-
59
- yolo_model_name = self.selected_model.split("_")[0]
60
- model_file = None
61
-
62
- files = [
63
- f"coreml_{self.selected_model}/{self.selected_precision}/{yolo_model_name}.mlpackage/Data/com.apple.CoreML/weights/weight.bin",
64
- f"coreml_{self.selected_model}/{self.selected_precision}/{yolo_model_name}.mlpackage/Data/com.apple.CoreML/model.mlmodel",
65
- f"coreml_{self.selected_model}/{self.selected_precision}/{yolo_model_name}.mlpackage/Manifest.json",
66
- ]
67
-
68
- for f in files:
69
- p = self.__downloadFile(
70
- f"https://raw.githubusercontent.com/seydx/models/main/models/coreml/{f}", f
71
- )
72
- model_file = os.path.dirname(p)
73
-
74
- self.model = ct.models.MLModel(model=model_file, compute_units=ct.ComputeUnit.ALL)
75
- self.input_name: str = self.model.get_spec().description.input[0].name # type: ignore
76
-
77
- modelspec: Any = self.model.get_spec()
78
- inputdesc = modelspec.description.input[0] # type: ignore
79
- inputheight: Any = inputdesc.type.imageType.height
80
- inputwidth: Any = inputdesc.type.imageType.width
81
-
82
- self.input_size: tuple[int, int] = (inputwidth, inputheight)
83
- self.labels = parse_labels(modelspec.description.metadata.userDefined) # type: ignore
84
-
85
- self.logger.log("CoreML Object Detector initialized")
86
-
87
- async def detect(self, image: Image.Image) -> tuple[list[int], list[float], np.ndarray[Any, Any]]:
88
- if not self.initialized:
89
- raise Exception("CoreML Object Detector not initialized")
90
-
91
- out_dict = await asyncio.get_event_loop().run_in_executor(self.executor, self.__model_predict, image)
92
-
93
- coordinatesList = out_dict["coordinates"]
94
- confidenceList = out_dict["confidence"]
95
-
96
- indices = np.argmax(confidenceList, axis=1)
97
- maxConfidences = confidenceList[np.arange(len(confidenceList)), indices]
98
- valid = maxConfidences >= self.minThreshold
99
-
100
- if not np.any(valid):
101
- return [], [], np.array([])
102
-
103
- coordinates = coordinatesList[valid]
104
- class_ids = indices[valid]
105
- scores = maxConfidences[valid]
106
-
107
- x_center = self.__torelative(coordinates[:, 0], self.input_width)
108
- y_center = self.__torelative(coordinates[:, 1], self.input_height)
109
- width = self.__torelative(coordinates[:, 2], self.input_width)
110
- height = self.__torelative(coordinates[:, 3], self.input_height)
111
-
112
- xmin = np.maximum(0, x_center - width / 2)
113
- ymin = np.maximum(0, y_center - height / 2)
114
- xmax = np.minimum(self.input_width, x_center + width / 2)
115
- ymax = np.minimum(self.input_height, y_center + height / 2)
116
-
117
- boxes = np.vstack([xmin, ymin, xmax, ymax]).T
118
- # dets = np.hstack([boxes, scores.reshape(-1, 1), class_ids.reshape(-1, 1)])
119
-
120
- return class_ids.tolist(), scores.tolist(), boxes
121
-
122
- async def detect_single(self, image_path: str, metadata: ImageMetadata) -> list[Detection]:
123
- image = Image.open(image_path)
124
- image = image.convert("RGB").resize((self.input_width, self.input_height))
125
-
126
- detections: list[Detection] = []
127
-
128
- class_ids, scores, boxes = await self.detect(image)
129
-
130
- for class_id, score, box in zip(class_ids, scores, boxes):
131
- x1, y1, x2, y2 = box
132
-
133
- detection: Detection = {
134
- "id": str(uuid4()),
135
- "label": cast(ObjectClass, self.labels[class_id]),
136
- "confidence": score,
137
- "boundingBox": (float(x1), float(y1), float(x2), float(y2)),
138
- "inputWidth": self.input_width,
139
- "inputHeight": self.input_height,
140
- "origWidth": metadata["width"],
141
- "origHeight": metadata["height"],
142
- }
143
-
144
- detections.append(detection)
145
-
146
- image.close()
147
-
148
- return detections
149
-
150
- def __torelative(self, values: int, size: int) -> int:
151
- return values * size
152
-
153
- def __model_predict(self, image: Image.Image) -> Any:
154
- return self.model.predict({self.input_name: image}) # type: ignore
155
-
156
- def __downloadFile(self, url: str, filename: str) -> str:
157
- try:
158
- fullpath = os.path.join(self.model_path, filename)
159
-
160
- if os.path.isfile(fullpath):
161
- return fullpath
162
-
163
- tmp = fullpath + ".tmp"
164
-
165
- self.logger.debug("Creating directory for", tmp)
166
-
167
- os.makedirs(os.path.dirname(fullpath), exist_ok=True)
168
-
169
- self.logger.debug("Downloading", url)
170
-
171
- response = requests.get(url, stream=True)
172
-
173
- if response.status_code < 200 or response.status_code >= 300:
174
- raise Exception(f"Error downloading {url}: {response.status_code}")
175
-
176
- read = 0
177
-
178
- with open(tmp, "wb") as f:
179
- for chunk in response.iter_content(chunk_size=1024 * 1024):
180
- if chunk:
181
- read += len(chunk)
182
- self.logger.debug("Downloaded", read, "bytes")
183
- f.write(chunk)
184
-
185
- os.rename(tmp, fullpath)
186
-
187
- return fullpath
188
- except Exception as e:
189
- self.logger.error("Error downloading", url, e)
190
- raise
@@ -1,5 +0,0 @@
1
- from typing import TypedDict
2
-
3
-
4
- class CameraStorageValues(TypedDict):
5
- active_object_detections: list[str]
package/src/utils.py DELETED
@@ -1,44 +0,0 @@
1
- import ast
2
- import re
3
- from typing import Any
4
-
5
-
6
- def parse_label_contents(contents: str) -> dict[int, Any]:
7
- lines = contents.split(",")
8
- ret: dict[int, Any] = {}
9
-
10
- for row_number, content in enumerate(lines):
11
- pair = re.split(r"[:\s]+", content.strip(), maxsplit=1)
12
- if len(pair) == 2 and pair[0].strip().isdigit():
13
- ret[int(pair[0])] = pair[1].strip()
14
- else:
15
- ret[row_number] = content.strip()
16
-
17
- return ret
18
-
19
-
20
- def parse_labels(userDefined: dict[str, Any]) -> list[str]:
21
- yolo = userDefined.get("names") or userDefined.get("yolo.names")
22
-
23
- if yolo:
24
- j = ast.literal_eval(yolo)
25
- ret = []
26
- for _, v in j.items():
27
- ret.append(v)
28
-
29
- return ret
30
-
31
- classes = userDefined.get("classes")
32
-
33
- # print("classes", classes)
34
-
35
- if not classes:
36
- raise Exception("no classes found in model metadata")
37
-
38
- cont = parse_label_contents(classes)
39
- ret: list[str] = []
40
-
41
- for _, v in cont.items():
42
- ret.append(v)
43
-
44
- return ret