matrice-inference 0.1.2__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.

Files changed (37) hide show
  1. matrice_inference/__init__.py +72 -0
  2. matrice_inference/py.typed +0 -0
  3. matrice_inference/server/__init__.py +23 -0
  4. matrice_inference/server/inference_interface.py +176 -0
  5. matrice_inference/server/model/__init__.py +1 -0
  6. matrice_inference/server/model/model_manager.py +274 -0
  7. matrice_inference/server/model/model_manager_wrapper.py +550 -0
  8. matrice_inference/server/model/triton_model_manager.py +290 -0
  9. matrice_inference/server/model/triton_server.py +1248 -0
  10. matrice_inference/server/proxy_interface.py +371 -0
  11. matrice_inference/server/server.py +1004 -0
  12. matrice_inference/server/stream/__init__.py +0 -0
  13. matrice_inference/server/stream/app_deployment.py +228 -0
  14. matrice_inference/server/stream/consumer_worker.py +201 -0
  15. matrice_inference/server/stream/frame_cache.py +127 -0
  16. matrice_inference/server/stream/inference_worker.py +163 -0
  17. matrice_inference/server/stream/post_processing_worker.py +230 -0
  18. matrice_inference/server/stream/producer_worker.py +147 -0
  19. matrice_inference/server/stream/stream_pipeline.py +451 -0
  20. matrice_inference/server/stream/utils.py +23 -0
  21. matrice_inference/tmp/abstract_model_manager.py +58 -0
  22. matrice_inference/tmp/aggregator/__init__.py +18 -0
  23. matrice_inference/tmp/aggregator/aggregator.py +330 -0
  24. matrice_inference/tmp/aggregator/analytics.py +906 -0
  25. matrice_inference/tmp/aggregator/ingestor.py +438 -0
  26. matrice_inference/tmp/aggregator/latency.py +597 -0
  27. matrice_inference/tmp/aggregator/pipeline.py +968 -0
  28. matrice_inference/tmp/aggregator/publisher.py +431 -0
  29. matrice_inference/tmp/aggregator/synchronizer.py +594 -0
  30. matrice_inference/tmp/batch_manager.py +239 -0
  31. matrice_inference/tmp/overall_inference_testing.py +338 -0
  32. matrice_inference/tmp/triton_utils.py +638 -0
  33. matrice_inference-0.1.2.dist-info/METADATA +28 -0
  34. matrice_inference-0.1.2.dist-info/RECORD +37 -0
  35. matrice_inference-0.1.2.dist-info/WHEEL +5 -0
  36. matrice_inference-0.1.2.dist-info/licenses/LICENSE.txt +21 -0
  37. matrice_inference-0.1.2.dist-info/top_level.txt +1 -0
