camera-ui-ml 1.2.2__tar.gz → 1.2.4__tar.gz
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.
- {camera_ui_ml-1.2.2 → camera_ui_ml-1.2.4}/PKG-INFO +1 -1
- {camera_ui_ml-1.2.2 → camera_ui_ml-1.2.4}/camera_ui_ml/detectors/base.py +2 -0
- {camera_ui_ml-1.2.2 → camera_ui_ml-1.2.4}/camera_ui_ml/detectors/clip.py +8 -19
- {camera_ui_ml-1.2.2 → camera_ui_ml-1.2.4}/camera_ui_ml/model_manager.py +11 -9
- {camera_ui_ml-1.2.2 → camera_ui_ml-1.2.4}/camera_ui_ml/pipelines.py +3 -1
- {camera_ui_ml-1.2.2 → camera_ui_ml-1.2.4}/camera_ui_ml.egg-info/PKG-INFO +1 -1
- {camera_ui_ml-1.2.2 → camera_ui_ml-1.2.4}/pyproject.toml +1 -1
- {camera_ui_ml-1.2.2 → camera_ui_ml-1.2.4}/LICENSE.md +0 -0
- {camera_ui_ml-1.2.2 → camera_ui_ml-1.2.4}/README.md +0 -0
- {camera_ui_ml-1.2.2 → camera_ui_ml-1.2.4}/camera_ui_ml/__init__.py +0 -0
- {camera_ui_ml-1.2.2 → camera_ui_ml-1.2.4}/camera_ui_ml/backend.py +0 -0
- {camera_ui_ml-1.2.2 → camera_ui_ml-1.2.4}/camera_ui_ml/detectors/__init__.py +0 -0
- {camera_ui_ml-1.2.2 → camera_ui_ml-1.2.4}/camera_ui_ml/detectors/box.py +0 -0
- {camera_ui_ml-1.2.2 → camera_ui_ml-1.2.4}/camera_ui_ml/detectors/embedder.py +0 -0
- {camera_ui_ml-1.2.2 → camera_ui_ml-1.2.4}/camera_ui_ml/detectors/ocr.py +0 -0
- {camera_ui_ml-1.2.2 → camera_ui_ml-1.2.4}/camera_ui_ml/geometry.py +0 -0
- {camera_ui_ml-1.2.2 → camera_ui_ml-1.2.4}/camera_ui_ml/parsing.py +0 -0
- {camera_ui_ml-1.2.2 → camera_ui_ml-1.2.4}/camera_ui_ml/pipeline.py +0 -0
- {camera_ui_ml-1.2.2 → camera_ui_ml-1.2.4}/camera_ui_ml/preprocess.py +0 -0
- {camera_ui_ml-1.2.2 → camera_ui_ml-1.2.4}/camera_ui_ml/py.typed +0 -0
- {camera_ui_ml-1.2.2 → camera_ui_ml-1.2.4}/camera_ui_ml.egg-info/SOURCES.txt +0 -0
- {camera_ui_ml-1.2.2 → camera_ui_ml-1.2.4}/camera_ui_ml.egg-info/dependency_links.txt +0 -0
- {camera_ui_ml-1.2.2 → camera_ui_ml-1.2.4}/camera_ui_ml.egg-info/requires.txt +0 -0
- {camera_ui_ml-1.2.2 → camera_ui_ml-1.2.4}/camera_ui_ml.egg-info/top_level.txt +0 -0
- {camera_ui_ml-1.2.2 → camera_ui_ml-1.2.4}/setup.cfg +0 -0
|
@@ -38,6 +38,7 @@ class BaseDetector:
|
|
|
38
38
|
|
|
39
39
|
async def _do_initialize(self, model_name: str) -> None:
|
|
40
40
|
try:
|
|
41
|
+
self.logger.log(f"Loading {self.name}: {model_name}...")
|
|
41
42
|
self.backend = await self.manager.ensure_backend(model_name)
|
|
42
43
|
if self.closed:
|
|
43
44
|
return
|
|
@@ -46,6 +47,7 @@ class BaseDetector:
|
|
|
46
47
|
self.logger.success(f"Loaded {self.name}: {model_name}")
|
|
47
48
|
except Exception as error:
|
|
48
49
|
self.logger.error(f"Failed to initialize {self.name}: {error}")
|
|
50
|
+
raise
|
|
49
51
|
finally:
|
|
50
52
|
self._init_task = None
|
|
51
53
|
|
|
@@ -38,9 +38,7 @@ class ClipEncoder:
|
|
|
38
38
|
if self.initialized:
|
|
39
39
|
return
|
|
40
40
|
if self._init_task is None:
|
|
41
|
-
self._init_task = asyncio.create_task(
|
|
42
|
-
self._do_initialize(vision_model, text_model)
|
|
43
|
-
)
|
|
41
|
+
self._init_task = asyncio.create_task(self._do_initialize(vision_model, text_model))
|
|
44
42
|
await self._init_task
|
|
45
43
|
|
|
46
44
|
async def close(self) -> None:
|
|
@@ -83,10 +81,7 @@ class ClipEncoder:
|
|
|
83
81
|
async def embed_frames(self, frames: list[VideoFrameData]) -> list[list[float]]:
|
|
84
82
|
if not self._ready():
|
|
85
83
|
return [[] for _ in frames]
|
|
86
|
-
return [
|
|
87
|
-
await self.embed_frame(frame["width"], frame["height"], frame["data"])
|
|
88
|
-
for frame in frames
|
|
89
|
-
]
|
|
84
|
+
return [await self.embed_frame(frame["width"], frame["height"], frame["data"]) for frame in frames]
|
|
90
85
|
|
|
91
86
|
def _ready(self) -> bool:
|
|
92
87
|
return (
|
|
@@ -98,35 +93,29 @@ class ClipEncoder:
|
|
|
98
93
|
|
|
99
94
|
async def _do_initialize(self, vision_model: str, text_model: str) -> None:
|
|
100
95
|
try:
|
|
96
|
+
self.logger.log(f"Loading CLIP: {vision_model} + {text_model}...")
|
|
101
97
|
self.vision = await self.manager.ensure_backend(vision_model)
|
|
102
98
|
self.text = await self.manager.ensure_backend(text_model)
|
|
103
99
|
processor_dir = await self.manager.ensure_clip_processor()
|
|
104
100
|
if processor_dir is None:
|
|
105
|
-
raise RuntimeError(
|
|
106
|
-
|
|
107
|
-
)
|
|
108
|
-
self.processor = await asyncio.to_thread(
|
|
109
|
-
CLIPProcessor.from_pretrained, processor_dir
|
|
110
|
-
)
|
|
101
|
+
raise RuntimeError("CLIP processor files are not bundled (clip_processor_files is empty)")
|
|
102
|
+
self.processor = await asyncio.to_thread(CLIPProcessor.from_pretrained, processor_dir)
|
|
111
103
|
if self.closed:
|
|
112
104
|
return
|
|
113
105
|
self.initialized = True
|
|
114
106
|
self.logger.success(f"Loaded CLIP: {vision_model} + {text_model}")
|
|
115
107
|
except Exception as error:
|
|
116
108
|
self.logger.error(f"Failed to initialize CLIP encoder: {error}")
|
|
109
|
+
raise
|
|
117
110
|
finally:
|
|
118
111
|
self._init_task = None
|
|
119
112
|
|
|
120
113
|
def _vision_input(self, pil: Any) -> NDArray:
|
|
121
|
-
inputs = self.processor(
|
|
122
|
-
images=pil, return_tensors="np", padding="max_length", truncation=True
|
|
123
|
-
)
|
|
114
|
+
inputs = self.processor(images=pil, return_tensors="np", padding="max_length", truncation=True)
|
|
124
115
|
return np.asarray(inputs["pixel_values"], dtype=np.float32)
|
|
125
116
|
|
|
126
117
|
def _text_input(self, text: str) -> tuple[NDArray, NDArray]:
|
|
127
|
-
inputs = self.processor(
|
|
128
|
-
text=text, return_tensors="np", padding="max_length", truncation=True
|
|
129
|
-
)
|
|
118
|
+
inputs = self.processor(text=text, return_tensors="np", padding="max_length", truncation=True)
|
|
130
119
|
return (
|
|
131
120
|
np.asarray(inputs["input_ids"], dtype=np.int64),
|
|
132
121
|
np.asarray(inputs["attention_mask"], dtype=np.int64),
|
|
@@ -27,13 +27,19 @@ class BaseModelManager(ABC):
|
|
|
27
27
|
self.logger = logger
|
|
28
28
|
self.model_path = os.path.join(storage_path, "models", version)
|
|
29
29
|
self._load_tasks: dict[str, asyncio.Task[InferenceBackend]] = {}
|
|
30
|
+
self._build_lock = asyncio.Lock()
|
|
30
31
|
|
|
31
32
|
async def ensure_backend(self, model_name: str) -> InferenceBackend:
|
|
32
33
|
task = self._load_tasks.get(model_name)
|
|
33
34
|
if task is None:
|
|
34
35
|
task = asyncio.create_task(self._load(model_name))
|
|
35
36
|
self._load_tasks[model_name] = task
|
|
36
|
-
|
|
37
|
+
try:
|
|
38
|
+
return await task
|
|
39
|
+
except Exception:
|
|
40
|
+
if self._load_tasks.get(model_name) is task:
|
|
41
|
+
del self._load_tasks[model_name]
|
|
42
|
+
raise
|
|
37
43
|
|
|
38
44
|
def reset(self) -> None:
|
|
39
45
|
self._load_tasks.clear()
|
|
@@ -57,9 +63,7 @@ class BaseModelManager(ABC):
|
|
|
57
63
|
"""Map of file key → (download_url, path relative to ``model_path``)."""
|
|
58
64
|
|
|
59
65
|
@abstractmethod
|
|
60
|
-
async def build_backend(
|
|
61
|
-
self, model_name: str, paths: Mapping[str, str]
|
|
62
|
-
) -> InferenceBackend:
|
|
66
|
+
async def build_backend(self, model_name: str, paths: Mapping[str, str]) -> InferenceBackend:
|
|
63
67
|
"""Build the runtime backend from the (already downloaded) local paths."""
|
|
64
68
|
|
|
65
69
|
async def _load(self, model_name: str) -> InferenceBackend:
|
|
@@ -67,11 +71,9 @@ class BaseModelManager(ABC):
|
|
|
67
71
|
for url, rel in files.values():
|
|
68
72
|
await self._download(url, rel)
|
|
69
73
|
|
|
70
|
-
paths = {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
}
|
|
74
|
-
return await self.build_backend(model_name, paths)
|
|
74
|
+
paths = {key: os.path.join(self.model_path, rel) for key, (_url, rel) in files.items()}
|
|
75
|
+
async with self._build_lock:
|
|
76
|
+
return await self.build_backend(model_name, paths)
|
|
75
77
|
|
|
76
78
|
async def _download(self, url: str, rel: str) -> None:
|
|
77
79
|
full_path = os.path.join(self.model_path, rel)
|
|
@@ -105,11 +105,13 @@ async def _detect_plates_one(
|
|
|
105
105
|
for (_cid, conf, box), result in zip(raw, ocr_results, strict=False):
|
|
106
106
|
if result is None or not result.text:
|
|
107
107
|
continue
|
|
108
|
+
# box conf = "this looks like a plate", ocr conf = "the text is legible";
|
|
109
|
+
# combine so illegible reads rank low for the downstream vote
|
|
108
110
|
detections.append(
|
|
109
111
|
{
|
|
110
112
|
"label": "vehicle",
|
|
111
113
|
"attribute": "license_plate",
|
|
112
|
-
"confidence": conf,
|
|
114
|
+
"confidence": float(conf) * float(result.confidence),
|
|
113
115
|
"plateText": result.text,
|
|
114
116
|
"box": normalize_box(box, width, height),
|
|
115
117
|
}
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "camera-ui-ml"
|
|
7
|
-
version = "1.2.
|
|
7
|
+
version = "1.2.4"
|
|
8
8
|
description = "Shared ML inference framework for camera.ui detection plugins (onnx, openvino, coreml, ncnn)"
|
|
9
9
|
keywords = ["camera.ui", "python", "ml", "inference", "detection"]
|
|
10
10
|
readme = "README.md"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|