mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-02-08 12:15:25 +03:00
Improve scaling of graph motion activity
This commit is contained in:
parent
ba1b2158dd
commit
48b672e4b4
@ -784,10 +784,11 @@ def hourly_timeline_activity(camera_name: str):
|
|||||||
)
|
)
|
||||||
|
|
||||||
data_type = recording.objects > 0
|
data_type = recording.objects > 0
|
||||||
|
count = recording.motion + recording.objects
|
||||||
hours[int(key.timestamp())].append(
|
hours[int(key.timestamp())].append(
|
||||||
[
|
[
|
||||||
recording.start_time + (recording.duration / 2),
|
recording.start_time + (recording.duration / 2),
|
||||||
max(recording.motion, recording.objects),
|
0 if count == 0 else np.log2(count),
|
||||||
data_type,
|
data_type,
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
@ -801,7 +802,6 @@ def hourly_timeline_activity(camera_name: str):
|
|||||||
df.set_index(["date"], inplace=True)
|
df.set_index(["date"], inplace=True)
|
||||||
|
|
||||||
# normalize data
|
# normalize data
|
||||||
df["count"] = np.clip(np.log10(df["count"], where=df["count"] > 0), None, 10)
|
|
||||||
df = df.resample("T").mean().fillna(0)
|
df = df.resample("T").mean().fillna(0)
|
||||||
|
|
||||||
# change types for output
|
# change types for output
|
||||||
|
|||||||
@ -108,6 +108,15 @@ class TimelineProcessor(threading.Thread):
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# update sub labels for existing entries that haven't been added yet
|
||||||
|
if (
|
||||||
|
prev_event_data != None
|
||||||
|
and prev_event_data["sub_label"] != event_data["sub_label"]
|
||||||
|
and event_id in self.pre_event_cache.keys()
|
||||||
|
):
|
||||||
|
for e in self.pre_event_cache[event_id]:
|
||||||
|
e[Timeline.data]["sub_label"] = event_data["sub_label"]
|
||||||
|
|
||||||
if event_type == "start":
|
if event_type == "start":
|
||||||
timeline_entry[Timeline.class_type] = "visible"
|
timeline_entry[Timeline.class_type] = "visible"
|
||||||
save = True
|
save = True
|
||||||
|
|||||||
@ -48,7 +48,7 @@ export default function TimelineGraph({
|
|||||||
grid: {
|
grid: {
|
||||||
show: false,
|
show: false,
|
||||||
padding: {
|
padding: {
|
||||||
bottom: 20,
|
bottom: 2,
|
||||||
top: -12,
|
top: -12,
|
||||||
left: -20,
|
left: -20,
|
||||||
right: 0,
|
right: 0,
|
||||||
|
|||||||
@ -143,8 +143,8 @@ function ActivityScrubber({
|
|||||||
|
|
||||||
const timelineInstance = new VisTimeline(
|
const timelineInstance = new VisTimeline(
|
||||||
divElement,
|
divElement,
|
||||||
items as DataItem[],
|
(items || []) as DataItem[],
|
||||||
groups as DataGroup[],
|
(groups || []) as DataGroup[],
|
||||||
timelineOptions
|
timelineOptions
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@ -6,110 +6,124 @@ export function getHourlyTimelineData(
|
|||||||
detailLevel: string
|
detailLevel: string
|
||||||
): CardsData {
|
): CardsData {
|
||||||
const cards: CardsData = {};
|
const cards: CardsData = {};
|
||||||
|
const allHours: { [key: string]: Timeline[] } = {};
|
||||||
|
|
||||||
timelinePages.forEach((hourlyTimeline) => {
|
timelinePages.forEach((hourlyTimeline) => {
|
||||||
Object.keys(hourlyTimeline["hours"])
|
Object.entries(hourlyTimeline.hours).forEach(([key, values]) => {
|
||||||
.reverse()
|
if (key in allHours) {
|
||||||
.forEach((hour) => {
|
// only occurs when multiple pages contain elements in the same hour
|
||||||
const day = new Date(parseInt(hour) * 1000);
|
allHours[key] = allHours[key]
|
||||||
day.setHours(0, 0, 0, 0);
|
.concat(values)
|
||||||
const dayKey = (day.getTime() / 1000).toString();
|
.sort((a, b) => a.timestamp - b.timestamp);
|
||||||
|
} else {
|
||||||
|
allHours[key] = values;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
// build a map of course to the types that are included in this hour
|
Object.keys(allHours)
|
||||||
// which allows us to know what items to keep depending on detail level
|
.sort((a, b) => a.localeCompare(b))
|
||||||
const sourceToTypes: { [key: string]: string[] } = {};
|
.reverse()
|
||||||
let cardTypeStart: { [camera: string]: number } = {};
|
.forEach((hour) => {
|
||||||
Object.values(hourlyTimeline["hours"][hour]).forEach((i) => {
|
const day = new Date(parseInt(hour) * 1000);
|
||||||
if (i.timestamp > (cardTypeStart[i.camera] ?? 0) + GROUP_SECONDS) {
|
day.setHours(0, 0, 0, 0);
|
||||||
cardTypeStart[i.camera] = i.timestamp;
|
const dayKey = (day.getTime() / 1000).toString();
|
||||||
}
|
|
||||||
|
|
||||||
const groupKey = `${i.source_id}-${cardTypeStart[i.camera]}`;
|
// build a map of course to the types that are included in this hour
|
||||||
|
// which allows us to know what items to keep depending on detail level
|
||||||
if (groupKey in sourceToTypes) {
|
const sourceToTypes: { [key: string]: string[] } = {};
|
||||||
sourceToTypes[groupKey].push(i.class_type);
|
let cardTypeStart: { [camera: string]: number } = {};
|
||||||
} else {
|
Object.values(allHours[hour]).forEach((i) => {
|
||||||
sourceToTypes[groupKey] = [i.class_type];
|
if (i.timestamp > (cardTypeStart[i.camera] ?? 0) + GROUP_SECONDS) {
|
||||||
}
|
cardTypeStart[i.camera] = i.timestamp;
|
||||||
});
|
|
||||||
|
|
||||||
if (!(dayKey in cards)) {
|
|
||||||
cards[dayKey] = {};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!(hour in cards[dayKey])) {
|
const groupKey = `${i.source_id}-${cardTypeStart[i.camera]}`;
|
||||||
cards[dayKey][hour] = {};
|
|
||||||
|
if (groupKey in sourceToTypes) {
|
||||||
|
sourceToTypes[groupKey].push(i.class_type);
|
||||||
|
} else {
|
||||||
|
sourceToTypes[groupKey] = [i.class_type];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!(dayKey in cards)) {
|
||||||
|
cards[dayKey] = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(hour in cards[dayKey])) {
|
||||||
|
cards[dayKey][hour] = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
let cardStart: { [camera: string]: number } = {};
|
||||||
|
Object.values(allHours[hour]).forEach((i) => {
|
||||||
|
if (i.timestamp > (cardStart[i.camera] ?? 0) + GROUP_SECONDS) {
|
||||||
|
cardStart[i.camera] = i.timestamp;
|
||||||
}
|
}
|
||||||
|
|
||||||
let cardStart: { [camera: string]: number } = {};
|
const time = new Date(i.timestamp * 1000);
|
||||||
Object.values(hourlyTimeline["hours"][hour]).forEach((i) => {
|
const groupKey = `${i.camera}-${cardStart[i.camera]}`;
|
||||||
if (i.timestamp > (cardStart[i.camera] ?? 0) + GROUP_SECONDS) {
|
const sourceKey = `${i.source_id}-${cardStart[i.camera]}`;
|
||||||
cardStart[i.camera] = i.timestamp;
|
const uniqueKey = `${i.source_id}-${i.class_type}`;
|
||||||
}
|
|
||||||
|
|
||||||
const time = new Date(i.timestamp * 1000);
|
// detail level for saving items
|
||||||
const groupKey = `${i.camera}-${cardStart[i.camera]}`;
|
// detail level determines which timeline items for each moment is returned
|
||||||
const sourceKey = `${i.source_id}-${cardStart[i.camera]}`;
|
// values can be normal, extra, or full
|
||||||
const uniqueKey = `${i.source_id}-${i.class_type}`;
|
// normal: return all items except active / attribute / gone / stationary / visible unless that is the only item.
|
||||||
|
// extra: return all items except attribute / gone / visible unless that is the only item
|
||||||
|
// full: return all items
|
||||||
|
|
||||||
// detail level for saving items
|
let add = true;
|
||||||
// detail level determines which timeline items for each moment is returned
|
const sourceType = sourceToTypes[sourceKey];
|
||||||
// values can be normal, extra, or full
|
let hiddenItems: string[] = [];
|
||||||
// normal: return all items except active / attribute / gone / stationary / visible unless that is the only item.
|
if (detailLevel == "normal") {
|
||||||
// extra: return all items except attribute / gone / visible unless that is the only item
|
hiddenItems = [
|
||||||
// full: return all items
|
"active",
|
||||||
|
"attribute",
|
||||||
|
"gone",
|
||||||
|
"stationary",
|
||||||
|
"visible",
|
||||||
|
];
|
||||||
|
} else if (detailLevel == "extra") {
|
||||||
|
hiddenItems = ["attribute", "gone", "visible"];
|
||||||
|
}
|
||||||
|
|
||||||
let add = true;
|
if (sourceType.length > 1) {
|
||||||
const sourceType = sourceToTypes[sourceKey];
|
// we have multiple timeline items for this card
|
||||||
let hiddenItems: string[] = [];
|
|
||||||
if (detailLevel == "normal") {
|
|
||||||
hiddenItems = [
|
|
||||||
"active",
|
|
||||||
"attribute",
|
|
||||||
"gone",
|
|
||||||
"stationary",
|
|
||||||
"visible",
|
|
||||||
];
|
|
||||||
} else if (detailLevel == "extra") {
|
|
||||||
hiddenItems = ["attribute", "gone", "visible"];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sourceType.length > 1) {
|
if (
|
||||||
// we have multiple timeline items for this card
|
sourceType.find((type) => hiddenItems.includes(type) == false) ==
|
||||||
|
undefined
|
||||||
if (
|
) {
|
||||||
sourceType.find((type) => hiddenItems.includes(type) == false) ==
|
// all of the attribute items for this card make it hidden, but we need to show one
|
||||||
undefined
|
if (sourceType.indexOf(i.class_type) != 0) {
|
||||||
) {
|
|
||||||
// all of the attribute items for this card make it hidden, but we need to show one
|
|
||||||
if (sourceType.indexOf(i.class_type) != 0) {
|
|
||||||
add = false;
|
|
||||||
}
|
|
||||||
} else if (hiddenItems.includes(i.class_type)) {
|
|
||||||
add = false;
|
add = false;
|
||||||
}
|
}
|
||||||
|
} else if (hiddenItems.includes(i.class_type)) {
|
||||||
|
add = false;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (add) {
|
if (add) {
|
||||||
if (groupKey in cards[dayKey][hour]) {
|
if (groupKey in cards[dayKey][hour]) {
|
||||||
if (
|
if (
|
||||||
!cards[dayKey][hour][groupKey].uniqueKeys.includes(uniqueKey) ||
|
!cards[dayKey][hour][groupKey].uniqueKeys.includes(uniqueKey) ||
|
||||||
detailLevel == "full"
|
detailLevel == "full"
|
||||||
) {
|
) {
|
||||||
cards[dayKey][hour][groupKey].entries.push(i);
|
cards[dayKey][hour][groupKey].entries.push(i);
|
||||||
cards[dayKey][hour][groupKey].uniqueKeys.push(uniqueKey);
|
cards[dayKey][hour][groupKey].uniqueKeys.push(uniqueKey);
|
||||||
}
|
|
||||||
} else {
|
|
||||||
cards[dayKey][hour][groupKey] = {
|
|
||||||
camera: i.camera,
|
|
||||||
time: time.getTime() / 1000,
|
|
||||||
entries: [i],
|
|
||||||
uniqueKeys: [uniqueKey],
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
cards[dayKey][hour][groupKey] = {
|
||||||
|
camera: i.camera,
|
||||||
|
time: time.getTime() / 1000,
|
||||||
|
entries: [i],
|
||||||
|
uniqueKeys: [uniqueKey],
|
||||||
|
};
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
return cards;
|
return cards;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -162,7 +162,6 @@ export default function DesktopTimelineView({
|
|||||||
{isSelected ? (
|
{isSelected ? (
|
||||||
<div className="p-2 relative bg-secondary bg-opacity-30 rounded-md">
|
<div className="p-2 relative bg-secondary bg-opacity-30 rounded-md">
|
||||||
<ActivityScrubber
|
<ActivityScrubber
|
||||||
items={[]}
|
|
||||||
timeBars={
|
timeBars={
|
||||||
isSelected
|
isSelected
|
||||||
? [
|
? [
|
||||||
@ -180,7 +179,10 @@ export default function DesktopTimelineView({
|
|||||||
snap: null,
|
snap: null,
|
||||||
min: new Date(timeline.range.start * 1000),
|
min: new Date(timeline.range.start * 1000),
|
||||||
max: new Date(timeline.range.end * 1000),
|
max: new Date(timeline.range.end * 1000),
|
||||||
|
start: new Date(timeline.range.start * 1000),
|
||||||
|
end: new Date(timeline.range.end * 1000),
|
||||||
zoomable: false,
|
zoomable: false,
|
||||||
|
height: "120px",
|
||||||
}}
|
}}
|
||||||
timechangeHandler={(data) => {
|
timechangeHandler={(data) => {
|
||||||
controllerRef.current?.scrubToTimestamp(
|
controllerRef.current?.scrubToTimestamp(
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user