Refactor activity api to send motion and audio data

This commit is contained in:
Nicolas Mowen 2024-03-04 12:05:09 -07:00
parent a174d82eb9
commit e88a0baccb
2 changed files with 59 additions and 103 deletions

View File

@ -5,12 +5,9 @@ import json
import logging
import os
import traceback
from collections import defaultdict
from datetime import datetime, timedelta
from functools import reduce
import numpy as np
import pandas as pd
import requests
from flask import (
Blueprint,
@ -632,102 +629,3 @@ def hourly_timeline():
"hours": hours,
}
)
@bp.route("/<camera_name>/recording/hourly/activity")
def hourly_timeline_activity(camera_name: str):
"""Get hourly summary for timeline."""
if camera_name not in current_app.frigate_config.cameras:
return make_response(
jsonify({"success": False, "message": "Camera not found"}),
404,
)
before = request.args.get("before", type=float, default=datetime.now())
after = request.args.get(
"after", type=float, default=datetime.now() - timedelta(hours=1)
)
tz_name = request.args.get("timezone", default="utc", type=str)
_, minute_modifier, _ = get_tz_modifiers(tz_name)
minute_offset = int(minute_modifier.split(" ")[0])
all_recordings: list[Recordings] = (
Recordings.select(
Recordings.start_time,
Recordings.duration,
Recordings.objects,
Recordings.motion,
)
.where(Recordings.camera == camera_name)
.where(Recordings.motion > 0)
.where((Recordings.start_time > after) & (Recordings.end_time < before))
.order_by(Recordings.start_time.asc())
.iterator()
)
# data format is ex:
# {timestamp: [{ date: 1, count: 1, type: motion }]}] }}
hours: dict[int, list[dict[str, any]]] = defaultdict(list)
key = datetime.fromtimestamp(after).replace(second=0, microsecond=0) + timedelta(
minutes=minute_offset
)
check = (key + timedelta(hours=1)).timestamp()
# set initial start so data is representative of full hour
hours[int(key.timestamp())].append(
[
key.timestamp(),
0,
False,
]
)
for recording in all_recordings:
if recording.start_time > check:
hours[int(key.timestamp())].append(
[
(key + timedelta(minutes=59, seconds=59)).timestamp(),
0,
False,
]
)
key = key + timedelta(hours=1)
check = (key + timedelta(hours=1)).timestamp()
hours[int(key.timestamp())].append(
[
key.timestamp(),
0,
False,
]
)
data_type = recording.objects > 0
count = recording.motion + recording.objects
hours[int(key.timestamp())].append(
[
recording.start_time + (recording.duration / 2),
0 if count == 0 else np.log2(count),
data_type,
]
)
# resample data using pandas to get activity on minute to minute basis
for key, data in hours.items():
df = pd.DataFrame(data, columns=["date", "count", "hasObjects"])
# set date as datetime index
df["date"] = pd.to_datetime(df["date"], unit="s")
df.set_index(["date"], inplace=True)
# normalize data
df = df.resample("T").mean().fillna(0)
# change types for output
df.index = df.index.astype(int) // (10**9)
df["count"] = df["count"].astype(int)
df["hasObjects"] = df["hasObjects"].astype(bool)
hours[key] = df.reset_index().to_dict("records")
return jsonify(hours)

View File

@ -4,6 +4,8 @@ import logging
from datetime import datetime, timedelta
from functools import reduce
import numpy as np
import pandas as pd
from flask import (
Blueprint,
jsonify,
@ -12,7 +14,7 @@ from flask import (
)
from peewee import Case, DoesNotExist, fn, operator
from frigate.models import ReviewSegment
from frigate.models import Recordings, ReviewSegment
from frigate.util.builtin import get_tz_modifiers
logger = logging.getLogger(__name__)
@ -258,3 +260,59 @@ def delete_reviews(ids: str):
ReviewSegment.delete().where(ReviewSegment.id << list_of_ids).execute()
return make_response(jsonify({"success": True, "message": "Delete reviews"}), 200)
@ReviewBp.route("/review/activity")
def review_activity():
"""Get motion and audio activity."""
before = request.args.get("before", type=float, default=datetime.now().timestamp())
after = request.args.get(
"after", type=float, default=(datetime.now() - timedelta(hours=1)).timestamp()
)
# get scale in seconds
scale = request.args.get(
"scale", type=int, default=30
)
all_recordings: list[Recordings] = (
Recordings.select(
Recordings.start_time,
Recordings.duration,
Recordings.objects,
Recordings.motion,
Recordings.dBFS,
)
.where((Recordings.start_time > after) & (Recordings.end_time < before))
.order_by(Recordings.start_time.asc())
.iterator()
)
# format is: { timestamp: segment_start_ts, motion: [0-100], audio: [0 - -100] }
# periods where active objects / audio was detected will cause motion / audio to be scaled down
data: list[dict[str, float]] = []
for rec in all_recordings:
factor = 0.1 if rec.objects > 0 else 1.0
data.append({
"date": rec.start_time,
"motion": rec.motion * factor,
"audio": rec.dBFS * factor,
})
# resample data using pandas to get activity on scaled basis
df = pd.DataFrame(data, columns=["date", "motion", "audio"])
# set date as datetime index
df["date"] = pd.to_datetime(df["date"], unit="s")
df.set_index(["date"], inplace=True)
# normalize data
df = df.resample(f"{scale}S").mean().fillna(0.0)
df["motion"] = (df["motion"] - df["motion"].min()) / (df["motion"].max() - df["motion"].min()) * 100
df["audio"] = (df["audio"] - df["audio"].max()) / (df["audio"].min() - df["audio"].max()) * -100
# change types for output
df.index = df.index.astype(int) // (10**9)
normalized = df.reset_index().to_dict("records")
return jsonify(normalized)