matrice 1.0.99127__py3-none-any.whl → 1.0.99128__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.
- matrice/deploy/utils/post_processing/config.py +2 -0
- matrice/deploy/utils/post_processing/usecases/banana_defect_detection.py +362 -394
- {matrice-1.0.99127.dist-info → matrice-1.0.99128.dist-info}/METADATA +1 -1
- {matrice-1.0.99127.dist-info → matrice-1.0.99128.dist-info}/RECORD +7 -7
- {matrice-1.0.99127.dist-info → matrice-1.0.99128.dist-info}/WHEEL +0 -0
- {matrice-1.0.99127.dist-info → matrice-1.0.99128.dist-info}/licenses/LICENSE.txt +0 -0
- {matrice-1.0.99127.dist-info → matrice-1.0.99128.dist-info}/top_level.txt +0 -0
@@ -24,6 +24,7 @@ APP_NAME_TO_USECASE = {
|
|
24
24
|
"shopping_cart_analysis": "shopping_cart_analysis",
|
25
25
|
"car_part_segmentation": "car_part_segmentation",
|
26
26
|
"weld_defect_detection" : "weld_defect_detection",
|
27
|
+
"fruit_monitoring" : "fruit_monitoring",
|
27
28
|
}
|
28
29
|
|
29
30
|
APP_NAME_TO_CATEGORY = {
|
@@ -52,6 +53,7 @@ APP_NAME_TO_CATEGORY = {
|
|
52
53
|
"shopping_cart_analysis": "retail",
|
53
54
|
"car_part_segmentation": "automobile",
|
54
55
|
"weld_defect_detection" : "weld",
|
56
|
+
"fruit_monitoring" : "agriculture",
|
55
57
|
}
|
56
58
|
|
57
59
|
def get_usecase_from_app_name(app_name: str) -> str:
|
@@ -1,9 +1,9 @@
|
|
1
1
|
from typing import Any, Dict, List, Optional
|
2
|
-
from dataclasses import asdict
|
2
|
+
from dataclasses import asdict
|
3
3
|
import time
|
4
4
|
from datetime import datetime, timezone
|
5
5
|
|
6
|
-
from ..core.base import BaseProcessor, ProcessingContext, ProcessingResult, ConfigProtocol
|
6
|
+
from ..core.base import BaseProcessor, ProcessingContext, ProcessingResult, ConfigProtocol
|
7
7
|
from ..utils import (
|
8
8
|
filter_by_confidence,
|
9
9
|
filter_by_categories,
|
@@ -16,11 +16,13 @@ from ..utils import (
|
|
16
16
|
BBoxSmoothingConfig,
|
17
17
|
BBoxSmoothingTracker
|
18
18
|
)
|
19
|
+
from dataclasses import dataclass, field
|
19
20
|
from ..core.config import BaseConfig, AlertConfig, ZoneConfig
|
20
21
|
|
22
|
+
|
21
23
|
@dataclass
|
22
24
|
class BananaMonitoringConfig(BaseConfig):
|
23
|
-
"""Configuration for
|
25
|
+
"""Configuration for banana defect detection use case."""
|
24
26
|
enable_smoothing: bool = True
|
25
27
|
smoothing_algorithm: str = "observability"
|
26
28
|
smoothing_window_size: int = 20
|
@@ -45,139 +47,23 @@ class BananaMonitoringConfig(BaseConfig):
|
|
45
47
|
}
|
46
48
|
)
|
47
49
|
|
48
|
-
class BananaMonitoringUseCase(BaseProcessor):
|
49
|
-
def _get_track_ids_info(self, detections: list) -> Dict[str, Any]:
|
50
|
-
frame_track_ids = set()
|
51
|
-
for det in detections:
|
52
|
-
tid = det.get('track_id')
|
53
|
-
if tid is not None:
|
54
|
-
frame_track_ids.add(tid)
|
55
|
-
total_track_ids = set()
|
56
|
-
for s in getattr(self, '_fruit_total_track_ids', {}).values():
|
57
|
-
total_track_ids.update(s)
|
58
|
-
return {
|
59
|
-
"total_count": len(total_track_ids),
|
60
|
-
"current_frame_count": len(frame_track_ids),
|
61
|
-
"total_unique_track_ids": len(total_track_ids),
|
62
|
-
"current_frame_track_ids": list(frame_track_ids),
|
63
|
-
"last_update_time": time.time(),
|
64
|
-
"total_frames_processed": getattr(self, '_total_frame_counter', 0)
|
65
|
-
}
|
66
|
-
|
67
|
-
@staticmethod
|
68
|
-
def _iou(bbox1, bbox2):
|
69
|
-
x1 = max(bbox1["xmin"], bbox2["xmin"])
|
70
|
-
y1 = max(bbox1["ymin"], bbox2["ymin"])
|
71
|
-
x2 = min(bbox1["xmax"], bbox2["xmax"])
|
72
|
-
y2 = min(bbox1["ymax"], bbox2["ymax"])
|
73
|
-
inter_w = max(0, x2 - x1)
|
74
|
-
inter_h = max(0, y2 - y1)
|
75
|
-
inter_area = inter_w * inter_h
|
76
|
-
area1 = (bbox1["xmax"] - bbox1["xmin"]) * (bbox1["ymax"] - bbox1["ymin"])
|
77
|
-
area2 = (bbox2["xmax"] - bbox2["xmin"]) * (bbox2["ymax"] - bbox2["ymin"])
|
78
|
-
union = area1 + area2 - inter_area
|
79
|
-
if union == 0:
|
80
|
-
return 0.0
|
81
|
-
return inter_area / union
|
82
|
-
|
83
|
-
@staticmethod
|
84
|
-
def _deduplicate_fruits(detections, iou_thresh=0.7):
|
85
|
-
filtered = []
|
86
|
-
used = [False] * len(detections)
|
87
|
-
for i, det in enumerate(detections):
|
88
|
-
if used[i]:
|
89
|
-
continue
|
90
|
-
group = [i]
|
91
|
-
for j in range(i+1, len(detections)):
|
92
|
-
if used[j]:
|
93
|
-
continue
|
94
|
-
if det.get("category") == detections[j].get("category"):
|
95
|
-
bbox1 = det.get("bounding_box")
|
96
|
-
bbox2 = detections[j].get("bounding_box")
|
97
|
-
if bbox1 and bbox2:
|
98
|
-
iou = BananaMonitoringUseCase._iou(bbox1, bbox2)
|
99
|
-
if iou > iou_thresh:
|
100
|
-
used[j] = True
|
101
|
-
group.append(j)
|
102
|
-
best_idx = max(group, key=lambda idx: detections[idx].get("confidence", 0))
|
103
|
-
filtered.append(detections[best_idx])
|
104
|
-
used[best_idx] = True
|
105
|
-
return filtered
|
106
|
-
|
107
|
-
def _update_fruit_tracking_state(self, detections: list):
|
108
|
-
if not hasattr(self, "_fruit_total_track_ids"):
|
109
|
-
self._fruit_total_track_ids = {cat: set() for cat in self.fruit_categories}
|
110
|
-
self._fruit_current_frame_track_ids = {cat: set() for cat in self.fruit_categories}
|
111
|
-
|
112
|
-
for det in detections:
|
113
|
-
cat = det.get("category")
|
114
|
-
raw_track_id = det.get("track_id")
|
115
|
-
if cat not in self.fruit_categories or raw_track_id is None:
|
116
|
-
continue
|
117
|
-
bbox = det.get("bounding_box", det.get("bbox"))
|
118
|
-
canonical_id = self._merge_or_register_track(raw_track_id, bbox)
|
119
|
-
det["track_id"] = canonical_id
|
120
|
-
self._fruit_total_track_ids.setdefault(cat, set()).add(canonical_id)
|
121
|
-
self._fruit_current_frame_track_ids[cat].add(canonical_id)
|
122
|
-
|
123
|
-
def get_total_fruit_counts(self):
|
124
|
-
return {cat: len(ids) for cat, ids in getattr(self, '_fruit_total_track_ids', {}).items()}
|
125
|
-
|
126
|
-
def _format_timestamp_for_video(self, timestamp: float) -> str:
|
127
|
-
hours = int(timestamp // 3600)
|
128
|
-
minutes = int((timestamp % 3600) // 60)
|
129
|
-
seconds = timestamp % 60
|
130
|
-
return f"{hours:02d}:{minutes:02d}:{seconds:06.2f}"
|
131
|
-
|
132
|
-
def _format_timestamp_for_stream(self, timestamp: float) -> str:
|
133
|
-
dt = datetime.fromtimestamp(timestamp, tz=timezone.utc)
|
134
|
-
return dt.strftime('%Y:%m:%d %H:%M:%S')
|
135
|
-
|
136
|
-
def _get_current_timestamp_str(self, stream_info: Optional[Dict[str, Any]]) -> str:
|
137
|
-
if not stream_info:
|
138
|
-
return "00:00:00.00"
|
139
|
-
if stream_info.get("input_settings", {}).get("stream_type", "video_file") == "video_file":
|
140
|
-
stream_time_str = stream_info.get("video_timestamp", "")
|
141
|
-
return stream_time_str[:8]
|
142
|
-
else:
|
143
|
-
stream_time_str = stream_info.get("stream_time", "")
|
144
|
-
if stream_time_str:
|
145
|
-
try:
|
146
|
-
timestamp_str = stream_time_str.replace(" UTC", "")
|
147
|
-
dt = datetime.strptime(timestamp_str, "%Y-%m-%d-%H:%M:%S.%f")
|
148
|
-
timestamp = dt.replace(tzinfo=timezone.utc).timestamp()
|
149
|
-
return self._format_timestamp_for_stream(timestamp)
|
150
|
-
except:
|
151
|
-
return self._format_timestamp_for_stream(time.time())
|
152
|
-
else:
|
153
|
-
return self._format_timestamp_for_stream(time.time())
|
154
|
-
|
155
|
-
def _get_start_timestamp_str(self, stream_info: Optional[Dict[str, Any]]) -> str:
|
156
|
-
if not stream_info:
|
157
|
-
return "00:00:00"
|
158
|
-
is_video_chunk = stream_info.get("input_settings", {}).get("is_video_chunk", False)
|
159
|
-
if is_video_chunk or stream_info.get("input_settings", {}).get("stream_type", "video_file") == "video_file":
|
160
|
-
return "00:00:00"
|
161
|
-
else:
|
162
|
-
if self._tracking_start_time is None:
|
163
|
-
stream_time_str = stream_info.get("stream_time", "")
|
164
|
-
if stream_time_str:
|
165
|
-
try:
|
166
|
-
timestamp_str = stream_time_str.replace(" UTC", "")
|
167
|
-
dt = datetime.strptime(timestamp_str, "%Y-%m-%d-%H:%M:%S.%f")
|
168
|
-
self._tracking_start_time = dt.replace(tzinfo=timezone.utc).timestamp()
|
169
|
-
except:
|
170
|
-
self._tracking_start_time = time.time()
|
171
|
-
else:
|
172
|
-
self._tracking_start_time = time.time()
|
173
|
-
dt = datetime.fromtimestamp(self._tracking_start_time, tz=timezone.utc)
|
174
|
-
dt = dt.replace(minute=0, second=0, microsecond=0)
|
175
|
-
return dt.strftime('%Y:%m:%d %H:%M:%S')
|
176
50
|
|
51
|
+
class BananaMonitoringUseCase(BaseProcessor):
|
52
|
+
CATEGORY_DISPLAY = {
|
53
|
+
"freshripe": "Fresh Ripe",
|
54
|
+
"freshunripe": "Fresh Unripe",
|
55
|
+
"overripe": "Overripe",
|
56
|
+
"ripe": "Ripe",
|
57
|
+
"rotten": "Rotten",
|
58
|
+
"unripe": "Unripe"
|
59
|
+
}
|
60
|
+
|
177
61
|
def __init__(self):
|
178
62
|
super().__init__("fruit_monitoring")
|
179
63
|
self.category = "agriculture"
|
180
|
-
self.
|
64
|
+
self.CASE_TYPE: Optional[str] = 'banana_defect_detection'
|
65
|
+
self.CASE_VERSION: Optional[str] = '1.0'
|
66
|
+
self.target_categories = ['freshripe', 'freshunripe', 'overripe', 'ripe', 'rotten', 'unripe']
|
181
67
|
self.smoothing_tracker = None
|
182
68
|
self.tracker = None
|
183
69
|
self._total_frame_counter = 0
|
@@ -187,8 +73,11 @@ class BananaMonitoringUseCase(BaseProcessor):
|
|
187
73
|
self._canonical_tracks: Dict[Any, Dict[str, Any]] = {}
|
188
74
|
self._track_merge_iou_threshold: float = 0.05
|
189
75
|
self._track_merge_time_window: float = 7.0
|
76
|
+
self._ascending_alert_list: List[int] = []
|
77
|
+
self.current_incident_end_timestamp: str = "N/A"
|
190
78
|
|
191
|
-
def process(self, data: Any, config: ConfigProtocol, context: Optional[ProcessingContext] = None,
|
79
|
+
def process(self, data: Any, config: ConfigProtocol, context: Optional[ProcessingContext] = None,
|
80
|
+
stream_info: Optional[Dict[str, Any]] = None) -> ProcessingResult:
|
192
81
|
start_time = time.time()
|
193
82
|
if not isinstance(config, BananaMonitoringConfig):
|
194
83
|
return self.create_error_result("Invalid config type", usecase=self.name, category=self.category, context=context)
|
@@ -204,15 +93,15 @@ class BananaMonitoringUseCase(BaseProcessor):
|
|
204
93
|
self.logger.debug(f"Applied confidence filtering with threshold {config.confidence_threshold}")
|
205
94
|
else:
|
206
95
|
processed_data = data
|
207
|
-
self.logger.debug("
|
96
|
+
self.logger.debug("No confidence filtering applied")
|
208
97
|
|
209
98
|
if config.index_to_category:
|
210
99
|
processed_data = apply_category_mapping(processed_data, config.index_to_category)
|
211
100
|
self.logger.debug("Applied category mapping")
|
212
101
|
|
213
102
|
if config.target_fruit_categories:
|
214
|
-
processed_data = [d for d in processed_data if d.get('category') in self.
|
215
|
-
self.logger.debug("Applied
|
103
|
+
processed_data = [d for d in processed_data if d.get('category') in self.target_categories]
|
104
|
+
self.logger.debug("Applied category filtering")
|
216
105
|
|
217
106
|
if config.enable_smoothing:
|
218
107
|
if self.smoothing_tracker is None:
|
@@ -225,8 +114,7 @@ class BananaMonitoringUseCase(BaseProcessor):
|
|
225
114
|
enable_smoothing=True
|
226
115
|
)
|
227
116
|
self.smoothing_tracker = BBoxSmoothingTracker(smoothing_config)
|
228
|
-
|
229
|
-
processed_data = smoothed_fruits
|
117
|
+
processed_data = bbox_smoothing(processed_data, self.smoothing_tracker.config, self.smoothing_tracker)
|
230
118
|
|
231
119
|
try:
|
232
120
|
from ..advanced_tracker import AdvancedTracker
|
@@ -234,13 +122,12 @@ class BananaMonitoringUseCase(BaseProcessor):
|
|
234
122
|
if self.tracker is None:
|
235
123
|
tracker_config = TrackerConfig()
|
236
124
|
self.tracker = AdvancedTracker(tracker_config)
|
237
|
-
self.logger.info("Initialized AdvancedTracker for
|
125
|
+
self.logger.info("Initialized AdvancedTracker for Banana Monitoring")
|
238
126
|
processed_data = self.tracker.update(processed_data)
|
239
127
|
except Exception as e:
|
240
128
|
self.logger.warning(f"AdvancedTracker failed: {e}")
|
241
129
|
|
242
|
-
|
243
|
-
self._update_fruit_tracking_state(processed_data)
|
130
|
+
self._update_tracking_state(processed_data)
|
244
131
|
self._total_frame_counter += 1
|
245
132
|
|
246
133
|
frame_number = None
|
@@ -253,212 +140,372 @@ class BananaMonitoringUseCase(BaseProcessor):
|
|
253
140
|
|
254
141
|
general_counting_summary = calculate_counting_summary(data)
|
255
142
|
counting_summary = self._count_categories(processed_data, config)
|
256
|
-
|
257
|
-
counting_summary['
|
258
|
-
|
259
|
-
alerts = self._check_alerts(counting_summary, config)
|
143
|
+
total_counts = self.get_total_counts()
|
144
|
+
counting_summary['total_counts'] = total_counts
|
145
|
+
alerts = self._check_alerts(counting_summary, frame_number, config)
|
260
146
|
predictions = self._extract_predictions(processed_data)
|
261
|
-
summary = self._generate_summary(counting_summary, alerts)
|
262
147
|
|
263
|
-
|
264
|
-
tracking_stats_list = self._generate_tracking_stats(counting_summary,
|
148
|
+
incidents_list = self._generate_incidents(counting_summary, alerts, config, frame_number, stream_info)
|
149
|
+
tracking_stats_list = self._generate_tracking_stats(counting_summary, alerts, config, frame_number, stream_info)
|
150
|
+
business_analytics_list = self._generate_business_analytics(counting_summary, alerts, config, stream_info, is_empty=True)
|
151
|
+
summary_list = self._generate_summary(counting_summary, incidents_list, tracking_stats_list, business_analytics_list, alerts)
|
265
152
|
|
266
|
-
|
153
|
+
incidents = incidents_list[0] if incidents_list else {}
|
267
154
|
tracking_stats = tracking_stats_list[0] if tracking_stats_list else {}
|
155
|
+
business_analytics = business_analytics_list[0] if business_analytics_list else {}
|
156
|
+
summary = summary_list[0] if summary_list else {}
|
157
|
+
agg_summary = {str(frame_number): {
|
158
|
+
"incidents": incidents,
|
159
|
+
"tracking_stats": tracking_stats,
|
160
|
+
"business_analytics": business_analytics,
|
161
|
+
"alerts": alerts,
|
162
|
+
"human_text": summary}
|
163
|
+
}
|
268
164
|
|
269
165
|
context.mark_completed()
|
270
166
|
result = self.create_result(
|
271
|
-
data={
|
272
|
-
"counting_summary": counting_summary,
|
273
|
-
"general_counting_summary": general_counting_summary,
|
274
|
-
"alerts": alerts,
|
275
|
-
"total_fruits": counting_summary.get("total_count", 0),
|
276
|
-
"events": events,
|
277
|
-
"tracking_stats": tracking_stats,
|
278
|
-
},
|
167
|
+
data={"agg_summary": agg_summary},
|
279
168
|
usecase=self.name,
|
280
169
|
category=self.category,
|
281
170
|
context=context
|
282
171
|
)
|
283
|
-
result.summary = summary
|
284
|
-
result.insights = insights
|
285
|
-
result.predictions = predictions
|
286
172
|
return result
|
287
173
|
|
288
|
-
def
|
289
|
-
|
290
|
-
|
291
|
-
|
174
|
+
def _check_alerts(self, summary: dict, frame_number: Any, config: BananaMonitoringConfig) -> List[Dict]:
|
175
|
+
def get_trend(data, lookback=900, threshold=0.6):
|
176
|
+
window = data[-lookback:] if len(data) >= lookback else data
|
177
|
+
if len(window) < 2:
|
178
|
+
return True
|
179
|
+
increasing = 0
|
180
|
+
total = 0
|
181
|
+
for i in range(1, len(window)):
|
182
|
+
if window[i] >= window[i - 1]:
|
183
|
+
increasing += 1
|
184
|
+
total += 1
|
185
|
+
ratio = increasing / total
|
186
|
+
return ratio >= threshold
|
292
187
|
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
self._track_aliases.clear()
|
299
|
-
self._canonical_tracks.clear()
|
300
|
-
self.logger.info("Fruit Monitoring tracking state reset")
|
188
|
+
frame_key = str(frame_number) if frame_number is not None else "current_frame"
|
189
|
+
alerts = []
|
190
|
+
total_detections = summary.get("total_count", 0)
|
191
|
+
total_counts_dict = summary.get("total_counts", {})
|
192
|
+
per_category_count = summary.get("per_category_count", {})
|
301
193
|
|
302
|
-
|
303
|
-
|
304
|
-
self.reset_fruit_tracking()
|
305
|
-
self.logger.info("All Fruits tracking state reset")
|
194
|
+
if not config.alert_config:
|
195
|
+
return alerts
|
306
196
|
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
197
|
+
total = summary.get("total_count", 0)
|
198
|
+
if hasattr(config.alert_config, 'count_thresholds') and config.alert_config.count_thresholds:
|
199
|
+
for category, threshold in config.alert_config.count_thresholds.items():
|
200
|
+
if category == "all" and total > threshold:
|
201
|
+
alerts.append({
|
202
|
+
"alert_type": getattr(config.alert_config, 'alert_type', ['Default']),
|
203
|
+
"alert_id": f"alert_{category}_{frame_key}",
|
204
|
+
"incident_category": self.CASE_TYPE,
|
205
|
+
"threshold_level": threshold,
|
206
|
+
"ascending": get_trend(self._ascending_alert_list, lookback=900, threshold=0.8),
|
207
|
+
"settings": {t: v for t, v in zip(getattr(config.alert_config, 'alert_type', ['Default']),
|
208
|
+
getattr(config.alert_config, 'alert_value', ['JSON']))}
|
209
|
+
})
|
210
|
+
elif category in per_category_count:
|
211
|
+
count = per_category_count[category]
|
212
|
+
if count > threshold:
|
213
|
+
alerts.append({
|
214
|
+
"alert_type": getattr(config.alert_config, 'alert_type', ['Default']),
|
215
|
+
"alert_id": f"alert_{category}_{frame_key}",
|
216
|
+
"incident_category": self.CASE_TYPE,
|
217
|
+
"threshold_level": threshold,
|
218
|
+
"ascending": get_trend(self._ascending_alert_list, lookback=900, threshold=0.8),
|
219
|
+
"settings": {t: v for t, v in zip(getattr(config.alert_config, 'alert_type', ['Default']),
|
220
|
+
getattr(config.alert_config, 'alert_value', ['JSON']))}
|
221
|
+
})
|
222
|
+
return alerts
|
223
|
+
|
224
|
+
def _generate_incidents(self, counting_summary: Dict, alerts: List, config: BananaMonitoringConfig,
|
225
|
+
frame_number: Optional[int] = None, stream_info: Optional[Dict[str, Any]] = None) -> List[Dict]:
|
226
|
+
incidents = []
|
227
|
+
total_detections = counting_summary.get("total_count", 0)
|
228
|
+
current_timestamp = self._get_current_timestamp_str(stream_info)
|
229
|
+
camera_info = self.get_camera_info_from_stream(stream_info)
|
230
|
+
|
231
|
+
self._ascending_alert_list = self._ascending_alert_list[-900:] if len(self._ascending_alert_list) > 900 else self._ascending_alert_list
|
312
232
|
|
313
|
-
if
|
314
|
-
level = "
|
233
|
+
if total_detections > 0:
|
234
|
+
level = "low"
|
315
235
|
intensity = 5.0
|
236
|
+
start_timestamp = self._get_start_timestamp_str(stream_info)
|
237
|
+
if start_timestamp and self.current_incident_end_timestamp == 'N/A':
|
238
|
+
self.current_incident_end_timestamp = 'Incident still active'
|
239
|
+
elif start_timestamp and self.current_incident_end_timestamp == 'Incident still active':
|
240
|
+
if len(self._ascending_alert_list) >= 15 and sum(self._ascending_alert_list[-15:]) / 15 < 1.5:
|
241
|
+
self.current_incident_end_timestamp = current_timestamp
|
242
|
+
elif self.current_incident_end_timestamp != 'Incident still active' and self.current_incident_end_timestamp != 'N/A':
|
243
|
+
self.current_incident_end_timestamp = 'N/A'
|
244
|
+
|
316
245
|
if config.alert_config and config.alert_config.count_thresholds:
|
317
246
|
threshold = config.alert_config.count_thresholds.get("all", 15)
|
318
|
-
intensity = min(10.0, (
|
319
|
-
if intensity >=
|
247
|
+
intensity = min(10.0, (total_detections / threshold) * 10)
|
248
|
+
if intensity >= 9:
|
320
249
|
level = "critical"
|
250
|
+
self._ascending_alert_list.append(3)
|
251
|
+
elif intensity >= 7:
|
252
|
+
level = "significant"
|
253
|
+
self._ascending_alert_list.append(2)
|
321
254
|
elif intensity >= 5:
|
322
|
-
level = "
|
255
|
+
level = "medium"
|
256
|
+
self._ascending_alert_list.append(1)
|
323
257
|
else:
|
324
|
-
level = "
|
258
|
+
level = "low"
|
259
|
+
self._ascending_alert_list.append(0)
|
325
260
|
else:
|
326
|
-
if
|
261
|
+
if total_detections > 30:
|
327
262
|
level = "critical"
|
263
|
+
intensity = 10.0
|
264
|
+
self._ascending_alert_list.append(3)
|
265
|
+
elif total_detections > 25:
|
266
|
+
level = "significant"
|
328
267
|
intensity = 9.0
|
329
|
-
|
330
|
-
|
268
|
+
self._ascending_alert_list.append(2)
|
269
|
+
elif total_detections > 15:
|
270
|
+
level = "medium"
|
331
271
|
intensity = 7.0
|
272
|
+
self._ascending_alert_list.append(1)
|
332
273
|
else:
|
333
|
-
level = "
|
334
|
-
intensity = min(10.0,
|
274
|
+
level = "low"
|
275
|
+
intensity = min(10.0, total_detections / 3.0)
|
276
|
+
self._ascending_alert_list.append(0)
|
335
277
|
|
336
|
-
human_text_lines = ["
|
337
|
-
human_text_lines.append(f"
|
278
|
+
human_text_lines = [f"INCIDENTS DETECTED @ {current_timestamp}:"]
|
279
|
+
human_text_lines.append(f"\tSeverity Level: {(self.CASE_TYPE, level)}")
|
338
280
|
human_text = "\n".join(human_text_lines)
|
339
281
|
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
"
|
347
|
-
"
|
348
|
-
|
349
|
-
}
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
intensity_message = "ALERT: Low fruit density in the scene"
|
376
|
-
else:
|
377
|
-
intensity_message = "ALERT: Moderate fruit density in the scene"
|
378
|
-
|
379
|
-
alert_event = {
|
380
|
-
"type": alert.get("type", "density_alert"),
|
381
|
-
"stream_time": datetime.now(timezone.utc).strftime("%Y-%m-%d-%H:%M:%S UTC"),
|
382
|
-
"level": alert.get("severity", "warning"),
|
383
|
-
"intensity": 8.0,
|
384
|
-
"config": {
|
385
|
-
"min_value": 0,
|
386
|
-
"max_value": 10,
|
387
|
-
"level_settings": {"info": 2, "warning": 5, "critical": 7}
|
388
|
-
},
|
389
|
-
"application_name": "Fruit Density Alert System",
|
390
|
-
"application_version": "1.2",
|
391
|
-
"location_info": alert.get("zone"),
|
392
|
-
"human_text": f"{datetime.now(timezone.utc).strftime('%Y-%m-%d-%H:%M:%S UTC')} : {intensity_message}"
|
393
|
-
}
|
394
|
-
frame_events.append(alert_event)
|
395
|
-
|
396
|
-
return events
|
397
|
-
|
398
|
-
def _generate_tracking_stats(
|
399
|
-
self,
|
400
|
-
counting_summary: Dict,
|
401
|
-
insights: List[str],
|
402
|
-
summary: str,
|
403
|
-
config: BananaMonitoringConfig,
|
404
|
-
frame_number: Optional[int] = None,
|
405
|
-
stream_info: Optional[Dict[str, Any]] = None
|
406
|
-
) -> List[Dict]:
|
407
|
-
frame_key = str(frame_number) if frame_number is not None else "current_frame"
|
408
|
-
tracking_stats = [{frame_key: []}]
|
409
|
-
frame_tracking_stats = tracking_stats[0][frame_key]
|
410
|
-
|
411
|
-
total_fruits = counting_summary.get("total_count", 0)
|
412
|
-
total_fruit_counts = counting_summary.get("total_fruit_counts", {})
|
413
|
-
cumulative_total = sum(total_fruit_counts.values()) if total_fruit_counts else 0
|
282
|
+
alert_settings = []
|
283
|
+
if config.alert_config and hasattr(config.alert_config, 'alert_type'):
|
284
|
+
alert_settings.append({
|
285
|
+
"alert_type": getattr(config.alert_config, 'alert_type', ['Default']),
|
286
|
+
"incident_category": self.CASE_TYPE,
|
287
|
+
"threshold_level": config.alert_config.count_thresholds if hasattr(config.alert_config, 'count_thresholds') else {},
|
288
|
+
"ascending": True,
|
289
|
+
"settings": {t: v for t, v in zip(getattr(config.alert_config, 'alert_type', ['Default']),
|
290
|
+
getattr(config.alert_config, 'alert_value', ['JSON']))}
|
291
|
+
})
|
292
|
+
|
293
|
+
event = self.create_incident(
|
294
|
+
incident_id=self.CASE_TYPE + '_' + str(frame_number),
|
295
|
+
incident_type=self.CASE_TYPE,
|
296
|
+
severity_level=level,
|
297
|
+
human_text=human_text,
|
298
|
+
camera_info=camera_info,
|
299
|
+
alerts=alerts,
|
300
|
+
alert_settings=alert_settings,
|
301
|
+
start_time=start_timestamp,
|
302
|
+
end_time=self.current_incident_end_timestamp,
|
303
|
+
level_settings={"low": 1, "medium": 3, "significant": 4, "critical": 7}
|
304
|
+
)
|
305
|
+
incidents.append(event)
|
306
|
+
else:
|
307
|
+
self._ascending_alert_list.append(0)
|
308
|
+
incidents.append({})
|
309
|
+
return incidents
|
310
|
+
|
311
|
+
def _generate_tracking_stats(self, counting_summary: Dict, alerts: List, config: BananaMonitoringConfig,
|
312
|
+
frame_number: Optional[int] = None, stream_info: Optional[Dict[str, Any]] = None) -> List[Dict]:
|
313
|
+
camera_info = self.get_camera_info_from_stream(stream_info)
|
314
|
+
tracking_stats = []
|
315
|
+
total_detections = counting_summary.get("total_count", 0)
|
316
|
+
total_counts_dict = counting_summary.get("total_counts", {})
|
414
317
|
per_category_count = counting_summary.get("per_category_count", {})
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
318
|
+
current_timestamp = self._get_current_timestamp_str(stream_info, precision=False)
|
319
|
+
start_timestamp = self._get_start_timestamp_str(stream_info, precision=False)
|
320
|
+
high_precision_start_timestamp = self._get_current_timestamp_str(stream_info, precision=True)
|
321
|
+
high_precision_reset_timestamp = self._get_start_timestamp_str(stream_info, precision=True)
|
322
|
+
|
323
|
+
total_counts = [{"category": cat, "count": count} for cat, count in total_counts_dict.items() if count > 0]
|
324
|
+
current_counts = [{"category": cat, "count": count} for cat, count in per_category_count.items() if count > 0 or total_detections > 0]
|
325
|
+
|
326
|
+
detections = []
|
327
|
+
for detection in counting_summary.get("detections", []):
|
328
|
+
bbox = detection.get("bounding_box", {})
|
329
|
+
category = detection.get("category", "banana")
|
330
|
+
if detection.get("masks"):
|
331
|
+
segmentation = detection.get("masks", [])
|
332
|
+
detection_obj = self.create_detection_object(category, bbox, segmentation=segmentation)
|
333
|
+
elif detection.get("segmentation"):
|
334
|
+
segmentation = detection.get("segmentation")
|
335
|
+
detection_obj = self.create_detection_object(category, bbox, segmentation=segmentation)
|
336
|
+
elif detection.get("mask"):
|
337
|
+
segmentation = detection.get("mask")
|
338
|
+
detection_obj = self.create_detection_object(category, bbox, segmentation=segmentation)
|
429
339
|
else:
|
430
|
-
|
431
|
-
|
340
|
+
detection_obj = self.create_detection_object(category, bbox)
|
341
|
+
detections.append(detection_obj)
|
342
|
+
|
343
|
+
alert_settings = []
|
344
|
+
if config.alert_config and hasattr(config.alert_config, 'alert_type'):
|
345
|
+
alert_settings.append({
|
346
|
+
"alert_type": getattr(config.alert_config, 'alert_type', ['Default']),
|
347
|
+
"incident_category": self.CASE_TYPE,
|
348
|
+
"threshold_level": config.alert_config.count_thresholds if hasattr(config.alert_config, 'count_thresholds') else {},
|
349
|
+
"ascending": True,
|
350
|
+
"settings": {t: v for t, v in zip(getattr(config.alert_config, 'alert_type', ['Default']),
|
351
|
+
getattr(config.alert_config, 'alert_value', ['JSON']))}
|
352
|
+
})
|
353
|
+
|
354
|
+
human_text_lines = [f"Tracking Statistics:"]
|
355
|
+
human_text_lines.append(f"CURRENT FRAME @ {current_timestamp}")
|
356
|
+
for cat, count in per_category_count.items():
|
357
|
+
human_text_lines.append(f"\t{cat}: {count}")
|
358
|
+
human_text_lines.append(f"TOTAL SINCE {start_timestamp}")
|
359
|
+
for cat, count in total_counts_dict.items():
|
360
|
+
if count > 0:
|
361
|
+
human_text_lines.append(f"\t{cat}: {count}")
|
362
|
+
if alerts:
|
363
|
+
for alert in alerts:
|
364
|
+
human_text_lines.append(f"Alerts: {alert.get('settings', {})} sent @ {current_timestamp}")
|
432
365
|
else:
|
433
|
-
human_text_lines.append(
|
366
|
+
human_text_lines.append("Alerts: None")
|
367
|
+
human_text = "\n".join(human_text_lines)
|
434
368
|
|
435
|
-
|
436
|
-
|
437
|
-
|
438
|
-
|
439
|
-
|
440
|
-
|
441
|
-
|
369
|
+
reset_settings = [{"interval_type": "daily", "reset_time": {"value": 9, "time_unit": "hour"}}]
|
370
|
+
tracking_stat = self.create_tracking_stats(
|
371
|
+
total_counts=total_counts,
|
372
|
+
current_counts=current_counts,
|
373
|
+
detections=detections,
|
374
|
+
human_text=human_text,
|
375
|
+
camera_info=camera_info,
|
376
|
+
alerts=alerts,
|
377
|
+
alert_settings=alert_settings,
|
378
|
+
reset_settings=reset_settings,
|
379
|
+
start_time=high_precision_start_timestamp,
|
380
|
+
reset_time=high_precision_reset_timestamp
|
381
|
+
)
|
382
|
+
tracking_stats.append(tracking_stat)
|
383
|
+
return tracking_stats
|
442
384
|
|
443
|
-
|
385
|
+
def _generate_business_analytics(self, counting_summary: Dict, alerts: Any, config: BananaMonitoringConfig,
|
386
|
+
stream_info: Optional[Dict[str, Any]] = None, is_empty=False) -> List[Dict]:
|
387
|
+
if is_empty:
|
388
|
+
return []
|
389
|
+
|
390
|
+
def _generate_summary(self, summary: dict, incidents: List, tracking_stats: List, business_analytics: List, alerts: List) -> List[Dict]:
|
391
|
+
lines = {}
|
392
|
+
lines["Application Name"] = self.CASE_TYPE
|
393
|
+
lines["Application Version"] = self.CASE_VERSION
|
394
|
+
if len(incidents) > 0:
|
395
|
+
lines["Incidents"] = f"\n\t{incidents[0].get('human_text', 'No incidents detected')}\n"
|
396
|
+
if len(tracking_stats) > 0:
|
397
|
+
lines["Tracking Statistics"] = f"\t{tracking_stats[0].get('human_text', 'No tracking statistics detected')}\n"
|
398
|
+
if len(business_analytics) > 0:
|
399
|
+
lines["Business Analytics"] = f"\t{business_analytics[0].get('human_text', 'No business analytics detected')}\n"
|
400
|
+
if len(incidents) == 0 and len(tracking_stats) == 0 and len(business_analytics) == 0:
|
401
|
+
lines["Summary"] = "No Summary Data"
|
402
|
+
return [lines]
|
444
403
|
|
445
|
-
|
446
|
-
|
447
|
-
|
448
|
-
|
449
|
-
|
450
|
-
|
451
|
-
|
452
|
-
|
453
|
-
|
454
|
-
|
455
|
-
"
|
456
|
-
"
|
457
|
-
|
404
|
+
def _get_track_ids_info(self, detections: list) -> Dict[str, Any]:
|
405
|
+
frame_track_ids = set()
|
406
|
+
for det in detections:
|
407
|
+
tid = det.get('track_id')
|
408
|
+
if tid is not None:
|
409
|
+
frame_track_ids.add(tid)
|
410
|
+
total_track_ids = set()
|
411
|
+
for s in getattr(self, '_per_category_total_track_ids', {}).values():
|
412
|
+
total_track_ids.update(s)
|
413
|
+
return {
|
414
|
+
"total_count": len(total_track_ids),
|
415
|
+
"current_frame_count": len(frame_track_ids),
|
416
|
+
"total_unique_track_ids": len(total_track_ids),
|
417
|
+
"current_frame_track_ids": list(frame_track_ids),
|
418
|
+
"last_update_time": time.time(),
|
419
|
+
"total_frames_processed": getattr(self, '_total_frame_counter', 0)
|
458
420
|
}
|
459
421
|
|
460
|
-
|
461
|
-
|
422
|
+
def _update_tracking_state(self, detections: list):
|
423
|
+
if not hasattr(self, "_per_category_total_track_ids"):
|
424
|
+
self._per_category_total_track_ids = {cat: set() for cat in self.target_categories}
|
425
|
+
self._current_frame_track_ids = {cat: set() for cat in self.target_categories}
|
426
|
+
|
427
|
+
for det in detections:
|
428
|
+
cat = det.get("category")
|
429
|
+
raw_track_id = det.get("track_id")
|
430
|
+
if cat not in self.target_categories or raw_track_id is None:
|
431
|
+
continue
|
432
|
+
bbox = det.get("bounding_box", det.get("bbox"))
|
433
|
+
canonical_id = self._merge_or_register_track(raw_track_id, bbox)
|
434
|
+
det["track_id"] = canonical_id
|
435
|
+
self._per_category_total_track_ids.setdefault(cat, set()).add(canonical_id)
|
436
|
+
self._current_frame_track_ids[cat].add(canonical_id)
|
437
|
+
|
438
|
+
def get_total_counts(self):
|
439
|
+
return {cat: len(ids) for cat, ids in getattr(self, '_per_category_total_track_ids', {}).items()}
|
440
|
+
|
441
|
+
def _format_timestamp_for_stream(self, timestamp: float) -> str:
|
442
|
+
dt = datetime.fromtimestamp(timestamp, tz=timezone.utc)
|
443
|
+
return dt.strftime('%Y:%m:%d %H:%M:%S')
|
444
|
+
|
445
|
+
def _format_timestamp_for_video(self, timestamp: float) -> str:
|
446
|
+
hours = int(timestamp // 3600)
|
447
|
+
minutes = int((timestamp % 3600) // 60)
|
448
|
+
seconds = round(float(timestamp % 60), 2)
|
449
|
+
return f"{hours:02d}:{minutes:02d}:{seconds:.1f}"
|
450
|
+
|
451
|
+
def _get_current_timestamp_str(self, stream_info: Optional[Dict[str, Any]], precision=False, frame_id: Optional[str] = None) -> str:
|
452
|
+
if not stream_info:
|
453
|
+
return "00:00:00.00"
|
454
|
+
if precision:
|
455
|
+
if stream_info.get("input_settings", {}).get("start_frame", "na") != "na":
|
456
|
+
if frame_id:
|
457
|
+
start_time = int(frame_id) / stream_info.get("input_settings", {}).get("original_fps", 30)
|
458
|
+
else:
|
459
|
+
start_time = stream_info.get("input_settings", {}).get("start_frame", 30) / stream_info.get("input_settings", {}).get("original_fps", 30)
|
460
|
+
stream_time_str = self._format_timestamp_for_video(start_time)
|
461
|
+
return stream_time_str
|
462
|
+
else:
|
463
|
+
return datetime.now(timezone.utc).strftime("%Y-%m-%d-%H:%M:%S.%f UTC")
|
464
|
+
if stream_info.get("input_settings", {}).get("start_frame", "na") != "na":
|
465
|
+
if frame_id:
|
466
|
+
start_time = int(frame_id) / stream_info.get("input_settings", {}).get("original_fps", 30)
|
467
|
+
else:
|
468
|
+
start_time = stream_info.get("input_settings", {}).get("start_frame", 30) / stream_info.get("input_settings", {}).get("original_fps", 30)
|
469
|
+
stream_time_str = self._format_timestamp_for_video(start_time)
|
470
|
+
return stream_time_str
|
471
|
+
else:
|
472
|
+
stream_time_str = stream_info.get("input_settings", {}).get("stream_info", {}).get("stream_time", "")
|
473
|
+
if stream_time_str:
|
474
|
+
try:
|
475
|
+
timestamp_str = stream_time_str.replace(" UTC", "")
|
476
|
+
dt = datetime.strptime(timestamp_str, "%Y-%m-%d-%H:%M:%S.%f")
|
477
|
+
timestamp = dt.replace(tzinfo=timezone.utc).timestamp()
|
478
|
+
return self._format_timestamp_for_stream(timestamp)
|
479
|
+
except:
|
480
|
+
return self._format_timestamp_for_stream(time.time())
|
481
|
+
else:
|
482
|
+
return self._format_timestamp_for_stream(time.time())
|
483
|
+
|
484
|
+
def _get_start_timestamp_str(self, stream_info: Optional[Dict[str, Any]], precision=False) -> str:
|
485
|
+
if not stream_info:
|
486
|
+
return "00:00:00"
|
487
|
+
if precision:
|
488
|
+
if stream_info.get("input_settings", {}).get("start_frame", "na") != "na":
|
489
|
+
return "00:00:00"
|
490
|
+
else:
|
491
|
+
return datetime.now(timezone.utc).strftime("%Y-%m-%d-%H:%M:%S.%f UTC")
|
492
|
+
if stream_info.get("input_settings", {}).get("start_frame", "na") != "na":
|
493
|
+
return "00:00:00"
|
494
|
+
else:
|
495
|
+
if self._tracking_start_time is None:
|
496
|
+
stream_time_str = stream_info.get("input_settings", {}).get("stream_info", {}).get("stream_time", "")
|
497
|
+
if stream_time_str:
|
498
|
+
try:
|
499
|
+
timestamp_str = stream_time_str.replace(" UTC", "")
|
500
|
+
dt = datetime.strptime(timestamp_str, "%Y-%m-%d-%H:%M:%S.%f")
|
501
|
+
self._tracking_start_time = dt.replace(tzinfo=timezone.utc).timestamp()
|
502
|
+
except:
|
503
|
+
self._tracking_start_time = time.time()
|
504
|
+
else:
|
505
|
+
self._tracking_start_time = time.time()
|
506
|
+
dt = datetime.fromtimestamp(self._tracking_start_time, tz=timezone.utc)
|
507
|
+
dt = dt.replace(minute=0, second=0, microsecond=0)
|
508
|
+
return dt.strftime('%Y:%m:%d %H:%M:%S')
|
462
509
|
|
463
510
|
def _count_categories(self, detections: list, config: BananaMonitoringConfig) -> dict:
|
464
511
|
counts = {}
|
@@ -480,73 +527,6 @@ class BananaMonitoringUseCase(BaseProcessor):
|
|
480
527
|
]
|
481
528
|
}
|
482
529
|
|
483
|
-
CATEGORY_DISPLAY = {
|
484
|
-
"Fresh Ripe": "freshripe",
|
485
|
-
"Fresh Unripe": "freshunripe",
|
486
|
-
"Overripe": "overripe",
|
487
|
-
"Ripe": "ripe",
|
488
|
-
"Rotten": "rotten",
|
489
|
-
"Unripe": "unripe"
|
490
|
-
}
|
491
|
-
|
492
|
-
def _generate_insights(self, summary: dict, config: BananaMonitoringConfig) -> List[str]:
|
493
|
-
insights = []
|
494
|
-
per_cat = summary.get("per_category_count", {})
|
495
|
-
total_fruits = summary.get("total_count", 0)
|
496
|
-
|
497
|
-
if total_fruits == 0:
|
498
|
-
insights.append("No fruits detected in the scene")
|
499
|
-
return insights
|
500
|
-
insights.append(f"EVENT: Detected {total_fruits} fruits in the scene")
|
501
|
-
intensity_threshold = None
|
502
|
-
if config.alert_config and config.alert_config.count_thresholds and "all" in config.alert_config.count_thresholds:
|
503
|
-
intensity_threshold = config.alert_config.count_thresholds["all"]
|
504
|
-
|
505
|
-
if intensity_threshold is not None:
|
506
|
-
percentage = (total_fruits / intensity_threshold) * 100
|
507
|
-
if percentage < 20:
|
508
|
-
insights.append(f"INTENSITY: Low fruit density in the scene ({percentage:.1f}% of capacity)")
|
509
|
-
elif percentage <= 50:
|
510
|
-
insights.append(f"INTENSITY: Moderate fruit density in the scene ({percentage:.1f}% of capacity)")
|
511
|
-
elif percentage <= 70:
|
512
|
-
insights.append(f"INTENSITY: High fruit density in the scene ({percentage:.1f}% of capacity)")
|
513
|
-
else:
|
514
|
-
insights.append(f"INTENSITY: Severe fruit density in the scene ({percentage:.1f}% of capacity)")
|
515
|
-
|
516
|
-
for cat, count in per_cat.items():
|
517
|
-
display = self.CATEGORY_DISPLAY.get(cat, cat)
|
518
|
-
insights.append(f"{display}: {count}")
|
519
|
-
return insights
|
520
|
-
|
521
|
-
def _check_alerts(self, summary: dict, config: BananaMonitoringConfig) -> List[Dict]:
|
522
|
-
alerts = []
|
523
|
-
if not config.alert_config:
|
524
|
-
return alerts
|
525
|
-
total = summary.get("total_count", 0)
|
526
|
-
if config.alert_config.count_thresholds:
|
527
|
-
for category, threshold in config.alert_config.count_thresholds.items():
|
528
|
-
if category == "all" and total >= threshold:
|
529
|
-
alerts.append({
|
530
|
-
"type": "count_threshold",
|
531
|
-
"severity": "warning",
|
532
|
-
"message": f"Total fruit count ({total}) exceeds threshold ({threshold})",
|
533
|
-
"category": category,
|
534
|
-
"current_count": total,
|
535
|
-
"threshold": threshold
|
536
|
-
})
|
537
|
-
elif category in summary.get("per_category_count", {}):
|
538
|
-
count = summary.get("per_category_count", {})[category]
|
539
|
-
if count >= threshold:
|
540
|
-
alerts.append({
|
541
|
-
"type": "count_threshold",
|
542
|
-
"severity": "warning",
|
543
|
-
"message": f"{category} count ({count}) exceeds threshold ({threshold})",
|
544
|
-
"category": category,
|
545
|
-
"current_count": count,
|
546
|
-
"threshold": threshold
|
547
|
-
})
|
548
|
-
return alerts
|
549
|
-
|
550
530
|
def _extract_predictions(self, detections: list) -> List[Dict[str, Any]]:
|
551
531
|
return [
|
552
532
|
{
|
@@ -557,25 +537,6 @@ class BananaMonitoringUseCase(BaseProcessor):
|
|
557
537
|
for det in detections
|
558
538
|
]
|
559
539
|
|
560
|
-
def _generate_summary(self, summary: dict, alerts: List) -> str:
|
561
|
-
total = summary.get("total_count", 0)
|
562
|
-
per_cat = summary.get("per_category_count", {})
|
563
|
-
cumulative = summary.get("total_fruit_counts", {})
|
564
|
-
cumulative_total = sum(cumulative.values()) if cumulative else 0
|
565
|
-
lines = []
|
566
|
-
if total > 0:
|
567
|
-
lines.append(f"{total} Fruit(s) detected")
|
568
|
-
if per_cat:
|
569
|
-
lines.append("Fruits:")
|
570
|
-
for cat, count in per_cat.items():
|
571
|
-
lines.append(f"\t{cat}: {count}")
|
572
|
-
else:
|
573
|
-
lines.append("No fruit detected")
|
574
|
-
lines.append(f"Total fruits detected: {cumulative_total}")
|
575
|
-
if alerts:
|
576
|
-
lines.append(f"{len(alerts)} alert(s)")
|
577
|
-
return "\n".join(lines)
|
578
|
-
|
579
540
|
def _compute_iou(self, box1: Any, box2: Any) -> float:
|
580
541
|
def _bbox_to_list(bbox):
|
581
542
|
if bbox is None:
|
@@ -597,20 +558,25 @@ class BananaMonitoringUseCase(BaseProcessor):
|
|
597
558
|
return 0.0
|
598
559
|
x1_min, y1_min, x1_max, y1_max = l1
|
599
560
|
x2_min, y2_min, x2_max, y2_max = l2
|
561
|
+
|
600
562
|
x1_min, x1_max = min(x1_min, x1_max), max(x1_min, x1_max)
|
601
563
|
y1_min, y1_max = min(y1_min, y1_max), max(y1_min, y1_max)
|
602
564
|
x2_min, x2_max = min(x2_min, x2_max), max(x2_min, x2_max)
|
603
565
|
y2_min, y2_max = min(y2_min, y2_max), max(y2_min, y2_max)
|
566
|
+
|
604
567
|
inter_x_min = max(x1_min, x2_min)
|
605
568
|
inter_y_min = max(y1_min, y2_min)
|
606
569
|
inter_x_max = min(x1_max, x2_max)
|
607
570
|
inter_y_max = min(y1_max, y2_max)
|
571
|
+
|
608
572
|
inter_w = max(0.0, inter_x_max - inter_x_min)
|
609
573
|
inter_h = max(0.0, inter_y_max - inter_y_min)
|
610
574
|
inter_area = inter_w * inter_h
|
575
|
+
|
611
576
|
area1 = (x1_max - x1_min) * (y1_max - y1_min)
|
612
577
|
area2 = (x2_max - x2_min) * (y2_max - y2_min)
|
613
578
|
union_area = area1 + area2 - inter_area
|
579
|
+
|
614
580
|
return (inter_area / union_area) if union_area > 0 else 0.0
|
615
581
|
|
616
582
|
def _merge_or_register_track(self, raw_id: Any, bbox: Any) -> Any:
|
@@ -625,6 +591,7 @@ class BananaMonitoringUseCase(BaseProcessor):
|
|
625
591
|
track_info["last_update"] = now
|
626
592
|
track_info["raw_ids"].add(raw_id)
|
627
593
|
return canonical_id
|
594
|
+
|
628
595
|
for canonical_id, info in self._canonical_tracks.items():
|
629
596
|
if now - info["last_update"] > self._track_merge_time_window:
|
630
597
|
continue
|
@@ -635,6 +602,7 @@ class BananaMonitoringUseCase(BaseProcessor):
|
|
635
602
|
info["last_update"] = now
|
636
603
|
info["raw_ids"].add(raw_id)
|
637
604
|
return canonical_id
|
605
|
+
|
638
606
|
canonical_id = raw_id
|
639
607
|
self._track_aliases[raw_id] = canonical_id
|
640
608
|
self._canonical_tracks[canonical_id] = {
|
@@ -127,7 +127,7 @@ matrice/deploy/utils/boundary_drawing_internal/boundary_drawing_internal.py,sha2
|
|
127
127
|
matrice/deploy/utils/boundary_drawing_internal/boundary_drawing_tool.py,sha256=eY0VQGZ8BfTmR4_ThIAXaumBjh8_c7w69w-d3kta8p0,15421
|
128
128
|
matrice/deploy/utils/boundary_drawing_internal/example_usage.py,sha256=cUBhxxsVdTQWIPvIOjCUGrhqon7ZBr5N6qNewjrTIuk,6434
|
129
129
|
matrice/deploy/utils/post_processing/__init__.py,sha256=QaVlnp8KYxWlFabtX4dcZzGHcNL_Wf1E1usnd67sCKY,22862
|
130
|
-
matrice/deploy/utils/post_processing/config.py,sha256=
|
130
|
+
matrice/deploy/utils/post_processing/config.py,sha256=zSBW2KE2iXyJhhkBXrIy-5eWg9ZqGl9uuWkIvcuovCM,2874
|
131
131
|
matrice/deploy/utils/post_processing/processor.py,sha256=_tRjlZH-HEAwT0bHDCcXkbP6Hmx0m8DD1NMmYh4xMs0,30414
|
132
132
|
matrice/deploy/utils/post_processing/advanced_tracker/__init__.py,sha256=tAPFzI_Yep5TLX60FDwKqBqppc-EbxSr0wNsQ9DGI1o,423
|
133
133
|
matrice/deploy/utils/post_processing/advanced_tracker/base.py,sha256=VqWy4dd5th5LK-JfueTt2_GSEoOi5QQfQxjTNhmQoLc,3580
|
@@ -157,7 +157,7 @@ matrice/deploy/utils/post_processing/usecases/advanced_customer_service.py,sha25
|
|
157
157
|
matrice/deploy/utils/post_processing/usecases/age_detection.py,sha256=cgOIx2zqPd_OWQMfTDV_4HSlvXlI8eKcZSEZ85SnZWA,35263
|
158
158
|
matrice/deploy/utils/post_processing/usecases/anti_spoofing_detection.py,sha256=XdtDdXGzZMLQdWcoOoiE5t4LPYHhgOtJ7tZCNlq1A2E,31329
|
159
159
|
matrice/deploy/utils/post_processing/usecases/assembly_line_detection.py,sha256=I_oeuec84KJnGMg_A8Wgs9U6h_IiopkDz9FbM1JG110,40410
|
160
|
-
matrice/deploy/utils/post_processing/usecases/banana_defect_detection.py,sha256=
|
160
|
+
matrice/deploy/utils/post_processing/usecases/banana_defect_detection.py,sha256=uKvB550Xve3iWfdWeOU3VhgORxD-M_UzsQDQKkYcFBc,30338
|
161
161
|
matrice/deploy/utils/post_processing/usecases/basic_counting_tracking.py,sha256=-vr2z0J-qMh5wOoGubqeTWPttJ4NOYtGqKSV-_8PaKw,28311
|
162
162
|
matrice/deploy/utils/post_processing/usecases/blood_cancer_detection_img.py,sha256=Ay_0OC9gM1dTQHWC5wL8XLZjl7ulHzd0zdhNzGH4JKU,42128
|
163
163
|
matrice/deploy/utils/post_processing/usecases/car_damage_detection.py,sha256=WvD7M90DrJ9serjp4mOkZiCDwSXUyJNv6OX1AjlXs6s,34505
|
@@ -225,8 +225,8 @@ matrice/deployment/camera_manager.py,sha256=ReBZqm1CNXRImKcbcZ4uWAT3TUWkof1D28oB
|
|
225
225
|
matrice/deployment/deployment.py,sha256=PLIUD-PxTaC2Zxb3Y12wUddsryV-OJetjCjLoSUh7S4,48103
|
226
226
|
matrice/deployment/inference_pipeline.py,sha256=bXLgd29ViA7o0c7YWLFJl1otBUQfTPb61jS6VawQB0Y,37918
|
227
227
|
matrice/deployment/streaming_gateway_manager.py,sha256=w5swGsuFVfZIdOm2ZuBHRHlRdYYJMLopLsf2gb91lQ8,20946
|
228
|
-
matrice-1.0.
|
229
|
-
matrice-1.0.
|
230
|
-
matrice-1.0.
|
231
|
-
matrice-1.0.
|
232
|
-
matrice-1.0.
|
228
|
+
matrice-1.0.99128.dist-info/licenses/LICENSE.txt,sha256=2bm9uFabQZ3Ykb_SaSU_uUbAj2-htc6WJQmS_65qD00,1073
|
229
|
+
matrice-1.0.99128.dist-info/METADATA,sha256=gXAqcRNMLWJ3W-9UIIn1jK-Fmj9OydTdwUCGVbqxjAo,14624
|
230
|
+
matrice-1.0.99128.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
231
|
+
matrice-1.0.99128.dist-info/top_level.txt,sha256=P97js8ur6o5ClRqMH3Cjoab_NqbJ6sOQ3rJmVzKBvMc,8
|
232
|
+
matrice-1.0.99128.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|