mirror of
				https://github.com/blakeblackshear/frigate.git
				synced 2025-10-27 10:52:11 +01:00 
			
		
		
		
	Add region count to database and use for motion activity (#10480)
* Add region count to database and use for motion activity * Fix test
This commit is contained in:
		
							parent
							
								
									c93b186eda
								
							
						
					
					
						commit
						93260f6cfd
					
				@ -358,7 +358,6 @@ def motion_activity():
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    clauses = [(Recordings.start_time > after) & (Recordings.end_time < before)]
 | 
			
		||||
    clauses.append((Recordings.motion <= 100))
 | 
			
		||||
 | 
			
		||||
    if cameras != "all":
 | 
			
		||||
        camera_list = cameras.split(",")
 | 
			
		||||
@ -367,7 +366,7 @@ def motion_activity():
 | 
			
		||||
    data: list[Recordings] = (
 | 
			
		||||
        Recordings.select(
 | 
			
		||||
            Recordings.start_time,
 | 
			
		||||
            Recordings.motion,
 | 
			
		||||
            Recordings.regions,
 | 
			
		||||
        )
 | 
			
		||||
        .where(reduce(operator.and_, clauses))
 | 
			
		||||
        .order_by(Recordings.start_time.asc())
 | 
			
		||||
@ -379,7 +378,8 @@ def motion_activity():
 | 
			
		||||
    scale = request.args.get("scale", type=int, default=30)
 | 
			
		||||
 | 
			
		||||
    # resample data using pandas to get activity on scaled basis
 | 
			
		||||
    df = pd.DataFrame(data, columns=["start_time", "motion"])
 | 
			
		||||
    df = pd.DataFrame(data, columns=["start_time", "regions"])
 | 
			
		||||
    df = df.rename(columns={"regions": "motion"})
 | 
			
		||||
 | 
			
		||||
    # set date as datetime index
 | 
			
		||||
    df["start_time"] = pd.to_datetime(df["start_time"], unit="s")
 | 
			
		||||
@ -391,6 +391,11 @@ def motion_activity():
 | 
			
		||||
        .apply(lambda x: max(x, key=abs, default=0.0))
 | 
			
		||||
        .fillna(0.0)
 | 
			
		||||
    )
 | 
			
		||||
    df["motion"] = (
 | 
			
		||||
        (df["motion"] - df["motion"].min())
 | 
			
		||||
        / (df["motion"].max() - df["motion"].min())
 | 
			
		||||
        * 100
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    # change types for output
 | 
			
		||||
    df.index = df.index.astype(int) // (10**9)
 | 
			
		||||
 | 
			
		||||
@ -74,6 +74,7 @@ class Recordings(Model):  # type: ignore[misc]
 | 
			
		||||
    objects = IntegerField(null=True)
 | 
			
		||||
    dBFS = IntegerField(null=True)
 | 
			
		||||
    segment_size = FloatField(default=0)  # this should be stored as MB
 | 
			
		||||
    regions = IntegerField(null=True)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ReviewSegment(Model):  # type: ignore[misc]
 | 
			
		||||
 | 
			
		||||
@ -38,10 +38,15 @@ QUEUE_READ_TIMEOUT = 0.00001  # seconds
 | 
			
		||||
 | 
			
		||||
class SegmentInfo:
 | 
			
		||||
    def __init__(
 | 
			
		||||
        self, motion_area: int, active_object_count: int, average_dBFS: int
 | 
			
		||||
        self,
 | 
			
		||||
        motion_area: int,
 | 
			
		||||
        active_object_count: int,
 | 
			
		||||
        region_count: int,
 | 
			
		||||
        average_dBFS: int,
 | 
			
		||||
    ) -> None:
 | 
			
		||||
        self.motion_area = motion_area
 | 
			
		||||
        self.active_object_count = active_object_count
 | 
			
		||||
        self.region_count = region_count
 | 
			
		||||
        self.average_dBFS = average_dBFS
 | 
			
		||||
 | 
			
		||||
    def should_discard_segment(self, retain_mode: RetainModeEnum) -> bool:
 | 
			
		||||
@ -298,6 +303,7 @@ class RecordingMaintainer(threading.Thread):
 | 
			
		||||
    ) -> SegmentInfo:
 | 
			
		||||
        video_frame_count = 0
 | 
			
		||||
        active_count = 0
 | 
			
		||||
        region_count = 0
 | 
			
		||||
        total_motion_area = 0
 | 
			
		||||
        for frame in self.object_recordings_info[camera]:
 | 
			
		||||
            # frame is after end time of segment
 | 
			
		||||
@ -315,8 +321,8 @@ class RecordingMaintainer(threading.Thread):
 | 
			
		||||
                    if not o["false_positive"] and o["motionless_count"] == 0
 | 
			
		||||
                ]
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
            total_motion_area += sum([area(box) for box in frame[2]])
 | 
			
		||||
            region_count += len(frame[3])
 | 
			
		||||
 | 
			
		||||
        if video_frame_count > 0:
 | 
			
		||||
            normalized_motion_area = min(
 | 
			
		||||
@ -350,7 +356,9 @@ class RecordingMaintainer(threading.Thread):
 | 
			
		||||
 | 
			
		||||
        average_dBFS = 0 if not audio_values else np.average(audio_values)
 | 
			
		||||
 | 
			
		||||
        return SegmentInfo(normalized_motion_area, active_count, round(average_dBFS))
 | 
			
		||||
        return SegmentInfo(
 | 
			
		||||
            normalized_motion_area, active_count, region_count, round(average_dBFS)
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    async def move_segment(
 | 
			
		||||
        self,
 | 
			
		||||
@ -438,6 +446,7 @@ class RecordingMaintainer(threading.Thread):
 | 
			
		||||
                    Recordings.motion: segment_info.motion_area,
 | 
			
		||||
                    # TODO: update this to store list of active objects at some point
 | 
			
		||||
                    Recordings.objects: segment_info.active_object_count,
 | 
			
		||||
                    Recordings.regions: segment_info.region_count,
 | 
			
		||||
                    Recordings.dBFS: segment_info.average_dBFS,
 | 
			
		||||
                    Recordings.segment_size: segment_size,
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
@ -6,20 +6,28 @@ from frigate.record.maintainer import SegmentInfo
 | 
			
		||||
 | 
			
		||||
class TestRecordRetention(unittest.TestCase):
 | 
			
		||||
    def test_motion_should_keep_motion_not_object(self):
 | 
			
		||||
        segment_info = SegmentInfo(motion_area=1, active_object_count=0, average_dBFS=0)
 | 
			
		||||
        segment_info = SegmentInfo(
 | 
			
		||||
            motion_area=1, active_object_count=0, region_count=0, average_dBFS=0
 | 
			
		||||
        )
 | 
			
		||||
        assert not segment_info.should_discard_segment(RetainModeEnum.motion)
 | 
			
		||||
        assert segment_info.should_discard_segment(RetainModeEnum.active_objects)
 | 
			
		||||
 | 
			
		||||
    def test_object_should_keep_object_not_motion(self):
 | 
			
		||||
        segment_info = SegmentInfo(motion_area=0, active_object_count=1, average_dBFS=0)
 | 
			
		||||
        segment_info = SegmentInfo(
 | 
			
		||||
            motion_area=0, active_object_count=1, region_count=0, average_dBFS=0
 | 
			
		||||
        )
 | 
			
		||||
        assert segment_info.should_discard_segment(RetainModeEnum.motion)
 | 
			
		||||
        assert not segment_info.should_discard_segment(RetainModeEnum.active_objects)
 | 
			
		||||
 | 
			
		||||
    def test_all_should_keep_all(self):
 | 
			
		||||
        segment_info = SegmentInfo(motion_area=0, active_object_count=0, average_dBFS=0)
 | 
			
		||||
        segment_info = SegmentInfo(
 | 
			
		||||
            motion_area=0, active_object_count=0, region_count=0, average_dBFS=0
 | 
			
		||||
        )
 | 
			
		||||
        assert not segment_info.should_discard_segment(RetainModeEnum.all)
 | 
			
		||||
 | 
			
		||||
    def test_should_keep_audio_in_motion_mode(self):
 | 
			
		||||
        segment_info = SegmentInfo(motion_area=0, active_object_count=0, average_dBFS=1)
 | 
			
		||||
        segment_info = SegmentInfo(
 | 
			
		||||
            motion_area=0, active_object_count=0, region_count=0, average_dBFS=1
 | 
			
		||||
        )
 | 
			
		||||
        assert not segment_info.should_discard_segment(RetainModeEnum.motion)
 | 
			
		||||
        assert segment_info.should_discard_segment(RetainModeEnum.active_objects)
 | 
			
		||||
 | 
			
		||||
@ -34,6 +34,9 @@ def migrate(migrator, database, fake=False, **kwargs):
 | 
			
		||||
        objects=pw.IntegerField(null=True),
 | 
			
		||||
        motion=pw.IntegerField(null=True),
 | 
			
		||||
    )
 | 
			
		||||
    migrator.sql(
 | 
			
		||||
        'CREATE INDEX "recordings_activity" ON "recordings" ("camera", "start_time" DESC, "regions")'
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def rollback(migrator, database, fake=False, **kwargs):
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										39
									
								
								migrations/023_add_regions.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								migrations/023_add_regions.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,39 @@
 | 
			
		||||
"""Peewee migrations -- 023_add_regions.py.
 | 
			
		||||
 | 
			
		||||
Some examples (model - class or model name)::
 | 
			
		||||
 | 
			
		||||
    > Model = migrator.orm['model_name']            # Return model in current state by name
 | 
			
		||||
 | 
			
		||||
    > migrator.sql(sql)                             # Run custom SQL
 | 
			
		||||
    > migrator.python(func, *args, **kwargs)        # Run python code
 | 
			
		||||
    > migrator.create_model(Model)                  # Create a model (could be used as decorator)
 | 
			
		||||
    > migrator.remove_model(model, cascade=True)    # Remove a model
 | 
			
		||||
    > migrator.add_fields(model, **fields)          # Add fields to a model
 | 
			
		||||
    > migrator.change_fields(model, **fields)       # Change fields
 | 
			
		||||
    > migrator.remove_fields(model, *field_names, cascade=True)
 | 
			
		||||
    > migrator.rename_field(model, old_field_name, new_field_name)
 | 
			
		||||
    > migrator.rename_table(model, new_table_name)
 | 
			
		||||
    > migrator.add_index(model, *col_names, unique=False)
 | 
			
		||||
    > migrator.drop_index(model, *col_names)
 | 
			
		||||
    > migrator.add_not_null(model, *field_names)
 | 
			
		||||
    > migrator.drop_not_null(model, *field_names)
 | 
			
		||||
    > migrator.add_default(model, field_name, default)
 | 
			
		||||
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
import peewee as pw
 | 
			
		||||
 | 
			
		||||
from frigate.models import Recordings
 | 
			
		||||
 | 
			
		||||
SQL = pw.SQL
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def migrate(migrator, database, fake=False, **kwargs):
 | 
			
		||||
    migrator.add_fields(
 | 
			
		||||
        Recordings,
 | 
			
		||||
        regions=pw.IntegerField(null=True),
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def rollback(migrator, database, fake=False, **kwargs):
 | 
			
		||||
    migrator.remove_fields(Recordings, ["regions"])
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user