Shinobi/libs/events/tracking.js

413 lines
18 KiB
JavaScript

const movingThings = require('shinobi-node-moving-things-tracker').Tracker
module.exports = (config) => {
const objectTrackers = {}
const objectTrackerTimeouts = {}
const peopleTags = new Set(config.peopleTags || ["person", "Person", "Man", "Woman", "Boy", "Girl"]);
const trackedMatrices = {}
function shiftSet(set) {
for (const res of set) {
set.delete(res);
return res
}
}
function resetObjectTracker(trackerId,matrices){
const Tracker = movingThings.newTracker();
objectTrackers[trackerId] = {
frameCount: 1,
tracker: Tracker,
lastPositions: []
}
return objectTrackers[trackerId]
}
function setLastTracked(trackerId, trackedMatrices){
const theTracker = objectTrackers[trackerId]
theTracker.lastPositions = trackedMatrices
}
function getTracked(trackerId){
const theTracker = objectTrackers[trackerId]
const frameCount = theTracker.frameCount
const trackedObjects = theTracker.tracker.getJSONOfTrackedItems().map((matrix) => {
return {
id: matrix.id,
tag: matrix.name,
x: matrix.x,
y: matrix.y,
width: matrix.w,
height: matrix.h,
confidence: matrix.confidence,
isZombie: matrix.isZombie,
}
})
return trackedObjects;
}
function trackObject(trackerId,matrices){
if(!objectTrackers[trackerId]){
resetObjectTracker(trackerId)
}
const mappedMatrices = matrices.map((matrix) => {
return {
x: matrix.x,
y: matrix.y,
w: matrix.width,
h: matrix.height,
confidence: matrix.confidence,
name: matrix.tag,
}
});
const theTracker = objectTrackers[trackerId]
theTracker.tracker.updateTrackedItemsWithNewFrame(mappedMatrices, theTracker.frameCount);
++theTracker.frameCount
}
function trackObjectWithTimeout(trackerId,matrices){
clearTimeout(objectTrackerTimeouts[trackerId]);
objectTrackerTimeouts[trackerId] = setTimeout(() => {
objectTrackers[trackerId].tracker.reset()
delete(objectTrackers[trackerId])
delete(objectTrackerTimeouts[trackerId])
},1000 * 60);
trackObject(trackerId,matrices);
}
function objectHasMoved(matrices, options = {}) {
const { imgHeight = 1, imgWidth = 1, threshold = 0 } = options;
for (let i = 0; i < matrices.length; i++) {
const current = matrices[i];
if (i < matrices.length - 1) {
const next = matrices[i + 1];
let totalDistanceMoved = 0;
let numPointsCompared = 0;
if (next) {
// Compare each corner of the matrices
const currentCorners = [
{ x: current.x, y: current.y },
{ x: current.x + current.width, y: current.y },
{ x: current.x, y: current.y + current.height },
{ x: current.x + current.width, y: current.y + current.height }
];
const nextCorners = [
{ x: next.x, y: next.y },
{ x: next.x + next.width, y: next.y },
{ x: next.x, y: next.y + next.height },
{ x: next.x + next.width, y: next.y + next.height }
];
for (let j = 0; j < currentCorners.length; j++) {
const currentCorner = currentCorners[j];
const nextCorner = nextCorners[j];
const dx = nextCorner.x - currentCorner.x;
const dy = nextCorner.y - currentCorner.y;
const distanceMoved = Math.sqrt(Math.pow(dx, 2) + Math.pow(dy, 2));
const distanceMovedPercent =
(100 * distanceMoved) / Math.max(current.width, current.height);
totalDistanceMoved += distanceMovedPercent;
numPointsCompared++;
}
const averageDistanceMoved = totalDistanceMoved / numPointsCompared;
if (averageDistanceMoved < threshold) {
continue;
} else {
return true;
}
}
}
}
return false;
}
function groupMatricesById(matrices) {
const matrixById = {};
const matrixTags = {};
matrices.forEach(matrix => {
const id = matrix.id;
const tag = matrix.tag;
if (!matrixById[id]) {
matrixById[id] = [];
}
matrixTags[tag] = id;
matrixById[id].push(matrix);
});
return matrixById
}
function getAllMatricesThatMoved(monitorConfig,matrices){
const monitorDetails = monitorConfig.details
const imgWidth = parseInt(monitorDetails.detector_scale_x_object) || 1280
const imgHeight = parseInt(monitorDetails.detector_scale_y_object) || 720
const objectMovePercent = parseInt(monitorDetails.detector_object_move_percent) || 5
const groupKey = monitorConfig.ke
const monitorId = monitorConfig.mid
const trackerId = `${groupKey}${monitorId}`
const theTracker = objectTrackers[trackerId]
const lastPositions = theTracker.lastPositions
const sortedById = groupMatricesById([...lastPositions,...matrices])
const movedMatrices = []
for (const objectId in sortedById) {
const sortedList = sortedById[objectId]
if(sortedList[1]){
const matrixHasMoved = objectHasMoved(sortedList,{
threshold: objectMovePercent,
imgWidth: imgWidth,
imgHeight: imgHeight,
});
if(matrixHasMoved){
movedMatrices.push(sortedList[1])
}
}
}
return movedMatrices
}
function isMatrixNearAnother(firstMatrix, secondMatrix, imgWidth, imgHeight) {
// Calculate the distance between two rectangles
function rectDistance(rect1, rect2) {
const xDist = Math.max(rect1.x - (rect2.x + rect2.width), rect2.x - (rect1.x + rect1.width), 0);
const yDist = Math.max(rect1.y - (rect2.y + rect2.height), rect2.y - (rect1.y + rect1.height), 0);
return Math.sqrt(xDist * xDist + yDist * yDist);
}
// Calculate the overlap area
function overlapArea(rect1, rect2) {
const xOverlap = Math.max(0, Math.min(rect1.x + rect1.width, rect2.x + rect2.width) - Math.max(rect1.x, rect2.x));
const yOverlap = Math.max(0, Math.min(rect1.y + rect1.height, rect2.y + rect2.height) - Math.max(rect1.y, rect2.y));
return xOverlap * yOverlap;
}
const pxDistance = rectDistance(firstMatrix, secondMatrix);
const overlapAreaValue = overlapArea(firstMatrix, secondMatrix);
const totalArea = firstMatrix.width * firstMatrix.height + secondMatrix.width * secondMatrix.height - overlapAreaValue;
const overlapPercent = totalArea > 0 ? (overlapAreaValue / totalArea) * 100 : 0;
const distancePercent = Math.sqrt(Math.pow(pxDistance / imgWidth, 2) + Math.pow(pxDistance / imgHeight, 2)) * 100;
const isOverlap = overlapAreaValue > 0;
const nearThreshold = 50;
const isNear = pxDistance < nearThreshold;
return { pxDistance, overlapPercent, distancePercent, isOverlap, isNear };
}
function combinePeopleAndMiscObjects(matrices, imgWidth, imgHeight) {
const peopleMatrices = [];
const otherMatrices = [];
// Separate the matrices into two arrays
matrices.forEach(matrix => {
if (peopleTags.has(matrix.tag)) {
peopleMatrices.push({...matrix, nearBy: []});
} else {
otherMatrices.push({...matrix, associatedPeople: [], color: 'green'}); // Initialize associatedPeople array
}
});
// Compare each people matrix with each other matrix
peopleMatrices.forEach(personMatrix => {
otherMatrices.forEach(otherMatrix => {
const comparisonResult = isMatrixNearAnother(personMatrix, otherMatrix, imgWidth, imgHeight);
// console.error(`comparisonResult (${comparisonResult.overlapPercent}%) : ${otherMatrix.tag} (${otherMatrix.id}) is on ${personMatrix.tag} (${personMatrix.id}) about ${comparisonResult.overlapPercent}%`,comparisonResult)
if (comparisonResult.overlapPercent > 35) {
// Attach the person's ID to the otherMatrix
// Combine comparison result with the properties of the other matrix
personMatrix.nearBy.push({
...otherMatrix,
...comparisonResult,
});
otherMatrix.associatedPeople.push(personMatrix.id);
otherMatrix.color = 'yellow'
}
});
});
return [...peopleMatrices, ...otherMatrices];
}
function addToTrackedHistory(theEvent){
const groupKey = theEvent.ke
const monitorId = theEvent.id
const matrices = theEvent.details.matrices
matrices.forEach((matrix) => {
const trackerId = `${groupKey}${monitorId}${matrix.id}${matrix.tag}`;
if(!trackedMatrices[trackerId])trackedMatrices[trackerId] = new Set();
trackedMatrices[trackerId].add(matrix);
if (trackedMatrices[trackerId].length > 30) {
shiftSet(trackedMatrices[trackerId]);
}
});
}
function filterOutLessSeenNearBy(theEvent){
const groupKey = theEvent.ke;
const monitorId = theEvent.id;
const matrices = theEvent.details.matrices;
matrices.forEach(matrix => {
if(!matrix.nearBy)matrix.nearBy = [];
const trackerId = `${groupKey}${monitorId}${matrix.id}${matrix.tag}`;
const trackedSet = trackedMatrices[trackerId];
if (trackedSet && trackedSet.size > 0) {
const frequencyMap = new Map();
trackedSet.forEach(trackedMatrix => {
trackedMatrix.nearBy.forEach(nearByMatrix => {
const key = JSON.stringify(nearByMatrix); // Assuming 'nearByMatrix' is an object
frequencyMap.set(key, (frequencyMap.get(key) || 0) + 1);
});
});
matrix.nearBy = matrix.nearBy.filter(nearByMatrix => {
const key = JSON.stringify(nearByMatrix);
return frequencyMap.get(key) / trackedSet.size >= 0.8;
});
}
});
return theEvent;
}
function separateMatricesByTag(matrices) {
const groupedByTag = matrices.reduce((acc, matrix) => {
if (!acc[matrix.tag]) {
acc[matrix.tag] = [];
}
acc[matrix.tag].push(matrix);
return acc;
}, {});
return Object.values(groupedByTag);
}
function trackMatrices(theEvent){
const groupKey = theEvent.ke;
const monitorId = theEvent.id;
const eventDetails = theEvent.details;
const trackedObjects = []
separateMatricesByTag(eventDetails.matrices).forEach((matrices) => {
if(!matrices[0])return;
const matrixTag = matrices[0].tag
const trackerId = `${groupKey}${monitorId}${matrixTag}`;
trackObjectWithTimeout(trackerId,matrices);
trackedObjects.push(...getTracked(trackerId));
setLastTracked(trackerId, trackedObjects);
});
return trackedObjects;
}
function markMatricesWithRedFlagTags(theEvent, redFlags) {
const groupKey = theEvent.ke;
const monitorId = theEvent.id;
const matrices = theEvent.details.matrices;
matrices.forEach((matrix) => {
const trackerId = `${groupKey}${monitorId}${matrix.id}${matrix.tag}`;
const trackedMatrixSet = trackedMatrices[trackerId];
if (trackedMatrixSet) {
let redFlagCount = 0; // Counter for matrices with red flag tags
trackedMatrixSet.forEach((trackedMatrix) => {
// Check if any nearBy matrix has a tag that matches the red flags
if (trackedMatrix.nearBy && trackedMatrix.nearBy.some(nearByMatrix => redFlags.includes(nearByMatrix.tag))) {
redFlagCount++; // Increment counter for each match
}
});
// Calculate if the red flag count is at least 30% of the trackedMatrixSet
if (redFlagCount / trackedMatrixSet.size >= 0.3) {
matrix.suspect = true; // Mark the matrix as suspect
}
}
});
}
function setMissingRecentlyMatrices(theEvent, redFlags) {
const groupKey = theEvent.ke;
const monitorId = theEvent.id;
let eventMatrices = theEvent.details.matrices.map(matrix => {
return { ...matrix, missingRecently: [] }
});
let nearByFrequencies = {};
// Calculate frequencies of nearBy tags across all trackedMatrixSets
eventMatrices.forEach((matrix) => {
if(!matrix.suspect)return;
const trackerId = `${groupKey}${monitorId}${matrix.id}${matrix.tag}`;
const trackedMatrixSet = trackedMatrices[trackerId];
if (trackedMatrixSet) {
trackedMatrixSet.forEach((trackedMatrix) => {
if (trackedMatrix.nearBy) {
trackedMatrix.nearBy.forEach((nearByMatrix) => {
nearByFrequencies[nearByMatrix.tag] = (nearByFrequencies[nearByMatrix.tag] || 0) + 1;
});
}
});
}
});
// Determine which nearBy items are seen in at least 30% of trackedMatrixSet
let frequentItems = Object.keys(nearByFrequencies).filter(tag => nearByFrequencies[tag] / Object.keys(trackedMatrices).length >= 0.3);
// Update eventMatrices with missingRecently data
eventMatrices = eventMatrices.map(matrix => {
if(!matrix.suspect)return matrix;
let missingTags = frequentItems.filter(item => !!item && !(matrix.nearBy && matrix.nearBy.some(nearByMatrix => nearByMatrix.tag === item)));
if (missingTags.length > 0) {
matrix.missingRecently = missingTags;
}
return matrix;
});
return eventMatrices;
}
function checkMissingItemsNearRedFlagContainers(theEvent, redFlagContainers) {
const groupKey = theEvent.ke;
const monitorId = theEvent.id;
let eventMatrices = theEvent.details.matrices;
eventMatrices.forEach(matrix => {
if(!matrix.suspect)return;
// Initialize an array to store red flag containers that are near missing items, if not already present
if (!matrix.nearRedFlagContainers) {
matrix.nearRedFlagContainers = [];
}
// Proceed if the matrix has missingRecently items
if (matrix.missingRecently && matrix.missingRecently.length > 0) {
const trackerId = `${groupKey}${monitorId}${matrix.id}${matrix.tag}`;
const trackedMatrixSet = trackedMatrices[trackerId];
if (trackedMatrixSet) {
trackedMatrixSet.forEach(trackedMatrix => {
// Check if this trackedMatrix is a redFlagContainer
if (redFlagContainers.includes(trackedMatrix.tag)) {
// Now, check each missingRecently item
matrix.missingRecently.forEach(missingItemTag => {
console.log(`missingItemTag`,missingItemTag)
// Find the original matrix for the missingItemTag to perform proximity check
const missingItemMatrix = Array.from(trackedMatrixSet).find(m => m.tag === missingItemTag);
if (missingItemMatrix) {
// Check if missingItemMatrix is near the redFlagContainer
const isNear = isMatrixNearAnother(missingItemMatrix, trackedMatrix, theEvent.imgWidth, theEvent.imgHeight);
if (isNear) {
// If near, add redFlagContainer information to the matrix
matrix.nearRedFlagContainers.push({
tag: trackedMatrix.tag,
id: trackedMatrix.id,
missingItemTag: missingItemTag
});
}
}
});
}
});
}
}
});
return eventMatrices;
}
return {
trackObjectWithTimeout,
resetObjectTracker,
trackObject,
getTracked,
setLastTracked,
getAllMatricesThatMoved,
isMatrixNearAnother,
combinePeopleAndMiscObjects,
filterOutLessSeenNearBy,
separateMatricesByTag,
addToTrackedHistory,
trackMatrices,
markMatricesWithRedFlagTags,
setMissingRecentlyMatrices,
checkMissingItemsNearRedFlagContainers,
peopleTags,
}
}