@@ -0,0 +1,163 @@
1
+ import asyncio
2
+ import json
3
+ import time
4
+ import logging
5
+ import threading
6
+ import queue
7
+ from typing import Any, Dict
8
+ from concurrent.futures import ThreadPoolExecutor
9
+
10
+ class InferenceWorker:
11
+ """Handles inference processing using threading."""
12
+
13
+ def __init__(self, worker_id: int, inference_queue: queue.PriorityQueue,
14
+ postproc_queue: queue.PriorityQueue, inference_executor: ThreadPoolExecutor,
15
+ message_timeout: float, inference_timeout: float, inference_interface=None):
16
+ self.worker_id = worker_id
17
+ self.inference_queue = inference_queue
18
+ self.postproc_queue = postproc_queue
19
+ self.inference_executor = inference_executor
20
+ self.message_timeout = message_timeout
21
+ self.inference_timeout = inference_timeout
22
+ self.inference_interface = inference_interface
23
+ self.running = False
24
+ self.logger = logging.getLogger(f"{__name__}.inference.{worker_id}")
25
+
26
+ def start(self):
27
+ """Start the inference worker in a separate thread."""
28
+ self.running = True
29
+ thread = threading.Thread(target=self._run, name=f"InferenceWorker-{self.worker_id}", daemon=False)
30
+ thread.start()
31
+ return thread
32
+
33
+ def stop(self):
34
+ """Stop the inference worker."""
35
+ self.running = False
36
+
37
+ def _run(self):
38
+ """Main inference dispatcher loop."""
39
+ self.logger.info(f"Started inference worker {self.worker_id}")
40
+
41
+ while self.running:
42
+ try:
43
+ # Get task from inference queue
44
+ try:
45
+ priority, timestamp, task_data = self.inference_queue.get(timeout=self.message_timeout)
46
+ except queue.Empty:
47
+ continue
48
+
49
+ # Process inference task
50
+ self._process_inference_task(priority, task_data)
51
+
52
+ except Exception as e:
53
+ self.logger.error(f"Inference worker error: {e}")
54
+
55
+ self.logger.info(f"Inference worker {self.worker_id} stopped")
56
+
57
+ def _process_inference_task(self, priority: int, task_data: Dict[str, Any]):
58
+ """Process a single inference task."""
59
+ try:
60
+ message = task_data["message"]
61
+
62
+ # Submit to thread pool for async execution
63
+ start_time = time.time()
64
+ future = self.inference_executor.submit(self._run_inference, task_data)
65
+ result = future.result(timeout=self.inference_timeout)
66
+ processing_time = time.time() - start_time
67
+
68
+ if result["success"]:
69
+ # Create post-processing task
70
+ postproc_task = {
71
+ "original_message": message,
72
+ "model_result": result["model_result"],
73
+ "metadata": result["metadata"],
74
+ "processing_time": processing_time,
75
+ "input_stream": task_data["input_stream"],
76
+ "stream_key": task_data["stream_key"],
77
+ "camera_config": task_data["camera_config"]
78
+ }
79
+
80
+ # Add to post-processing queue with timestamp as tie-breaker
81
+ self.postproc_queue.put((priority, time.time(), postproc_task))
82
+ else:
83
+ self.logger.error(f"Inference failed: {result['error']}")
84
+
85
+ except Exception as e:
86
+ self.logger.error(f"Inference task error: {e}")
87
+
88
+ def _run_inference(self, task_data: Dict[str, Any]) -> Dict[str, Any]:
89
+ """Run inference in thread pool."""
90
+ try:
91
+ # Extract task data - handle camera streamer format
92
+ input_stream_data = task_data.get("input_stream", {})
93
+ input_content = input_stream_data.get("content")
94
+
95
+ # Handle base64 encoded content from camera streamer
96
+ if input_content and isinstance(input_content, str):
97
+ import base64
98
+ try:
99
+ input_content = base64.b64decode(input_content)
100
+ except Exception as e:
101
+ logging.warning(f"Failed to decode base64 input: {str(e)}")
102
+
103
+ stream_key = task_data.get("stream_key")
104
+ stream_info = input_stream_data.get("stream_info", {})
105
+ camera_info = input_stream_data.get("camera_info", {})
106
+ extra_params = task_data.get("extra_params", {})
107
+
108
+ # Ensure extra_params is a dictionary
109
+ if not isinstance(extra_params, dict):
110
+ logging.warning(f"extra_params is not a dict in inference worker, converting from {type(extra_params)}: {extra_params}")
111
+ if isinstance(extra_params, list):
112
+ # Convert list to dict if possible
113
+ if len(extra_params) == 0:
114
+ extra_params = {}
115
+ elif all(isinstance(item, dict) for item in extra_params):
116
+ # Merge all dictionaries in the list
117
+ merged_params = {}
118
+ for item in extra_params:
119
+ merged_params.update(item)
120
+ extra_params = merged_params
121
+ else:
122
+ extra_params = {}
123
+ else:
124
+ extra_params = {}
125
+
126
+ if self.inference_interface is None:
127
+ raise ValueError("Inference interface not initialized")
128
+
129
+ # Create event loop for this thread if it doesn't exist
130
+ try:
131
+ loop = asyncio.get_event_loop()
132
+ except RuntimeError:
133
+ loop = asyncio.new_event_loop()
134
+ asyncio.set_event_loop(loop)
135
+
136
+ # Perform inference
137
+ model_result, metadata = loop.run_until_complete(
138
+ self.inference_interface.inference(
139
+ input=input_content,
140
+ extra_params=extra_params,
141
+ apply_post_processing=False, # Inference only
142
+ stream_key=stream_key,
143
+ stream_info=stream_info,
144
+ camera_info=camera_info
145
+ )
146
+ )
147
+
148
+ return {
149
+ "model_result": model_result,
150
+ "metadata": metadata,
151
+ "success": True,
152
+ "error": None
153
+ }
154
+
155
+ except Exception as e:
156
+ logging.error(f"Inference worker error: {str(e)}", exc_info=True)
157
+ return {
158
+ "model_result": None,
159
+ "metadata": None,
160
+ "success": False,
161
+ "error": str(e)
162
+ }
163
+
@@ -0,0 +1,230 @@
1
+ import asyncio
2
+ import logging
3
+ import threading
4
+ import queue
5
+ import time
6
+ from typing import Any, Dict
7
+ from concurrent.futures import ThreadPoolExecutor
8
+
9
+
10
+ class PostProcessingWorker:
11
+ """Handles post-processing using threading."""
12
+
13
+ def __init__(self, worker_id: int, postproc_queue: queue.PriorityQueue,
14
+ output_queue: queue.PriorityQueue, postprocessing_executor: ThreadPoolExecutor,
15
+ message_timeout: float, inference_timeout: float, post_processor=None,
16
+ frame_cache=None):
17
+ self.worker_id = worker_id
18
+ self.postproc_queue = postproc_queue
19
+ self.output_queue = output_queue
20
+ self.postprocessing_executor = postprocessing_executor
21
+ self.message_timeout = message_timeout
22
+ self.inference_timeout = inference_timeout
23
+ self.post_processor = post_processor
24
+ self.frame_cache = frame_cache
25
+ self.running = False
26
+ self.logger = logging.getLogger(f"{__name__}.postproc.{worker_id}")
27
+
28
+ def start(self):
29
+ """Start the post-processing worker in a separate thread."""
30
+ self.running = True
31
+ thread = threading.Thread(target=self._run, name=f"PostProcWorker-{self.worker_id}", daemon=False)
32
+ thread.start()
33
+ return thread
34
+
35
+ def stop(self):
36
+ """Stop the post-processing worker."""
37
+ self.running = False
38
+
39
+ def _run(self):
40
+ """Main post-processing dispatcher loop."""
41
+ self.logger.info(f"Started post-processing worker {self.worker_id}")
42
+
43
+ while self.running:
44
+ try:
45
+ # Get task from post-processing queue
46
+ try:
47
+ priority, timestamp, task_data = self.postproc_queue.get(timeout=self.message_timeout)
48
+ except queue.Empty:
49
+ continue
50
+
51
+ # Process post-processing task
52
+ self._process_postproc_task(priority, task_data)
53
+
54
+ except Exception as e:
55
+ self.logger.error(f"Post-processing worker error: {e}")
56
+
57
+ self.logger.info(f"Post-processing worker {self.worker_id} stopped")
58
+
59
+ def _process_postproc_task(self, priority: int, task_data: Dict[str, Any]):
60
+ """Process a single post-processing task."""
61
+ try:
62
+ # Submit to thread pool for async execution
63
+ future = self.postprocessing_executor.submit(self._run_post_processing, task_data)
64
+ result = future.result(timeout=self.inference_timeout)
65
+
66
+ if result["success"]:
67
+ # Cache disabled: preserving content in output and not pushing to Redis
68
+ # try:
69
+ # orig_input = task_data.get("input_stream", {}) or {}
70
+ # content = orig_input.get("content")
71
+ # frame_id_for_cache = task_data.get("frame_id") or orig_input.get("frame_id")
72
+ # if content and frame_id_for_cache and self.frame_cache:
73
+ # if isinstance(content, bytes):
74
+ # import base64
75
+ # try:
76
+ # content = base64.b64encode(content).decode("ascii")
77
+ # except Exception:
78
+ # content = None
79
+ # if isinstance(content, str):
80
+ # self.frame_cache.put(frame_id_for_cache, content)
81
+ # except Exception:
82
+ # pass
83
+
84
+ # Create final output message
85
+ # Prepare input_stream for output: ensure frame_id is present and strip bulky content
86
+ safe_input_stream = {}
87
+ try:
88
+ if isinstance(task_data.get("input_stream"), dict):
89
+ safe_input_stream = dict(task_data["input_stream"]) # shallow copy
90
+ # Ensure frame_id propagation
91
+ if "frame_id" not in safe_input_stream and "frame_id" in task_data:
92
+ safe_input_stream["frame_id"] = task_data["frame_id"]
93
+ # Do not strip content; keep as-is in output
94
+ # if "content" in safe_input_stream:
95
+ # safe_input_stream["content"] = ""
96
+ except Exception:
97
+ safe_input_stream = task_data.get("input_stream", {})
98
+
99
+ # Determine frame_id for top-level convenience
100
+ frame_id = task_data.get("frame_id")
101
+ if not frame_id and isinstance(safe_input_stream, dict):
102
+ frame_id = safe_input_stream.get("frame_id")
103
+
104
+ output_data = {
105
+ "camera_id": task_data["original_message"].camera_id,
106
+ "message_key": task_data["original_message"].message_key,
107
+ "timestamp": task_data["original_message"].timestamp.isoformat(),
108
+ "frame_id": frame_id,
109
+ "model_result": task_data["model_result"],
110
+ "input_stream": safe_input_stream,
111
+ "post_processing_result": result["post_processing_result"],
112
+ "processing_time_sec": task_data["processing_time"],
113
+ "metadata": task_data.get("metadata", {})
114
+ }
115
+
116
+ # Add to output queue
117
+ output_task = {
118
+ "camera_id": task_data["original_message"].camera_id,
119
+ "message_key": task_data["original_message"].message_key,
120
+ "data": output_data,
121
+ }
122
+ # Add to output queue with timestamp as tie-breaker
123
+ self.output_queue.put((priority, time.time(), output_task))
124
+ else:
125
+ self.logger.error(f"Post-processing failed: {result['error']}")
126
+
127
+ except Exception as e:
128
+ self.logger.error(f"Post-processing task error: {e}")
129
+
130
+ def _run_post_processing(self, task_data: Dict[str, Any]) -> Dict[str, Any]:
131
+ """Run post-processing in thread pool."""
132
+ try:
133
+ if self.post_processor is None:
134
+ raise ValueError("Post processor not initialized")
135
+
136
+ # Extract task data
137
+ model_result = task_data["model_result"]
138
+ input_stream_data = task_data.get("input_stream", {})
139
+ input_content = input_stream_data.get("content")
140
+
141
+ # Handle base64 encoded content
142
+ if input_content and isinstance(input_content, str):
143
+ import base64
144
+ try:
145
+ input_content = base64.b64decode(input_content)
146
+ except Exception as e:
147
+ logging.warning(f"Failed to decode base64 input: {str(e)}")
148
+ input_content = None
149
+
150
+ stream_key = task_data.get("stream_key")
151
+ stream_info = input_stream_data.get("stream_info", {})
152
+ camera_config = task_data.get("camera_config", {})
153
+
154
+ # Create event loop for this thread if it doesn't exist
155
+ try:
156
+ loop = asyncio.get_event_loop()
157
+ except RuntimeError:
158
+ loop = asyncio.new_event_loop()
159
+ asyncio.set_event_loop(loop)
160
+
161
+ # Perform post-processing
162
+ result = loop.run_until_complete(
163
+ self.post_processor.process(
164
+ data=model_result,
165
+ input_bytes=input_content if isinstance(input_content, bytes) else None,
166
+ stream_key=stream_key,
167
+ stream_info=stream_info
168
+ )
169
+ )
170
+
171
+ # For face recognition use case, return empty raw results
172
+ processed_raw_results = []
173
+ try:
174
+ if hasattr(result, 'usecase') and not result.usecase == 'face_recognition':
175
+ processed_raw_results = model_result
176
+ except Exception as e:
177
+ logging.warning(f"Failed to get processed raw results: {str(e)}")
178
+
179
+ # Extract agg_summary from result data if available
180
+ agg_summary = {}
181
+ try:
182
+ if hasattr(result, 'data') and isinstance(result.data, dict):
183
+ agg_summary = result.data.get("agg_summary", {})
184
+ except Exception as e:
185
+ logging.warning(f"Failed to get agg summary: {str(e)}")
186
+
187
+ # Format result similar to InferenceInterface
188
+ if result.is_success():
189
+ post_processing_result = {
190
+ "status": "success",
191
+ "processing_time": result.processing_time,
192
+ "usecase": getattr(result, 'usecase', ''),
193
+ "category": getattr(result, 'category', ''),
194
+ "summary": getattr(result, 'summary', ''),
195
+ "insights": getattr(result, 'insights', []),
196
+ "metrics": getattr(result, 'metrics', {}),
197
+ "predictions": getattr(result, 'predictions', []),
198
+ "agg_summary": agg_summary,
199
+ "raw_results": processed_raw_results,
200
+ "stream_key": stream_key
201
+ }
202
+ else:
203
+ post_processing_result = {
204
+ "status": "post_processing_failed",
205
+ "error": result.error_message,
206
+ "error_type": getattr(result, 'error_type', 'ProcessingError'),
207
+ "processing_time": result.processing_time,
208
+ "stream_key": stream_key,
209
+ "agg_summary": agg_summary,
210
+ "raw_results": model_result
211
+ }
212
+
213
+ return {
214
+ "post_processing_result": post_processing_result,
215
+ "success": True,
216
+ "error": None
217
+ }
218
+
219
+ except Exception as e:
220
+ logging.error(f"Post-processing worker error: {str(e)}", exc_info=True)
221
+ return {
222
+ "post_processing_result": {
223
+ "status": "post_processing_failed",
224
+ "error": str(e),
225
+ "error_type": type(e).__name__,
226
+ "stream_key": task_data.get("stream_key")
227
+ },
228
+ "success": False,
229
+ "error": str(e)
230
+ }
@@ -0,0 +1,147 @@
1
+ import asyncio
2
+ import json
3
+ import time
4
+ import logging
5
+ import threading
6
+ import queue
7
+ from typing import Any, Dict
8
+ from matrice_common.stream.matrice_stream import MatriceStream
9
+ from matrice_inference.server.stream.utils import CameraConfig
10
+
11
+ class ProducerWorker:
12
+ """Handles message production to streams."""
13
+
14
+ def __init__(self, worker_id: int, output_queue: queue.PriorityQueue,
15
+ camera_configs: Dict[str, CameraConfig], message_timeout: float):
16
+ self.worker_id = worker_id
17
+ self.output_queue = output_queue
18
+ self.camera_configs = camera_configs
19
+ self.message_timeout = message_timeout
20
+ self.running = False
21
+ self.producer_streams = {} # Will be created in worker thread's event loop
22
+ self.logger = logging.getLogger(f"{__name__}.producer.{worker_id}")
23
+
24
+ def start(self):
25
+ """Start the producer worker in a separate thread."""
26
+ self.running = True
27
+ thread = threading.Thread(target=self._run, name=f"ProducerWorker-{self.worker_id}", daemon=False)
28
+ thread.start()
29
+ return thread
30
+
31
+ def stop(self):
32
+ """Stop the producer worker."""
33
+ self.running = False
34
+
35
+ def _run(self):
36
+ """Main producer loop."""
37
+ loop = asyncio.new_event_loop()
38
+ asyncio.set_event_loop(loop)
39
+
40
+ self.logger.info(f"Started producer worker {self.worker_id}")
41
+
42
+ try:
43
+ # Initialize streams for all cameras in this event loop
44
+ loop.run_until_complete(self._initialize_streams())
45
+
46
+ while self.running:
47
+ try:
48
+ # Get task from output queue
49
+ try:
50
+ priority, timestamp, task_data = self.output_queue.get(timeout=self.message_timeout)
51
+ except queue.Empty:
52
+ continue
53
+
54
+ # Send message to stream
55
+ loop.run_until_complete(self._send_message_safely(task_data))
56
+
57
+ except Exception as e:
58
+ self.logger.error(f"Producer error: {e}")
59
+ time.sleep(0.1)
60
+
61
+ finally:
62
+ # Clean up streams
63
+ for stream in self.producer_streams.values():
64
+ try:
65
+ loop.run_until_complete(stream.async_close())
66
+ except Exception as e:
67
+ self.logger.error(f"Error closing producer stream: {e}")
68
+ loop.close()
69
+ self.logger.info(f"Producer worker {self.worker_id} stopped")
70
+
71
+ async def _initialize_streams(self):
72
+ """Initialize producer streams for all cameras in the current event loop."""
73
+ try:
74
+ from matrice_common.stream.matrice_stream import MatriceStream, StreamType
75
+
76
+ for camera_id, camera_config in self.camera_configs.items():
77
+ try:
78
+ stream_config = camera_config.stream_config
79
+ output_topic = camera_config.output_topic
80
+
81
+ # Determine stream type
82
+ stream_type = StreamType.KAFKA if stream_config.get("stream_type", "kafka").lower() == "kafka" else StreamType.REDIS
83
+
84
+ # Create stream configuration
85
+ if stream_type == StreamType.KAFKA:
86
+ stream_params = {
87
+ "bootstrap_servers": stream_config.get("bootstrap_servers", "localhost:9092"),
88
+ "sasl_username": stream_config.get("sasl_username", "matrice-sdk-user"),
89
+ "sasl_password": stream_config.get("sasl_password", "matrice-sdk-password"),
90
+ "sasl_mechanism": stream_config.get("sasl_mechanism", "SCRAM-SHA-256"),
91
+ "security_protocol": stream_config.get("security_protocol", "SASL_PLAINTEXT"),
92
+ }
93
+ else: # Redis
94
+ stream_params = {
95
+ "host": stream_config.get("host", "localhost"),
96
+ "port": stream_config.get("port", 6379),
97
+ "password": stream_config.get("password"),
98
+ "username": stream_config.get("username"),
99
+ "db": stream_config.get("db", 0),
100
+ }
101
+
102
+ # Create and setup producer stream
103
+ producer_stream = MatriceStream(stream_type, **stream_params)
104
+ await producer_stream.async_setup(output_topic)
105
+ self.producer_streams[camera_id] = producer_stream
106
+
107
+ self.logger.info(f"Initialized {stream_type.value} producer stream for camera {camera_id} in worker {self.worker_id}")
108
+
109
+ except Exception as e:
110
+ self.logger.error(f"Failed to initialize producer stream for camera {camera_id}: {e}")
111
+ continue
112
+
113
+ except Exception as e:
114
+ self.logger.error(f"Failed to initialize producer streams: {e}")
115
+ raise
116
+
117
+ async def _send_message_safely(self, task_data: Dict[str, Any]):
118
+ """Send message to the appropriate stream safely."""
119
+ try:
120
+ camera_id = task_data["camera_id"]
121
+ message_key = task_data["message_key"]
122
+ data = task_data["data"]
123
+
124
+ # Check if camera and stream still exist
125
+ if camera_id not in self.producer_streams or camera_id not in self.camera_configs:
126
+ self.logger.warning(f"Camera {camera_id} not found in producer streams or configs")
127
+ return
128
+
129
+ camera_config = self.camera_configs[camera_id]
130
+ if not camera_config.enabled:
131
+ self.logger.debug(f"Camera {camera_id} is disabled, skipping message")
132
+ return
133
+
134
+ # Get producer stream for camera
135
+ producer_stream = self.producer_streams[camera_id]
136
+ output_topic = camera_config.output_topic
137
+
138
+ # Send message to stream
139
+ await producer_stream.async_add_message(
140
+ output_topic,
141
+ json.dumps(data),
142
+ key=message_key
143
+ )
144
+
145
+ except Exception as e:
146
+ self.logger.error(f"Error sending message: {e}")
147
+