mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-10-31 10:50:32 +03:00
fixes
This commit is contained in:
+2
-2
@@ -72,7 +72,7 @@
|
|||||||
"x-public": true
|
"x-public": true
|
||||||
},
|
},
|
||||||
"audio_transcription": {
|
"audio_transcription": {
|
||||||
"description": "Cameras with audio transcription on",
|
"description": "Cameras with audio detection and transcription on",
|
||||||
"minimum": 0,
|
"minimum": 0,
|
||||||
"title": "Audio Transcription",
|
"title": "Audio Transcription",
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
@@ -143,7 +143,7 @@
|
|||||||
"x-public": true
|
"x-public": true
|
||||||
},
|
},
|
||||||
"enabled": {
|
"enabled": {
|
||||||
"description": "Enabled cameras",
|
"description": "Cameras enabled in config; every count below covers only these",
|
||||||
"minimum": 0,
|
"minimum": 0,
|
||||||
"title": "Enabled",
|
"title": "Enabled",
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
|
|||||||
@@ -108,6 +108,11 @@ def collect(ctx: ReportContext) -> CamerasSection:
|
|||||||
}
|
}
|
||||||
camera_stats = ctx.stats.get("cameras", {})
|
camera_stats = ctx.stats.get("cameras", {})
|
||||||
|
|
||||||
|
# cameras disabled in config are parked, so they'd skew feature adoption
|
||||||
|
active = [
|
||||||
|
(name, camera) for name, camera in cameras.items() if camera.enabled_in_config
|
||||||
|
]
|
||||||
|
|
||||||
flags: Counter[str] = Counter()
|
flags: Counter[str] = Counter()
|
||||||
types: Counter[CameraTypeEnum] = Counter()
|
types: Counter[CameraTypeEnum] = Counter()
|
||||||
heights: Counter[HeightBucket] = Counter()
|
heights: Counter[HeightBucket] = Counter()
|
||||||
@@ -119,7 +124,7 @@ def collect(ctx: ReportContext) -> CamerasSection:
|
|||||||
period: Counter() for period in ("continuous", "motion", "alerts", "detections")
|
period: Counter() for period in ("continuous", "motion", "alerts", "detections")
|
||||||
}
|
}
|
||||||
|
|
||||||
for name, camera in cameras.items():
|
for name, camera in active:
|
||||||
detect_input = role_input(camera, CameraRoleEnum.detect)
|
detect_input = role_input(camera, CameraRoleEnum.detect)
|
||||||
record_input = role_input(camera, CameraRoleEnum.record)
|
record_input = role_input(camera, CameraRoleEnum.record)
|
||||||
|
|
||||||
@@ -145,7 +150,8 @@ def collect(ctx: ReportContext) -> CamerasSection:
|
|||||||
|
|
||||||
state = camera_stats.get(name, {}).get("connection_quality")
|
state = camera_stats.get(name, {}).get("connection_quality")
|
||||||
|
|
||||||
if state in QUALITIES:
|
# a camera turned off at runtime reports unusable
|
||||||
|
if camera.enabled and state in QUALITIES:
|
||||||
quality[ConnectionQuality(state)] += 1
|
quality[ConnectionQuality(state)] += 1
|
||||||
|
|
||||||
if camera.record.enabled:
|
if camera.record.enabled:
|
||||||
@@ -157,7 +163,6 @@ def collect(ctx: ReportContext) -> CamerasSection:
|
|||||||
] += 1
|
] += 1
|
||||||
|
|
||||||
zones = len(camera.zones)
|
zones = len(camera.zones)
|
||||||
flags["enabled"] += camera.enabled
|
|
||||||
flags["go2rtc_restream"] += any(
|
flags["go2rtc_restream"] += any(
|
||||||
is_restream(i.path) for i in camera.ffmpeg.inputs
|
is_restream(i.path) for i in camera.ffmpeg.inputs
|
||||||
)
|
)
|
||||||
@@ -169,7 +174,9 @@ def collect(ctx: ReportContext) -> CamerasSection:
|
|||||||
flags["sub_stream_record"] += camera.record.sub.enabled
|
flags["sub_stream_record"] += camera.record.sub.enabled
|
||||||
flags["snapshots"] += camera.snapshots.enabled
|
flags["snapshots"] += camera.snapshots.enabled
|
||||||
flags["audio"] += camera.audio.enabled
|
flags["audio"] += camera.audio.enabled
|
||||||
flags["audio_transcription"] += camera.audio_transcription.enabled
|
flags["audio_transcription"] += (
|
||||||
|
camera.audio.enabled and camera.audio_transcription.enabled
|
||||||
|
)
|
||||||
flags["birdseye"] += camera.birdseye.enabled
|
flags["birdseye"] += camera.birdseye.enabled
|
||||||
flags["onvif"] += bool(camera.onvif.host)
|
flags["onvif"] += bool(camera.onvif.host)
|
||||||
flags["autotracking"] += camera.onvif.autotracking.enabled
|
flags["autotracking"] += camera.onvif.autotracking.enabled
|
||||||
@@ -187,7 +194,7 @@ def collect(ctx: ReportContext) -> CamerasSection:
|
|||||||
|
|
||||||
return CamerasSection(
|
return CamerasSection(
|
||||||
total=len(cameras),
|
total=len(cameras),
|
||||||
enabled=flags["enabled"],
|
enabled=len(active),
|
||||||
types=histogram(types),
|
types=histogram(types),
|
||||||
detect_height=histogram(heights),
|
detect_height=histogram(heights),
|
||||||
detect_fps=histogram(fps),
|
detect_fps=histogram(fps),
|
||||||
|
|||||||
@@ -42,7 +42,8 @@ def enrichment_device(label: object) -> EnrichmentDevice | None:
|
|||||||
runtime, _, target = label.partition(" ")
|
runtime, _, target = label.partition(" ")
|
||||||
|
|
||||||
if runtime == "OpenVINO":
|
if runtime == "OpenVINO":
|
||||||
first = target.split(",")[0].split(".")[0].strip().lower()
|
# AUTO reports "(CPU)" while it warms up on the CPU
|
||||||
|
first = target.split(",")[0].split(".")[0].strip("() ").lower()
|
||||||
return OPENVINO_DEVICES.get(first, EnrichmentDevice.other)
|
return OPENVINO_DEVICES.get(first, EnrichmentDevice.other)
|
||||||
|
|
||||||
return RUNTIME_DEVICES.get(label.lower(), EnrichmentDevice.other)
|
return RUNTIME_DEVICES.get(label.lower(), EnrichmentDevice.other)
|
||||||
|
|||||||
@@ -267,7 +267,9 @@ class RetainDays(AnalyticsModel):
|
|||||||
|
|
||||||
class CamerasSection(AnalyticsModel):
|
class CamerasSection(AnalyticsModel):
|
||||||
total: int = count("Configured cameras")
|
total: int = count("Configured cameras")
|
||||||
enabled: int = count("Enabled cameras")
|
enabled: int = count(
|
||||||
|
"Cameras enabled in config; every count below covers only these"
|
||||||
|
)
|
||||||
types: dict[CameraTypeEnum, int] = metric("Cameras by type")
|
types: dict[CameraTypeEnum, int] = metric("Cameras by type")
|
||||||
detect_height: dict[HeightBucket, int] = metric(
|
detect_height: dict[HeightBucket, int] = metric(
|
||||||
"Cameras by detect resolution height"
|
"Cameras by detect resolution height"
|
||||||
@@ -288,7 +290,9 @@ class CamerasSection(AnalyticsModel):
|
|||||||
sub_stream_record: int = count("Cameras with sub stream recording on")
|
sub_stream_record: int = count("Cameras with sub stream recording on")
|
||||||
snapshots: int = count("Cameras with snapshots on")
|
snapshots: int = count("Cameras with snapshots on")
|
||||||
audio: int = count("Cameras with audio detection on")
|
audio: int = count("Cameras with audio detection on")
|
||||||
audio_transcription: int = count("Cameras with audio transcription on")
|
audio_transcription: int = count(
|
||||||
|
"Cameras with audio detection and transcription on"
|
||||||
|
)
|
||||||
birdseye: int = count("Cameras in birdseye")
|
birdseye: int = count("Cameras in birdseye")
|
||||||
onvif: int = count("Cameras with an ONVIF host")
|
onvif: int = count("Cameras with an ONVIF host")
|
||||||
autotracking: int = count("Cameras with PTZ autotracking on")
|
autotracking: int = count("Cameras with PTZ autotracking on")
|
||||||
|
|||||||
@@ -328,7 +328,8 @@ class ObjectDetectProcess:
|
|||||||
self.name = name
|
self.name = name
|
||||||
self.cameras = cameras
|
self.cameras = cameras
|
||||||
self.detection_queue = detection_queue
|
self.detection_queue = detection_queue
|
||||||
self.avg_inference_speed = Value("d", 0.01)
|
# stays 0 until the first inference, so stats can tell an idle detector
|
||||||
|
self.avg_inference_speed = Value("d", 0.0)
|
||||||
self.detection_start = Value("d", 0.0)
|
self.detection_start = Value("d", 0.0)
|
||||||
self.detect_process: FrigateProcess | None = None
|
self.detect_process: FrigateProcess | None = None
|
||||||
self.config = config
|
self.config = config
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ from frigate.test.analytics_helpers import make_config, make_context
|
|||||||
MASK = {"coordinates": "0,0,1,0,1,1"}
|
MASK = {"coordinates": "0,0,1,0,1,1"}
|
||||||
|
|
||||||
CONFIG = {
|
CONFIG = {
|
||||||
|
"audio_transcription": {"enabled": True},
|
||||||
"record": {
|
"record": {
|
||||||
"enabled": True,
|
"enabled": True,
|
||||||
"continuous": {"days": 3},
|
"continuous": {"days": 3},
|
||||||
@@ -38,17 +39,27 @@ CONFIG = {
|
|||||||
"onvif": {"host": "10.0.0.9"},
|
"onvif": {"host": "10.0.0.9"},
|
||||||
},
|
},
|
||||||
"garage": {
|
"garage": {
|
||||||
"enabled": False,
|
|
||||||
"type": "lpr",
|
"type": "lpr",
|
||||||
"ffmpeg": {
|
"ffmpeg": {
|
||||||
"hwaccel_args": ["-hwaccel", "vaapi"],
|
"hwaccel_args": ["-hwaccel", "vaapi"],
|
||||||
"inputs": [
|
"inputs": [
|
||||||
{"path": "rtsp://10.0.0.5/stream", "roles": ["detect", "record"]}
|
{
|
||||||
|
"path": "rtsp://10.0.0.5/stream",
|
||||||
|
"roles": ["detect", "record", "audio"],
|
||||||
|
}
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
"audio": {"enabled": True},
|
||||||
"detect": {"height": 2160, "width": 3840, "fps": 15},
|
"detect": {"height": 2160, "width": 3840, "fps": 15},
|
||||||
"record": {"enabled": False},
|
"record": {"enabled": False},
|
||||||
},
|
},
|
||||||
|
"attic": {
|
||||||
|
"enabled": False,
|
||||||
|
"ffmpeg": {
|
||||||
|
"inputs": [{"path": "rtsp://10.0.0.6/stream", "roles": ["detect"]}]
|
||||||
|
},
|
||||||
|
"detect": {"enabled": True, "height": 480, "width": 640, "fps": 5},
|
||||||
|
},
|
||||||
"_replay_front": {
|
"_replay_front": {
|
||||||
"ffmpeg": {
|
"ffmpeg": {
|
||||||
"inputs": [{"path": "rtsp://10.0.0.1/replay", "roles": ["detect"]}]
|
"inputs": [{"path": "rtsp://10.0.0.1/replay", "roles": ["detect"]}]
|
||||||
@@ -62,16 +73,17 @@ STATS = {
|
|||||||
"cameras": {
|
"cameras": {
|
||||||
"front": {"connection_quality": "excellent"},
|
"front": {"connection_quality": "excellent"},
|
||||||
"garage": {"connection_quality": "offline"},
|
"garage": {"connection_quality": "offline"},
|
||||||
|
"attic": {"connection_quality": "unusable"},
|
||||||
"_replay_front": {"connection_quality": "fair"},
|
"_replay_front": {"connection_quality": "fair"},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class TestCamerasCollector(unittest.TestCase):
|
class TestCamerasCollector(unittest.TestCase):
|
||||||
def test_aggregates_cameras_and_skips_replays(self):
|
def test_aggregates_enabled_cameras_and_skips_replays(self):
|
||||||
section = cameras.collect(make_context(make_config(CONFIG), STATS))
|
section = cameras.collect(make_context(make_config(CONFIG), STATS))
|
||||||
|
|
||||||
self.assertEqual((section.total, section.enabled), (2, 1))
|
self.assertEqual((section.total, section.enabled), (3, 2))
|
||||||
self.assertEqual(section.types, {"generic": 1, "lpr": 1})
|
self.assertEqual(section.types, {"generic": 1, "lpr": 1})
|
||||||
self.assertEqual(section.detect_height, {"720": 1, "ge_2160": 1})
|
self.assertEqual(section.detect_height, {"720": 1, "ge_2160": 1})
|
||||||
self.assertEqual(section.detect_fps, {"le_5": 1, "gt_10": 1})
|
self.assertEqual(section.detect_fps, {"le_5": 1, "gt_10": 1})
|
||||||
@@ -82,7 +94,15 @@ class TestCamerasCollector(unittest.TestCase):
|
|||||||
self.assertEqual((section.detect, section.record, section.onvif), (1, 1, 1))
|
self.assertEqual((section.detect, section.record, section.onvif), (1, 1, 1))
|
||||||
self.assertEqual((section.zones, section.cameras_with_zones), (2, 1))
|
self.assertEqual((section.zones, section.cameras_with_zones), (2, 1))
|
||||||
self.assertEqual((section.motion_masks, section.object_masks), (1, 2))
|
self.assertEqual((section.motion_masks, section.object_masks), (1, 2))
|
||||||
|
self.assertEqual((section.audio, section.audio_transcription), (1, 1))
|
||||||
self.assertEqual(section.connection_quality, {"excellent": 1})
|
self.assertEqual(section.connection_quality, {"excellent": 1})
|
||||||
|
|
||||||
|
def test_runtime_disabled_camera_skips_connection_quality(self):
|
||||||
|
config = make_config(CONFIG)
|
||||||
|
config.cameras["front"].enabled = False
|
||||||
|
section = cameras.collect(make_context(config, STATS))
|
||||||
|
|
||||||
|
self.assertEqual(section.connection_quality, {})
|
||||||
self.assertEqual(section.retain_days.continuous, {"1_7": 1})
|
self.assertEqual(section.retain_days.continuous, {"1_7": 1})
|
||||||
self.assertEqual(section.retain_days.motion, {"0": 1})
|
self.assertEqual(section.retain_days.motion, {"0": 1})
|
||||||
self.assertEqual(section.retain_days.alerts, {"8_30": 1})
|
self.assertEqual(section.retain_days.alerts, {"8_30": 1})
|
||||||
|
|||||||
@@ -35,6 +35,13 @@ class TestDetectionCollector(unittest.TestCase):
|
|||||||
self.assertEqual(section.detection_fps, 12.35)
|
self.assertEqual(section.detection_fps, 12.35)
|
||||||
self.assertEqual(section.skipped_fps, 0.0)
|
self.assertEqual(section.skipped_fps, 0.0)
|
||||||
|
|
||||||
|
def test_idle_detector_reports_no_speed(self):
|
||||||
|
config = make_config({"models": [{"devices": ["cpu"]}]})
|
||||||
|
stats = {"detectors": {"cpu": {"inference_speed": 0.0}}}
|
||||||
|
|
||||||
|
[model] = detection.collect(make_context(config, stats)).models
|
||||||
|
self.assertIsNone(model.inference_ms)
|
||||||
|
|
||||||
def test_named_scene_reports_no_name(self):
|
def test_named_scene_reports_no_name(self):
|
||||||
config = make_config(
|
config = make_config(
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -109,6 +109,7 @@ class TestFeatureMappings(unittest.TestCase):
|
|||||||
"MIGraphX": EnrichmentDevice.migraphx,
|
"MIGraphX": EnrichmentDevice.migraphx,
|
||||||
"OpenVINO NPU": EnrichmentDevice.openvino_npu,
|
"OpenVINO NPU": EnrichmentDevice.openvino_npu,
|
||||||
"OpenVINO GPU.0,CPU": EnrichmentDevice.openvino_gpu,
|
"OpenVINO GPU.0,CPU": EnrichmentDevice.openvino_gpu,
|
||||||
|
"OpenVINO (CPU)": EnrichmentDevice.openvino_cpu,
|
||||||
"OpenVINO": EnrichmentDevice.other,
|
"OpenVINO": EnrichmentDevice.other,
|
||||||
"CoreML": EnrichmentDevice.other,
|
"CoreML": EnrichmentDevice.other,
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user