matrice-inference 0.1.2__py3-none-any.whl → 0.1.22__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.

Potentially problematic release.


This version of matrice-inference might be problematic. Click here for more details.

@@ -1,5 +1,5 @@
1
1
  """
2
- Ultra-optimized streaming pipeline using MatriceStream and updated inference interface:
2
+ Streaming pipeline using MatriceStream and updated inference interface:
3
3
  Direct processing with priority queues, dynamic camera configuration
4
4
 
5
5
  Architecture:
@@ -18,186 +18,137 @@ Features:
18
18
  - Non-blocking threading for consumers/producers
19
19
  """
20
20
 
21
- import asyncio
22
- import json
23
- import time
24
21
  import logging
25
- import threading
26
22
  import queue
27
- import signal
28
- import copy
29
- from dataclasses import dataclass, field
30
- from typing import Any, Dict, Optional, List, Union
31
23
  from concurrent.futures import ThreadPoolExecutor
24
+ from typing import Any, Dict, List, Optional
32
25
 
33
- from matrice_common.stream.matrice_stream import MatriceStream, StreamType
34
26
  from matrice_analytics.post_processing.post_processor import PostProcessor
35
-
36
27
  from matrice_inference.server.inference_interface import InferenceInterface
37
- from matrice_inference.server.model.model_manager_wrapper import ModelManagerWrapper
38
- from matrice_inference.server.stream.utils import CameraConfig, StreamMessage
39
28
  from matrice_inference.server.stream.consumer_worker import ConsumerWorker
40
29
  from matrice_inference.server.stream.inference_worker import InferenceWorker
41
30
  from matrice_inference.server.stream.post_processing_worker import PostProcessingWorker
42
31
  from matrice_inference.server.stream.producer_worker import ProducerWorker
43
- # from matrice_inference.server.stream.frame_cache import RedisFrameCache
32
+ from matrice_inference.server.stream.utils import CameraConfig
44
33
 
45
34
 
46
35
 
47
36
  class StreamingPipeline:
48
- """Ultra-optimized streaming pipeline with dynamic camera configuration."""
49
-
37
+ """Optimized streaming pipeline with dynamic camera configuration and clean resource management."""
38
+
39
+ DEFAULT_QUEUE_SIZE = 5000
40
+ DEFAULT_MESSAGE_TIMEOUT = 10.0
41
+ DEFAULT_INFERENCE_TIMEOUT = 30.0
42
+ DEFAULT_SHUTDOWN_TIMEOUT = 30.0
43
+
50
44
  def __init__(
51
45
  self,
52
46
  inference_interface: InferenceInterface,
53
47
  post_processor: PostProcessor,
54
- consumer_threads=1,
55
- producer_threads=1,
56
- inference_threads=1,
57
- postprocessing_threads=1,
58
- inference_queue_maxsize=5000,
59
- postproc_queue_maxsize=5000,
60
- output_queue_maxsize=5000,
61
- message_timeout=10.0,
62
- inference_timeout=30.0,
63
- shutdown_timeout=30.0,
48
+ inference_queue_maxsize: int = DEFAULT_QUEUE_SIZE,
49
+ postproc_queue_maxsize: int = DEFAULT_QUEUE_SIZE,
50
+ output_queue_maxsize: int = DEFAULT_QUEUE_SIZE,
51
+ message_timeout: float = DEFAULT_MESSAGE_TIMEOUT,
52
+ inference_timeout: float = DEFAULT_INFERENCE_TIMEOUT,
53
+ shutdown_timeout: float = DEFAULT_SHUTDOWN_TIMEOUT,
64
54
  camera_configs: Optional[Dict[str, CameraConfig]] = None,
65
55
  ):
66
56
  self.inference_interface = inference_interface
67
57
  self.post_processor = post_processor
68
- self.consumer_threads = consumer_threads
69
- self.producer_threads = producer_threads
70
- self.inference_threads = inference_threads
71
- self.postprocessing_threads = postprocessing_threads
72
- self.inference_queue_maxsize = inference_queue_maxsize
73
- self.postproc_queue_maxsize = postproc_queue_maxsize
74
- self.output_queue_maxsize = output_queue_maxsize
75
58
  self.message_timeout = message_timeout
