Compare commits

..
Author SHA1 Message Date
Josh Hawkins e5b25d23e5 fix autotracking movement time prediction and recorded move times
Movement time prediction multiplied the fitted intercept by the saved zero-step calibration time instead of 1, so predictions didn't match the regression. In absolute zoom mode, a pan/tilt followed by a zoom recorded the zoom's timestamps against the pan/tilt distance, because _zoom_absolute resets the PTZ start and stop times. The pan/tilt times are now captured before the zoom runs.
2026-11-10 07:25:40 -06:00
2 changed files with 22 additions and 10 deletions
+17 -4
View File
@@ -585,7 +585,7 @@ class PtzAutoTracker(threading.Thread):
def _predict_movement_time(self, camera, pan, tilt):
combined_movement = abs(pan) + abs(tilt)
input_data = np.array([self.intercept[camera], combined_movement])
input_data = np.array([1, combined_movement])
return np.dot(self.move_coefficients[camera], input_data)
@@ -744,6 +744,8 @@ class PtzAutoTracker(threading.Thread):
continue
else:
pan_tilt_times = None
if (
camera_config.onvif.autotracking.zooming
== ZoomingModeEnum.relative
@@ -755,17 +757,28 @@ class PtzAutoTracker(threading.Thread):
await self._wait_until_stopped(camera, metrics)
# _zoom_absolute resets the metrics timestamps
pan_tilt_times = (
metrics.start_time.value,
metrics.stop_time.value,
)
if zoom > 0 and metrics.zoom_level.value != zoom:
await self.onvif._zoom_absolute(camera, zoom, 1)
await self._wait_until_stopped(camera, metrics)
start_time, stop_time = pan_tilt_times or (
metrics.start_time.value,
metrics.stop_time.value,
)
if camera_config.onvif.autotracking.movement_weights:
logger.debug(
f"{camera}: Predicted movement time: {self._predict_movement_time(camera, pan, tilt)}"
)
logger.debug(
f"{camera}: Actual movement time: {metrics.stop_time.value - metrics.start_time.value}"
f"{camera}: Actual movement time: {stop_time - start_time}"
)
# save metrics for better estimate calculations
@@ -781,8 +794,8 @@ class PtzAutoTracker(threading.Thread):
{
"pan": pan,
"tilt": tilt,
"start_timestamp": metrics.start_time.value,
"end_timestamp": metrics.stop_time.value,
"start_timestamp": start_time,
"end_timestamp": stop_time,
}
)
@@ -150,18 +150,17 @@ export function TrackingDetails({
// Fetch the VOD manifest JSON to get the actual clipFrom after keyframe
// snapping. The backend may snap clipFrom backwards to a keyframe, making
// the video start earlier than the requested time. Uses sourceOffsetRef so
// the manifest describes the same clip as videoSource.
// the video start earlier than the requested time.
const vodManifestUrl = useMemo(() => {
if (!event.camera) return null;
const sourceOffset = sourceOffsetRef.current;
const startTime = event.start_time + sourceOffset / 1000 - REVIEW_PADDING;
const startTime =
event.start_time + annotationOffset / 1000 - REVIEW_PADDING;
const endTime =
(event.end_time ?? Date.now() / 1000) +
sourceOffset / 1000 +
annotationOffset / 1000 +
REVIEW_PADDING;
return `vod/clip/${event.camera}/start/${startTime}/end/${endTime}`;
}, [event]);
}, [event, annotationOffset]);
const { data: vodManifest } = useSWR<VodManifest>(vodManifestUrl, null, {
revalidateOnFocus: false,