76
59
  self.inference_timeout = inference_timeout
77
60
  self.shutdown_timeout = shutdown_timeout
78
-
79
- # Camera configurations (can be empty initially)
61
+
80
62
  self.camera_configs: Dict[str, CameraConfig] = camera_configs or {}
81
-
82
- # Priority queues for pipeline stages
83
- self.inference_queue = queue.PriorityQueue(maxsize=self.inference_queue_maxsize)
84
- self.postproc_queue = queue.PriorityQueue(maxsize=self.postproc_queue_maxsize)
85
- self.output_queue = queue.PriorityQueue(maxsize=self.output_queue_maxsize)
86
-
87
- # Thread pools for CPU/GPU intensive work
88
- self.inference_executor = ThreadPoolExecutor(self.inference_threads)
89
- self.postprocessing_executor = ThreadPoolExecutor(self.postprocessing_threads)
90
-
91
- # No centralized stream management - each worker creates its own streams
92
-
93
- # Worker instances
94
- self.consumer_workers: Dict[str, List[ConsumerWorker]] = {}
95
- self.inference_workers = []
96
- self.postproc_workers = []
97
- self.producer_workers = []
98
-
99
- # Worker threads
100
- self.worker_threads = []
101
-
102
- # Control state
103
63
  self.running = False
104
-
105
64
  self.logger = logging.getLogger(__name__)
106
-
107
- # Frame cache disabled (commented out)
108
- # self.frame_cache_config = frame_cache_config or {}
109
- # self.frame_cache: Optional[RedisFrameCache] = None
110
-
111
- # Removed set_global_instances method - now passing interfaces as parameters to worker functions
65
+
66
+ self._setup_queues(inference_queue_maxsize, postproc_queue_maxsize, output_queue_maxsize)
67
+ self._setup_executors()
68
+ self._setup_workers()
69
+ # Frame cache instance (initialized lazily at start)
70
+ self.frame_cache = None
71
+
72
+ def _setup_queues(self, inference_size: int, postproc_size: int, output_size: int) -> None:
73
+ """Initialize priority queues for pipeline stages."""
74
+ self.inference_queue = queue.PriorityQueue(maxsize=inference_size)
75
+ self.postproc_queue = queue.PriorityQueue(maxsize=postproc_size)
76
+ self.output_queue = queue.PriorityQueue(maxsize=output_size)
77
+
78
+ def _setup_executors(self) -> None:
79
+ """Initialize thread pool executors."""
80
+ # Single-thread executors to preserve strict ordering
81
+ self.inference_executor = ThreadPoolExecutor(max_workers=1)
82
+ self.postprocessing_executor = ThreadPoolExecutor(max_workers=1)
83
+
84
+ def _setup_workers(self) -> None:
85
+ """Initialize worker containers."""
86
+ self.consumer_workers: Dict[str, List[ConsumerWorker]] = {}
87
+ self.inference_workers: List = []
88
+ self.postproc_workers: List = []
89
+ self.producer_workers: List = []
90
+ self.worker_threads: List = []
112
91
 
113
- async def start(self):
114
- """Start the pipeline."""
92
+ async def start(self) -> None:
93
+ """Start the pipeline with proper error handling."""
115
94
  if self.running:
116
95
  self.logger.warning("Pipeline already running")
117
96
  return
118
-
97
+
119
98
  self.running = True
120
- self.logger.info("Starting ultra-optimized streaming pipeline...")
121
-
99
+ self.logger.info("Starting streaming pipeline...")
100
+
122
101
  try:
123
- # Frame cache initialization disabled
124
- # fc = dict(self.frame_cache_config)
125
- # if not fc.get("host"):
126
- # for cfg in self.camera_configs.values():
127
- # sc = cfg.stream_config
128
- # if sc.get("stream_type", "kafka").lower() == "redis":
129
- # fc.setdefault("host", sc.get("host", "localhost"))
130
- # fc.setdefault("port", sc.get("port", 6379))
131
- # fc.setdefault("password", sc.get("password"))
132
- # fc.setdefault("username", sc.get("username"))
133
- # fc.setdefault("db", sc.get("db", 0))
134
- # break
135
- # try:
136
- # self.frame_cache = RedisFrameCache(
137
- # host=fc.get("host", "localhost"),
138
- # port=fc.get("port", 6379),
139
- # db=fc.get("db", 0),
140
- # password=fc.get("password"),
141
- # username=fc.get("username"),
142
- # ttl_seconds=fc.get("ttl_seconds", 300),
143
- # prefix=fc.get("prefix", "stream:frames:"),
144
- # )
145
- # self.frame_cache.start()
146
- # except Exception as _:
147
- # self.frame_cache = None
148
- # self.logger.warning("Frame cache initialization failed; proceeding without cache")
149
-
150
- # Initialize streams for existing camera configs
151
- await self._initialize_streams()
152
-
153
- # Create and start workers
102
+ # Initialize frame cache before workers
103
+ self._initialize_frame_cache()
154
104
  await self._create_workers()
155
105
  self._start_workers()
156
-
157
- self.logger.info(
158
- f"Pipeline started with {len(self.camera_configs)} cameras, "
159
- f"{sum(len(workers) for workers in self.consumer_workers.values())} consumer workers, "
160
- f"{len(self.inference_workers)} inference workers, "
161
- f"{len(self.postproc_workers)} post-processing workers, "
162
- f"{len(self.producer_workers)} producer workers"
163
- )
164
-
106
+ self._log_startup_info()
165
107
  except Exception as e:
166
108
  self.logger.error(f"Failed to start pipeline: {e}")
167
109
  await self.stop()
168
110
  raise
111
+
112
+ def _log_startup_info(self) -> None:
113
+ """Log pipeline startup information."""
114
+ consumer_count = sum(len(workers) for workers in self.consumer_workers.values())
115
+ self.logger.info(
116
+ f"Pipeline started - Cameras: {len(self.camera_configs)}, "
117
+ f"Consumers: {consumer_count}, Inference: {len(self.inference_workers)}, "
118
+ f"PostProc: {len(self.postproc_workers)}, Producers: {len(self.producer_workers)}"
119
+ )
169
120
 
170
- async def stop(self):
171
- """Stop the pipeline gracefully."""
121
+ async def stop(self) -> None:
122
+ """Stop the pipeline gracefully with proper cleanup."""
172
123
  if not self.running:
173
124
  return
174
-
125
+
175
126
  self.logger.info("Stopping pipeline...")
176
127
  self.running = False
177
-
178
- # Stop all workers
128
+
179
129
  self._stop_workers()
180
-
181
- # Wait for all threads to complete
130
+ self._wait_for_threads()
131
+ self._shutdown_executors()
132
+
133
+ # Stop frame cache if running
134
+ try:
135
+ if self.frame_cache:
136
+ self.frame_cache.stop()
137
+ except Exception:
138
+ pass
139
+
140
+ self.logger.info("Pipeline stopped")
141
+
142
+ def _wait_for_threads(self) -> None:
143
+ """Wait for all worker threads to complete."""
182
144
  for thread in self.worker_threads:
183
145
  if thread.is_alive():
184
146
  thread.join(timeout=self.shutdown_timeout)
185
-
186
- # Close streams
187
- await self._close_streams()
188
-
189
- # Shutdown executors
147
+
148
+ def _shutdown_executors(self) -> None:
149
+ """Shutdown thread pool executors."""
190
150
  self.inference_executor.shutdown(wait=False)
191
151
  self.postprocessing_executor.shutdown(wait=False)
192
-
193
- # Frame cache stop disabled
194
- # try:
195
- # if self.frame_cache:
196
- # self.frame_cache.stop()
197
- # except Exception:
198
- # pass
199
-
200
- self.logger.info("Pipeline stopped")
201
152
 
202
153
  async def add_camera_config(self, camera_config: CameraConfig) -> bool:
203
154
  """
@@ -273,144 +224,132 @@ class StreamingPipeline:
273
224
  return await self.add_camera_config(camera_config)
274
225
 
275
226
  def enable_camera(self, camera_id: str) -> bool:
276
- """Enable a camera."""
277
- if camera_id in self.camera_configs:
278
- self.camera_configs[camera_id].enabled = True
279
- self.logger.info(f"Camera {camera_id} enabled")
280
- return True
281
- return False
282
-
227
+ """Enable a camera configuration."""
228
+ return self._set_camera_state(camera_id, True, "enabled")
229
+
283
230
  def disable_camera(self, camera_id: str) -> bool:
284
- """Disable a camera."""
231
+ """Disable a camera configuration."""
232
+ return self._set_camera_state(camera_id, False, "disabled")
233
+
234
+ def _set_camera_state(self, camera_id: str, enabled: bool, state_name: str) -> bool:
235
+ """Set camera enabled state."""
285
236
  if camera_id in self.camera_configs:
286
- self.camera_configs[camera_id].enabled = False
287
- self.logger.info(f"Camera {camera_id} disabled")
237
+ self.camera_configs[camera_id].enabled = enabled
238
+ self.logger.info(f"Camera {camera_id} {state_name}")
288
239
  return True
289
240
  return False
241
+
290
242
 
291
- async def _initialize_streams(self):
292
- """No centralized stream initialization - workers create their own streams."""
293
- pass
294
-
295
- async def _initialize_camera_streams(self, camera_config: CameraConfig):
296
- """No centralized camera stream initialization - workers create their own streams."""
297
- pass
298
-
299
- async def _close_streams(self):
300
- """No centralized streams to close - workers manage their own streams."""
301
- pass
302
-
303
- async def _close_camera_streams(self, camera_id: str):
304
- """No centralized camera streams to close - workers manage their own streams."""
305
- pass
306
-
307
- async def _create_workers(self):
308
- """Create all worker instances."""
309
- # Create consumer workers (per camera)
243
+ async def _create_workers(self) -> None:
244
+ """Create all worker instances for the pipeline."""
245
+ await self._create_consumer_workers()
246
+ self._create_inference_worker()
247
+ self._create_postprocessing_worker()
248
+ self._create_producer_worker()
249
+
250
+ async def _create_consumer_workers(self) -> None:
251
+ """Create consumer workers for all cameras."""
310
252
  for camera_config in self.camera_configs.values():
311
253
  await self._create_camera_workers(camera_config)
312
-
313
- # Create inference workers
314
- for i in range(self.inference_threads):
315
- worker = InferenceWorker(
316
- worker_id=i,
317
- inference_queue=self.inference_queue,
318
- postproc_queue=self.postproc_queue,
319
- inference_executor=self.inference_executor,
320
- message_timeout=self.message_timeout,
321
- inference_timeout=self.inference_timeout,
322
- inference_interface=self.inference_interface
323
- )
324
- self.inference_workers.append(worker)
325
-
326
- # Create post-processing workers
327
- for i in range(self.postprocessing_threads):
328
- worker = PostProcessingWorker(
329
- worker_id=i,
330
- postproc_queue=self.postproc_queue,
331
- output_queue=self.output_queue,
332
- postprocessing_executor=self.postprocessing_executor,
333
- message_timeout=self.message_timeout,
334
- inference_timeout=self.inference_timeout,
335
- post_processor=self.post_processor,
336
- # frame_cache=self.frame_cache,
337
- )
338
- self.postproc_workers.append(worker)
339
-
340
- # Create producer workers
341
- for i in range(self.producer_threads):
342
- worker = ProducerWorker(
343
- worker_id=i,
344
- output_queue=self.output_queue,
345
- camera_configs=self.camera_configs,
346
- message_timeout=self.message_timeout
347
- )
348
- self.producer_workers.append(worker)
254
+
255
+ def _create_inference_worker(self) -> None:
256
+ """Create single inference worker."""
257
+ worker = InferenceWorker(
258
+ worker_id=0,
259
+ inference_queue=self.inference_queue,
260
+ postproc_queue=self.postproc_queue,
261
+ inference_executor=self.inference_executor,
262
+ message_timeout=self.message_timeout,
263
+ inference_timeout=self.inference_timeout,
264
+ inference_interface=self.inference_interface
265
+ )
266
+ self.inference_workers.append(worker)
267
+
268
+ def _create_postprocessing_worker(self) -> None:
269
+ """Create single post-processing worker."""
270
+ worker = PostProcessingWorker(
271
+ worker_id=0,
272
+ postproc_queue=self.postproc_queue,
273
+ output_queue=self.output_queue,
274
+ postprocessing_executor=self.postprocessing_executor,
275
+ message_timeout=self.message_timeout,
276
+ inference_timeout=self.inference_timeout,
277
+ post_processor=self.post_processor,
278
+ frame_cache=self.frame_cache,
279
+ )
280
+ self.postproc_workers.append(worker)
281
+
282
+ def _create_producer_worker(self) -> None:
283
+ """Create single producer worker."""
284
+ worker = ProducerWorker(
285
+ worker_id=0,
286
+ output_queue=self.output_queue,
287
+ camera_configs=self.camera_configs,
288
+ message_timeout=self.message_timeout
289
+ )
290
+ self.producer_workers.append(worker)
349
291
 
350
- async def _create_camera_workers(self, camera_config: CameraConfig):
292
+ async def _create_camera_workers(self, camera_config: CameraConfig) -> None:
351
293
  """Create consumer workers for a specific camera."""
352
294
  camera_id = camera_config.camera_id
353
-
354
- # Create consumer workers for this camera - each worker will create its own stream
355
- camera_workers = []
356
- for i in range(self.consumer_threads):
357
- worker = ConsumerWorker(
358
- camera_id=camera_id,
359
- worker_id=i,
360
- stream_config=camera_config.stream_config,
361
- input_topic=camera_config.input_topic,
362
- inference_queue=self.inference_queue,
363
- message_timeout=self.message_timeout,
364
- camera_config=camera_config
365
- )
366
- camera_workers.append(worker)
367
-
368
- self.consumer_workers[camera_id] = camera_workers
295
+
296
+ worker = ConsumerWorker(
297
+ camera_id=camera_id,
298
+ worker_id=0,
299
+ stream_config=camera_config.stream_config,
300
+ input_topic=camera_config.input_topic,
301
+ inference_queue=self.inference_queue,
302
+ message_timeout=self.message_timeout,
303
+ camera_config=camera_config,
304
+ frame_cache=self.frame_cache
305
+ )
306
+
307
+ self.consumer_workers[camera_id] = [worker]
369
308
 
370
- def _start_workers(self):
371
- """Start all worker instances."""
372
- # Start consumer workers
309
+ def _start_workers(self) -> None:
310
+ """Start all worker instances and track their threads."""
311
+ self._start_all_camera_workers()
312
+ self._start_worker_group(self.inference_workers)
313
+ self._start_worker_group(self.postproc_workers)
314
+ self._start_worker_group(self.producer_workers)
315
+
316
+ def _start_all_camera_workers(self) -> None:
317
+ """Start consumer workers for all cameras."""
373
318
  for camera_id in self.consumer_workers:
374
319
  self._start_camera_workers(camera_id)
375
-
376
- # Start inference workers
377
- for worker in self.inference_workers:
378
- thread = worker.start()
379
- self.worker_threads.append(thread)
380
-
381
- # Start post-processing workers
382
- for worker in self.postproc_workers:
383
- thread = worker.start()
384
- self.worker_threads.append(thread)
385
-
386
- # Start producer workers
387
- for worker in self.producer_workers:
320
+
321
+ def _start_worker_group(self, workers: List) -> None:
322
+ """Start a group of workers and track their threads."""
323
+ for worker in workers:
388
324
  thread = worker.start()
389
325
  self.worker_threads.append(thread)
390
326
 
391
- def _start_camera_workers(self, camera_id: str):
327
+ def _start_camera_workers(self, camera_id: str) -> None:
392
328
  """Start consumer workers for a specific camera."""
393
329
  if camera_id in self.consumer_workers:
394
- for worker in self.consumer_workers[camera_id]:
395
- thread = worker.start()
396
- self.worker_threads.append(thread)
330
+ self._start_worker_group(self.consumer_workers[camera_id])
397
331
 
398
- def _stop_workers(self):
399
- """Stop all worker instances."""
400
- # Stop all workers
332
+ def _stop_workers(self) -> None:
333
+ """Stop all worker instances gracefully."""
334
+ self._stop_all_camera_workers()
335
+ self._stop_worker_group(self.inference_workers)
336
+ self._stop_worker_group(self.postproc_workers)
337
+ self._stop_worker_group(self.producer_workers)
338
+
339
+ def _stop_all_camera_workers(self) -> None:
340
+ """Stop all camera consumer workers."""
401
341
  for workers in self.consumer_workers.values():
402
- for worker in workers:
403
- worker.stop()
404
-
405
- for worker in (self.inference_workers + self.postproc_workers + self.producer_workers):
342
+ self._stop_worker_group(workers)
343
+
344
+ def _stop_worker_group(self, workers: List) -> None:
345
+ """Stop a group of workers."""
346
+ for worker in workers:
406
347
  worker.stop()
407
348
 
408
- async def _stop_camera_workers(self, camera_id: str):
349
+ async def _stop_camera_workers(self, camera_id: str) -> None:
409
350
  """Stop consumer workers for a specific camera."""
410
351
  if camera_id in self.consumer_workers:
411
- for worker in self.consumer_workers[camera_id]:
412
- worker.stop()
413
- # Remove from tracking
352
+ self._stop_worker_group(self.consumer_workers[camera_id])
414
353
  del self.consumer_workers[camera_id]
415
354
 
416
355
  def get_metrics(self) -> Dict[str, Any]:
@@ -434,10 +373,6 @@ class StreamingPipeline:
434
373
  "total_threads": len(self.worker_threads),
435
374
  "active_threads": len([t for t in self.worker_threads if t.is_alive()]),
436
375
  },
437
- "thread_pool_sizes": {
438
- "inference_threads": self.inference_threads,
439
- "postprocessing_threads": self.postprocessing_threads,
440
- },
441
376
  "camera_configs": {
442
377
  camera_id: {
443
378
  "input_topic": config.input_topic,
@@ -449,3 +384,40 @@ class StreamingPipeline:
449
384
  }
450
385
  }
451
386
 
387
+ def _initialize_frame_cache(self) -> None:
388
+ """Initialize RedisFrameCache with TTL 10 minutes, deriving connection from Redis cameras if available."""
389
+ try:
390
+ # Find a Redis camera config for connection params
391
+ host = "localhost"
392
+ port = 6379
393
+ password = None
394
+ username = None
395
+ db = 0
396
+
397
+ for cfg in self.camera_configs.values():
398
+ sc = cfg.stream_config or {}
399
+ st = sc.get("stream_type", "kafka").lower()
400
+ if st == "redis":
401
+ host = sc.get("host", host)
402
+ port = sc.get("port", port)
403
+ password = sc.get("password", password)
404
+ username = sc.get("username", username)
405
+ db = sc.get("db", db)
406
+ break
407
+
408
+ # Lazy import to avoid dependency issues if not used
409
+ from matrice_inference.server.stream.frame_cache import RedisFrameCache
410
+ self.frame_cache = RedisFrameCache(
411
+ host=host,
412
+ port=port,
413
+ db=db,
414
+ password=password,
415
+ username=username,
416
+ ttl_seconds=600, # 10 minutes
417
+ worker_threads=1, # conservative to avoid contention
418
+ )
419
+ self.frame_cache.start()
420
+ self.logger.info("Initialized RedisFrameCache with 10-minute TTL")
421
+ except Exception as e:
422
+ self.frame_cache = None
423
+ self.logger.warning(f"Frame cache initialization failed; proceeding without cache: {e}")
@@ -73,7 +73,7 @@ class AnalyticsSummarizer:
73
73
  # application_key_name: {
74
74
  # "meta": {name, key_name, version},
75
75
  # "last_input_timestamp": str,
76
- # "earliest_reset_timestamp": str | None,
76
+ # "earliest_reset_timestamp": str or None,
77
77
  # "current_counts": {category: last_value},
78
78
  # "total_counts": {category: max_value}
79
79
  # }
@@ -10,10 +10,6 @@ from triton_model_manager import TritonModelManager
10
10
  import GPUtil
11
11
  import pytz
12
12
 
13
- logging.basicConfig(
14
- level=logging.INFO,
15
- format="%(asctime)s [%(levelname)s] %(message)s",
16
- )
17
13
  logger = logging.getLogger(__name__)
18
14
 
19
15
  COCO_CLASSES = [
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: matrice_inference
3
- Version: 0.1.2
3
+ Version: 0.1.22
4
4
  Summary: Common server utilities for Matrice.ai services
5
5
  Author-email: "Matrice.ai" <dipendra@matrice.ai>
6
6
  License-Expression: MIT