diff --git a/metro-ai-suite/metro-vision-ai-app-recipe/compose-without-scenescape.yml b/metro-ai-suite/metro-vision-ai-app-recipe/compose-without-scenescape.yml index b291140794..84a4b27c4e 100644 --- a/metro-ai-suite/metro-vision-ai-app-recipe/compose-without-scenescape.yml +++ b/metro-ai-suite/metro-vision-ai-app-recipe/compose-without-scenescape.yml @@ -140,6 +140,7 @@ services: - "./${SAMPLE_APP}/src/node-red:/data" - "./${SAMPLE_APP}/src/dlstreamer-pipeline-server/videos:/data/public/videos" - node-red-node-modules:/usr/src/node-red/node_modules + - /etc/localtime:/etc/localtime:ro ipc: "none" entrypoint: [ diff --git a/metro-ai-suite/metro-vision-ai-app-recipe/docs/user-guide/support.md b/metro-ai-suite/metro-vision-ai-app-recipe/docs/user-guide/support.md index 4e4e98ed5b..b5e46e7c61 100644 --- a/metro-ai-suite/metro-vision-ai-app-recipe/docs/user-guide/support.md +++ b/metro-ai-suite/metro-vision-ai-app-recipe/docs/user-guide/support.md @@ -2,6 +2,55 @@ This page provides troubleshooting steps, FAQs, and resources to help you resolve common issues. +## Resolving Time Sync Issues in Prometheus + +If you see the following warning in Prometheus, it indicates a time sync issue. + +**Warning: Error fetching server time: Detected xxx.xxx seconds time difference between your browser and the server.** + +You can following the below steps to synchronize system time using NTP. + +1. **Install systemd-timesyncd** if not already installed: + + ```bash + sudo apt install systemd-timesyncd + ``` + +2. **Check service status**: + + ```bash + systemctl status systemd-timesyncd + ``` + +3. **Configure an NTP server** (if behind a corporate proxy): + + ```bash + sudo nano /etc/systemd/timesyncd.conf + ``` + + Add: + + ```ini + [Time] + NTP=corp.intel.com + ``` + + Replace `corp.intel.com` with a different ntp server that is supported on your network. + +4. **Restart the service**: + + ```bash + sudo systemctl restart systemd-timesyncd + ``` + +5. **Verify the status**: + + ```bash + systemctl status systemd-timesyncd + ``` + +This should resolve the time discrepancy in Prometheus. + ## Support - **Developer Forum**: [Join the community forum](#) - **Raise an Issue on GitHub**: [GitHub Issues](https://github.com/open-edge-platform/edge-ai-suites/issues) diff --git a/metro-ai-suite/metro-vision-ai-app-recipe/loitering-detection/docs/user-guide/troubleshooting.md b/metro-ai-suite/metro-vision-ai-app-recipe/loitering-detection/docs/user-guide/troubleshooting.md index 9a5d591153..a1e4d77c62 100644 --- a/metro-ai-suite/metro-vision-ai-app-recipe/loitering-detection/docs/user-guide/troubleshooting.md +++ b/metro-ai-suite/metro-vision-ai-app-recipe/loitering-detection/docs/user-guide/troubleshooting.md @@ -62,6 +62,55 @@ to file new tickets there (after learning about the guidelines for [Contributing https:///mediamtx/ ``` +5. **Resolving Time Sync Issues in Prometheus** + + If you see the following warning in Prometheus, it indicates a time sync issue. + + **Warning: Error fetching server time: Detected xxx.xxx seconds time difference between your browser and the server.** + + You can following the below steps to synchronize system time using NTP. + + 1. **Install systemd-timesyncd** if not already installed: + + ```bash + sudo apt install systemd-timesyncd + ``` + + 2. **Check service status**: + + ```bash + systemctl status systemd-timesyncd + ``` + + 3. **Configure an NTP server** (if behind a corporate proxy): + + ```bash + sudo nano /etc/systemd/timesyncd.conf + ``` + + Add: + + ```ini + [Time] + NTP=corp.intel.com + ``` + + Replace `corp.intel.com` with a different ntp server that is supported on your network. + + 4. **Restart the service**: + + ```bash + sudo systemctl restart systemd-timesyncd + ``` + + 5. **Verify the status**: + + ```bash + systemctl status systemd-timesyncd + ``` + + This should resolve the time discrepancy in Prometheus. + ## Troubleshooting Helm Deployments 1. Deploying with Intel GPU K8S Extension on Intel® Tiber™ Edge Platform diff --git a/metro-ai-suite/metro-vision-ai-app-recipe/loitering-detection/helm-chart/config/grafana/dashboards/visualizer.json b/metro-ai-suite/metro-vision-ai-app-recipe/loitering-detection/helm-chart/config/grafana/dashboards/visualizer.json index 8c4da231c0..f363322357 100644 --- a/metro-ai-suite/metro-vision-ai-app-recipe/loitering-detection/helm-chart/config/grafana/dashboards/visualizer.json +++ b/metro-ai-suite/metro-vision-ai-app-recipe/loitering-detection/helm-chart/config/grafana/dashboards/visualizer.json @@ -58,7 +58,7 @@ "showLineNumbers": false, "showMiniMap": false }, - "content": " ", + "content": " ", "mode": "html" }, "pluginVersion": "11.5.4", @@ -83,7 +83,7 @@ "showLineNumbers": false, "showMiniMap": false }, - "content": " ", + "content": " ", "mode": "html" }, "pluginVersion": "11.5.4", @@ -108,7 +108,7 @@ "showLineNumbers": false, "showMiniMap": false }, - "content": " ", + "content": " ", "mode": "html" }, "pluginVersion": "11.5.4", @@ -133,7 +133,7 @@ "showLineNumbers": false, "showMiniMap": false }, - "content": " ", + "content": " ", "mode": "html" }, "pluginVersion": "11.5.4", @@ -207,9 +207,13 @@ "title": "Loiter Table", "transformations": [ { - "id": "limit", + "id": "reduce", "options": { - "limitField": "1" + "reducers": [ + "lastNotNull" + ], + "mode": "reduceFields", + "includeTimeField": false } }, { diff --git a/metro-ai-suite/metro-vision-ai-app-recipe/loitering-detection/helm-chart/config/grafana/dashboards/visualizer_sub_1.json b/metro-ai-suite/metro-vision-ai-app-recipe/loitering-detection/helm-chart/config/grafana/dashboards/visualizer_sub_1.json index e354e51924..1b473b5a1a 100644 --- a/metro-ai-suite/metro-vision-ai-app-recipe/loitering-detection/helm-chart/config/grafana/dashboards/visualizer_sub_1.json +++ b/metro-ai-suite/metro-vision-ai-app-recipe/loitering-detection/helm-chart/config/grafana/dashboards/visualizer_sub_1.json @@ -58,7 +58,7 @@ "showLineNumbers": false, "showMiniMap": false }, - "content": " ", + "content": " ", "mode": "html" }, "pluginVersion": "11.5.4", @@ -132,9 +132,13 @@ "title": "Loiter Table", "transformations": [ { - "id": "limit", + "id": "reduce", "options": { - "limitField": "1" + "reducers": [ + "lastNotNull" + ], + "mode": "reduceFields", + "includeTimeField": false } }, { diff --git a/metro-ai-suite/metro-vision-ai-app-recipe/loitering-detection/helm-chart/config/grafana/dashboards/visualizer_sub_2.json b/metro-ai-suite/metro-vision-ai-app-recipe/loitering-detection/helm-chart/config/grafana/dashboards/visualizer_sub_2.json index d2aec4f2ad..dee1ccfbe6 100644 --- a/metro-ai-suite/metro-vision-ai-app-recipe/loitering-detection/helm-chart/config/grafana/dashboards/visualizer_sub_2.json +++ b/metro-ai-suite/metro-vision-ai-app-recipe/loitering-detection/helm-chart/config/grafana/dashboards/visualizer_sub_2.json @@ -58,7 +58,7 @@ "showLineNumbers": false, "showMiniMap": false }, - "content": " ", + "content": " ", "mode": "html" }, "pluginVersion": "11.5.4", @@ -132,9 +132,13 @@ "title": "Loiter Table", "transformations": [ { - "id": "limit", + "id": "reduce", "options": { - "limitField": "1" + "reducers": [ + "lastNotNull" + ], + "mode": "reduceFields", + "includeTimeField": false } }, { diff --git a/metro-ai-suite/metro-vision-ai-app-recipe/loitering-detection/helm-chart/config/grafana/dashboards/visualizer_sub_3.json b/metro-ai-suite/metro-vision-ai-app-recipe/loitering-detection/helm-chart/config/grafana/dashboards/visualizer_sub_3.json index 50f736218d..4b9552c6ae 100644 --- a/metro-ai-suite/metro-vision-ai-app-recipe/loitering-detection/helm-chart/config/grafana/dashboards/visualizer_sub_3.json +++ b/metro-ai-suite/metro-vision-ai-app-recipe/loitering-detection/helm-chart/config/grafana/dashboards/visualizer_sub_3.json @@ -58,7 +58,7 @@ "showLineNumbers": false, "showMiniMap": false }, - "content": " ", + "content": " ", "mode": "html" }, "pluginVersion": "11.5.4", @@ -132,9 +132,13 @@ "title": "Loiter Table", "transformations": [ { - "id": "limit", + "id": "reduce", "options": { - "limitField": "1" + "reducers": [ + "lastNotNull" + ], + "mode": "reduceFields", + "includeTimeField": false } }, { diff --git a/metro-ai-suite/metro-vision-ai-app-recipe/loitering-detection/helm-chart/config/grafana/dashboards/visualizer_sub_4.json b/metro-ai-suite/metro-vision-ai-app-recipe/loitering-detection/helm-chart/config/grafana/dashboards/visualizer_sub_4.json index 969cea91ca..0bd68dde50 100644 --- a/metro-ai-suite/metro-vision-ai-app-recipe/loitering-detection/helm-chart/config/grafana/dashboards/visualizer_sub_4.json +++ b/metro-ai-suite/metro-vision-ai-app-recipe/loitering-detection/helm-chart/config/grafana/dashboards/visualizer_sub_4.json @@ -58,7 +58,7 @@ "showLineNumbers": false, "showMiniMap": false }, - "content": " ", + "content": " ", "mode": "html" }, "pluginVersion": "11.5.4", @@ -132,9 +132,13 @@ "title": "Loiter Table", "transformations": [ { - "id": "limit", + "id": "reduce", "options": { - "limitField": "1" + "reducers": [ + "lastNotNull" + ], + "mode": "reduceFields", + "includeTimeField": false } }, { diff --git a/metro-ai-suite/metro-vision-ai-app-recipe/loitering-detection/src/grafana/dashboards/visualizer.json b/metro-ai-suite/metro-vision-ai-app-recipe/loitering-detection/src/grafana/dashboards/visualizer.json index 93d1f3b1a7..f363322357 100644 --- a/metro-ai-suite/metro-vision-ai-app-recipe/loitering-detection/src/grafana/dashboards/visualizer.json +++ b/metro-ai-suite/metro-vision-ai-app-recipe/loitering-detection/src/grafana/dashboards/visualizer.json @@ -207,9 +207,13 @@ "title": "Loiter Table", "transformations": [ { - "id": "limit", + "id": "reduce", "options": { - "limitField": "1" + "reducers": [ + "lastNotNull" + ], + "mode": "reduceFields", + "includeTimeField": false } }, { diff --git a/metro-ai-suite/metro-vision-ai-app-recipe/loitering-detection/src/grafana/dashboards/visualizer_sub_1.json b/metro-ai-suite/metro-vision-ai-app-recipe/loitering-detection/src/grafana/dashboards/visualizer_sub_1.json index 76efd3cd84..1b473b5a1a 100644 --- a/metro-ai-suite/metro-vision-ai-app-recipe/loitering-detection/src/grafana/dashboards/visualizer_sub_1.json +++ b/metro-ai-suite/metro-vision-ai-app-recipe/loitering-detection/src/grafana/dashboards/visualizer_sub_1.json @@ -132,9 +132,13 @@ "title": "Loiter Table", "transformations": [ { - "id": "limit", + "id": "reduce", "options": { - "limitField": "1" + "reducers": [ + "lastNotNull" + ], + "mode": "reduceFields", + "includeTimeField": false } }, { diff --git a/metro-ai-suite/metro-vision-ai-app-recipe/loitering-detection/src/grafana/dashboards/visualizer_sub_2.json b/metro-ai-suite/metro-vision-ai-app-recipe/loitering-detection/src/grafana/dashboards/visualizer_sub_2.json index a61b0be324..dee1ccfbe6 100644 --- a/metro-ai-suite/metro-vision-ai-app-recipe/loitering-detection/src/grafana/dashboards/visualizer_sub_2.json +++ b/metro-ai-suite/metro-vision-ai-app-recipe/loitering-detection/src/grafana/dashboards/visualizer_sub_2.json @@ -132,9 +132,13 @@ "title": "Loiter Table", "transformations": [ { - "id": "limit", + "id": "reduce", "options": { - "limitField": "1" + "reducers": [ + "lastNotNull" + ], + "mode": "reduceFields", + "includeTimeField": false } }, { diff --git a/metro-ai-suite/metro-vision-ai-app-recipe/loitering-detection/src/grafana/dashboards/visualizer_sub_3.json b/metro-ai-suite/metro-vision-ai-app-recipe/loitering-detection/src/grafana/dashboards/visualizer_sub_3.json index 4dd695199b..4b9552c6ae 100644 --- a/metro-ai-suite/metro-vision-ai-app-recipe/loitering-detection/src/grafana/dashboards/visualizer_sub_3.json +++ b/metro-ai-suite/metro-vision-ai-app-recipe/loitering-detection/src/grafana/dashboards/visualizer_sub_3.json @@ -132,9 +132,13 @@ "title": "Loiter Table", "transformations": [ { - "id": "limit", + "id": "reduce", "options": { - "limitField": "1" + "reducers": [ + "lastNotNull" + ], + "mode": "reduceFields", + "includeTimeField": false } }, { diff --git a/metro-ai-suite/metro-vision-ai-app-recipe/loitering-detection/src/grafana/dashboards/visualizer_sub_4.json b/metro-ai-suite/metro-vision-ai-app-recipe/loitering-detection/src/grafana/dashboards/visualizer_sub_4.json index 40b388f79f..0bd68dde50 100644 --- a/metro-ai-suite/metro-vision-ai-app-recipe/loitering-detection/src/grafana/dashboards/visualizer_sub_4.json +++ b/metro-ai-suite/metro-vision-ai-app-recipe/loitering-detection/src/grafana/dashboards/visualizer_sub_4.json @@ -132,9 +132,13 @@ "title": "Loiter Table", "transformations": [ { - "id": "limit", + "id": "reduce", "options": { - "limitField": "1" + "reducers": [ + "lastNotNull" + ], + "mode": "reduceFields", + "includeTimeField": false } }, { diff --git a/metro-ai-suite/metro-vision-ai-app-recipe/loitering-detection/src/node-red/flows.json b/metro-ai-suite/metro-vision-ai-app-recipe/loitering-detection/src/node-red/flows.json index de788e989c..4c6af36792 100644 --- a/metro-ai-suite/metro-vision-ai-app-recipe/loitering-detection/src/node-red/flows.json +++ b/metro-ai-suite/metro-vision-ai-app-recipe/loitering-detection/src/node-red/flows.json @@ -1041,7 +1041,7 @@ "type": "data extraction", "z": "2720733497fe05f3", "name": "data extraction 1", - "func": "let payload = msg.payload[\"metadata\"][\"objects\"];\nlet result = {};\nlet counter = 1;\n\nvar object_confidence = flow.get(\"object_confidence\");\nvar target_object = flow.get(\"target_object\");\nvar stop_duration = flow.get(\"stop_duration\");\n\nvar date = new Date();\nvar localDate = new Date(date.toLocaleString(\"en-US\", { timeZone: \"Asia/Kuala_Lumpur\" }));\n\nvar years = localDate.getFullYear();\nvar months = (localDate.getMonth() + 1).toString().padStart(2, \"0\");\nvar dates = (localDate.getDate()).toString().padStart(2, \"0\");\nvar hours = localDate.getHours();\nvar minutes = localDate.getMinutes();\nvar seconds = localDate.getSeconds();\n\nhours = (\"0\" + hours).slice(-2);\nminutes = (\"0\" + minutes).slice(-2);\nseconds = (\"0\" + seconds).slice(-2);\n\nmsg.nodered_timestamp = years + \"/\" + months + \"/\" + dates + \" \" + hours + \":\" + minutes + \":\" + seconds;\nmsg.object_timestamp = new Date().getTime() / 1000;\n\nfor (let key in payload) {\n if (payload.hasOwnProperty(key) && payload[key].hasOwnProperty(\"detection\")) {\n if (!(target_object.length == 1 && target_object[0] == \"\")) {\n var skip = true;\n for (let i = 0; i < target_object.length; i++) {\n if (payload[key][\"roi_type\"] == target_object[i]) {\n skip = false;\n break;\n }\n }\n if (skip == true) { continue; }\n }\n if (payload[key][\"detection\"][\"confidence\"] > object_confidence) {\n var current_object_data = {\n \"bbox\": {\n \"vertex1\": { \"x\": payload[key][\"x\"], \"y\": payload[key][\"y\"] },\n \"vertex2\": { \"x\": payload[key][\"x\"] + payload[key][\"w\"], \"y\": payload[key][\"y\"] },\n \"vertex3\": { \"x\": payload[key][\"x\"] + payload[key][\"w\"], \"y\": payload[key][\"y\"] + payload[key][\"h\"] },\n \"vertex4\": { \"x\": payload[key][\"x\"], \"y\": payload[key][\"y\"] + payload[key][\"h\"] },\n \"vertex_0\": { \"x\": payload[key][\"x\"], \"y\": payload[key][\"y\"] }\n }\n }\n if (payload[key].hasOwnProperty(\"type\") && payload[key][\"type\"].hasOwnProperty(\"label\")) {\n current_object_data[\"type\"] = payload[key][\"type\"][\"label\"];\n }\n if (payload[key].hasOwnProperty(\"color\") && payload[key][\"color\"].hasOwnProperty(\"label\")) {\n current_object_data[\"color\"] = payload[key][\"color\"][\"label\"];\n }\n if (payload[key].hasOwnProperty(\"license_plate\") && payload[key][\"license_plate\"].hasOwnProperty(\"label\")) {\n current_object_data[\"license_plate\"] = payload[key][\"license_plate\"][\"label\"];\n }\n if (payload[key].hasOwnProperty(\"id\")) {\n current_object_data[\"id\"] = payload[key][\"id\"];\n }\n if (payload[key].hasOwnProperty(\"roi_type\")) {\n current_object_data[\"roi_type\"] = payload[key][\"roi_type\"];\n }\n result[`object_${counter++}`] = current_object_data;\n }\n }\n}\n\nif (Object.keys(result).length > 0) {\n msg.payload = result;\n return msg;\n}", + "func": "let payload = msg.payload[\"metadata\"][\"objects\"];\nlet result = {};\nlet counter = 1;\n\nvar object_confidence = flow.get(\"object_confidence\");\nvar target_object = flow.get(\"target_object\");\nvar stop_duration = flow.get(\"stop_duration\");\n\nvar date = new Date();\nvar localDate = new Date();\n\nvar years = localDate.getFullYear();\nvar months = (localDate.getMonth() + 1).toString().padStart(2, \"0\");\nvar dates = (localDate.getDate()).toString().padStart(2, \"0\");\nvar hours = localDate.getHours();\nvar minutes = localDate.getMinutes();\nvar seconds = localDate.getSeconds();\n\nhours = (\"0\" + hours).slice(-2);\nminutes = (\"0\" + minutes).slice(-2);\nseconds = (\"0\" + seconds).slice(-2);\n\nmsg.nodered_timestamp = years + \"/\" + months + \"/\" + dates + \" \" + hours + \":\" + minutes + \":\" + seconds;\nmsg.object_timestamp = new Date().getTime() / 1000;\n\nfor (let key in payload) {\n if (payload.hasOwnProperty(key) && payload[key].hasOwnProperty(\"detection\")) {\n if (!(target_object.length == 1 && target_object[0] == \"\")) {\n var skip = true;\n for (let i = 0; i < target_object.length; i++) {\n if (payload[key][\"roi_type\"] == target_object[i]) {\n skip = false;\n break;\n }\n }\n if (skip == true) { continue; }\n }\n if (payload[key][\"detection\"][\"confidence\"] > object_confidence) {\n var current_object_data = {\n \"bbox\": {\n \"vertex1\": { \"x\": payload[key][\"x\"], \"y\": payload[key][\"y\"] },\n \"vertex2\": { \"x\": payload[key][\"x\"] + payload[key][\"w\"], \"y\": payload[key][\"y\"] },\n \"vertex3\": { \"x\": payload[key][\"x\"] + payload[key][\"w\"], \"y\": payload[key][\"y\"] + payload[key][\"h\"] },\n \"vertex4\": { \"x\": payload[key][\"x\"], \"y\": payload[key][\"y\"] + payload[key][\"h\"] },\n \"vertex_0\": { \"x\": payload[key][\"x\"], \"y\": payload[key][\"y\"] }\n }\n }\n if (payload[key].hasOwnProperty(\"type\") && payload[key][\"type\"].hasOwnProperty(\"label\")) {\n current_object_data[\"type\"] = payload[key][\"type\"][\"label\"];\n }\n if (payload[key].hasOwnProperty(\"color\") && payload[key][\"color\"].hasOwnProperty(\"label\")) {\n current_object_data[\"color\"] = payload[key][\"color\"][\"label\"];\n }\n if (payload[key].hasOwnProperty(\"license_plate\") && payload[key][\"license_plate\"].hasOwnProperty(\"label\")) {\n current_object_data[\"license_plate\"] = payload[key][\"license_plate\"][\"label\"];\n }\n if (payload[key].hasOwnProperty(\"id\")) {\n current_object_data[\"id\"] = payload[key][\"id\"];\n }\n if (payload[key].hasOwnProperty(\"roi_type\")) {\n current_object_data[\"roi_type\"] = payload[key][\"roi_type\"];\n }\n result[`object_${counter++}`] = current_object_data;\n }\n }\n}\n\nif (Object.keys(result).length > 0) {\n msg.payload = result;\n return msg;\n}", "outputs": 1, "timeout": 0, "noerr": 0, @@ -1100,7 +1100,7 @@ "type": "data extraction", "z": "2720733497fe05f3", "name": "data extraction 2", - "func": "let payload = msg.payload[\"metadata\"][\"objects\"];\nlet result = {};\nlet counter = 1;\n\nvar object_confidence = flow.get(\"object_confidence\");\nvar target_object = flow.get(\"target_object\");\nvar stop_duration = flow.get(\"stop_duration\");\n\nvar date = new Date();\nvar localDate = new Date(date.toLocaleString(\"en-US\", { timeZone: \"Asia/Kuala_Lumpur\" }));\n\nvar years = localDate.getFullYear();\nvar months = (localDate.getMonth() + 1).toString().padStart(2, \"0\");\nvar dates = (localDate.getDate()).toString().padStart(2, \"0\");\nvar hours = localDate.getHours();\nvar minutes = localDate.getMinutes();\nvar seconds = localDate.getSeconds();\n\nhours = (\"0\" + hours).slice(-2);\nminutes = (\"0\" + minutes).slice(-2);\nseconds = (\"0\" + seconds).slice(-2);\n\nmsg.nodered_timestamp = years + \"/\" + months + \"/\" + dates + \" \" + hours + \":\" + minutes + \":\" + seconds;\nmsg.object_timestamp = new Date().getTime() / 1000;\n\nfor (let key in payload) {\n if (payload.hasOwnProperty(key) && payload[key].hasOwnProperty(\"detection\")) {\n if (!(target_object.length == 1 && target_object[0] == \"\")) {\n var skip = true;\n for (let i = 0; i < target_object.length; i++) {\n if (payload[key][\"roi_type\"] == target_object[i]) {\n skip = false;\n break;\n }\n }\n if (skip == true) { continue; }\n }\n if (payload[key][\"detection\"][\"confidence\"] > object_confidence) {\n var current_object_data = {\n \"bbox\": {\n \"vertex1\": { \"x\": payload[key][\"x\"], \"y\": payload[key][\"y\"] },\n \"vertex2\": { \"x\": payload[key][\"x\"] + payload[key][\"w\"], \"y\": payload[key][\"y\"] },\n \"vertex3\": { \"x\": payload[key][\"x\"] + payload[key][\"w\"], \"y\": payload[key][\"y\"] + payload[key][\"h\"] },\n \"vertex4\": { \"x\": payload[key][\"x\"], \"y\": payload[key][\"y\"] + payload[key][\"h\"] },\n \"vertex_0\": { \"x\": payload[key][\"x\"], \"y\": payload[key][\"y\"] }\n }\n }\n if (payload[key].hasOwnProperty(\"type\") && payload[key][\"type\"].hasOwnProperty(\"label\")) {\n current_object_data[\"type\"] = payload[key][\"type\"][\"label\"];\n }\n if (payload[key].hasOwnProperty(\"color\") && payload[key][\"color\"].hasOwnProperty(\"label\")) {\n current_object_data[\"color\"] = payload[key][\"color\"][\"label\"];\n }\n if (payload[key].hasOwnProperty(\"license_plate\") && payload[key][\"license_plate\"].hasOwnProperty(\"label\")) {\n current_object_data[\"license_plate\"] = payload[key][\"license_plate\"][\"label\"];\n }\n if (payload[key].hasOwnProperty(\"id\")) {\n current_object_data[\"id\"] = payload[key][\"id\"];\n }\n if (payload[key].hasOwnProperty(\"roi_type\")) {\n current_object_data[\"roi_type\"] = payload[key][\"roi_type\"];\n }\n result[`object_${counter++}`] = current_object_data;\n }\n }\n}\n\nif (Object.keys(result).length > 0) {\n msg.payload = result;\n return msg;\n}", + "func": "let payload = msg.payload[\"metadata\"][\"objects\"];\nlet result = {};\nlet counter = 1;\n\nvar object_confidence = flow.get(\"object_confidence\");\nvar target_object = flow.get(\"target_object\");\nvar stop_duration = flow.get(\"stop_duration\");\n\nvar date = new Date();\nvar localDate = new Date();\n\nvar years = localDate.getFullYear();\nvar months = (localDate.getMonth() + 1).toString().padStart(2, \"0\");\nvar dates = (localDate.getDate()).toString().padStart(2, \"0\");\nvar hours = localDate.getHours();\nvar minutes = localDate.getMinutes();\nvar seconds = localDate.getSeconds();\n\nhours = (\"0\" + hours).slice(-2);\nminutes = (\"0\" + minutes).slice(-2);\nseconds = (\"0\" + seconds).slice(-2);\n\nmsg.nodered_timestamp = years + \"/\" + months + \"/\" + dates + \" \" + hours + \":\" + minutes + \":\" + seconds;\nmsg.object_timestamp = new Date().getTime() / 1000;\n\nfor (let key in payload) {\n if (payload.hasOwnProperty(key) && payload[key].hasOwnProperty(\"detection\")) {\n if (!(target_object.length == 1 && target_object[0] == \"\")) {\n var skip = true;\n for (let i = 0; i < target_object.length; i++) {\n if (payload[key][\"roi_type\"] == target_object[i]) {\n skip = false;\n break;\n }\n }\n if (skip == true) { continue; }\n }\n if (payload[key][\"detection\"][\"confidence\"] > object_confidence) {\n var current_object_data = {\n \"bbox\": {\n \"vertex1\": { \"x\": payload[key][\"x\"], \"y\": payload[key][\"y\"] },\n \"vertex2\": { \"x\": payload[key][\"x\"] + payload[key][\"w\"], \"y\": payload[key][\"y\"] },\n \"vertex3\": { \"x\": payload[key][\"x\"] + payload[key][\"w\"], \"y\": payload[key][\"y\"] + payload[key][\"h\"] },\n \"vertex4\": { \"x\": payload[key][\"x\"], \"y\": payload[key][\"y\"] + payload[key][\"h\"] },\n \"vertex_0\": { \"x\": payload[key][\"x\"], \"y\": payload[key][\"y\"] }\n }\n }\n if (payload[key].hasOwnProperty(\"type\") && payload[key][\"type\"].hasOwnProperty(\"label\")) {\n current_object_data[\"type\"] = payload[key][\"type\"][\"label\"];\n }\n if (payload[key].hasOwnProperty(\"color\") && payload[key][\"color\"].hasOwnProperty(\"label\")) {\n current_object_data[\"color\"] = payload[key][\"color\"][\"label\"];\n }\n if (payload[key].hasOwnProperty(\"license_plate\") && payload[key][\"license_plate\"].hasOwnProperty(\"label\")) {\n current_object_data[\"license_plate\"] = payload[key][\"license_plate\"][\"label\"];\n }\n if (payload[key].hasOwnProperty(\"id\")) {\n current_object_data[\"id\"] = payload[key][\"id\"];\n }\n if (payload[key].hasOwnProperty(\"roi_type\")) {\n current_object_data[\"roi_type\"] = payload[key][\"roi_type\"];\n }\n result[`object_${counter++}`] = current_object_data;\n }\n }\n}\n\nif (Object.keys(result).length > 0) {\n msg.payload = result;\n return msg;\n}", "outputs": 1, "timeout": 0, "noerr": 0, @@ -1159,7 +1159,7 @@ "type": "data extraction", "z": "2720733497fe05f3", "name": "data extraction 3", - "func": "let payload = msg.payload[\"metadata\"][\"objects\"];\nlet result = {};\nlet counter = 1;\n\nvar object_confidence = flow.get(\"object_confidence\");\nvar target_object = flow.get(\"target_object\");\nvar stop_duration = flow.get(\"stop_duration\");\n\nvar date = new Date();\nvar localDate = new Date(date.toLocaleString(\"en-US\", { timeZone: \"Asia/Kuala_Lumpur\" }));\n\nvar years = localDate.getFullYear();\nvar months = (localDate.getMonth() + 1).toString().padStart(2, \"0\");\nvar dates = (localDate.getDate()).toString().padStart(2, \"0\");\nvar hours = localDate.getHours();\nvar minutes = localDate.getMinutes();\nvar seconds = localDate.getSeconds();\n\nhours = (\"0\" + hours).slice(-2);\nminutes = (\"0\" + minutes).slice(-2);\nseconds = (\"0\" + seconds).slice(-2);\n\nmsg.nodered_timestamp = years + \"/\" + months + \"/\" + dates + \" \" + hours + \":\" + minutes + \":\" + seconds;\nmsg.object_timestamp = new Date().getTime() / 1000;\n\nfor (let key in payload) {\n if (payload.hasOwnProperty(key) && payload[key].hasOwnProperty(\"detection\")) {\n if (!(target_object.length == 1 && target_object[0] == \"\")) {\n var skip = true;\n for (let i = 0; i < target_object.length; i++) {\n if (payload[key][\"roi_type\"] == target_object[i]) {\n skip = false;\n break;\n }\n }\n if (skip == true) { continue; }\n }\n if (payload[key][\"detection\"][\"confidence\"] > object_confidence) {\n var current_object_data = {\n \"bbox\": {\n \"vertex1\": { \"x\": payload[key][\"x\"], \"y\": payload[key][\"y\"] },\n \"vertex2\": { \"x\": payload[key][\"x\"] + payload[key][\"w\"], \"y\": payload[key][\"y\"] },\n \"vertex3\": { \"x\": payload[key][\"x\"] + payload[key][\"w\"], \"y\": payload[key][\"y\"] + payload[key][\"h\"] },\n \"vertex4\": { \"x\": payload[key][\"x\"], \"y\": payload[key][\"y\"] + payload[key][\"h\"] },\n \"vertex_0\": { \"x\": payload[key][\"x\"], \"y\": payload[key][\"y\"] }\n }\n }\n if (payload[key].hasOwnProperty(\"type\") && payload[key][\"type\"].hasOwnProperty(\"label\")) {\n current_object_data[\"type\"] = payload[key][\"type\"][\"label\"];\n }\n if (payload[key].hasOwnProperty(\"color\") && payload[key][\"color\"].hasOwnProperty(\"label\")) {\n current_object_data[\"color\"] = payload[key][\"color\"][\"label\"];\n }\n if (payload[key].hasOwnProperty(\"license_plate\") && payload[key][\"license_plate\"].hasOwnProperty(\"label\")) {\n current_object_data[\"license_plate\"] = payload[key][\"license_plate\"][\"label\"];\n }\n if (payload[key].hasOwnProperty(\"id\")) {\n current_object_data[\"id\"] = payload[key][\"id\"];\n }\n if (payload[key].hasOwnProperty(\"roi_type\")) {\n current_object_data[\"roi_type\"] = payload[key][\"roi_type\"];\n }\n result[`object_${counter++}`] = current_object_data;\n }\n }\n}\n\nif (Object.keys(result).length > 0) {\n msg.payload = result;\n return msg;\n}", + "func": "let payload = msg.payload[\"metadata\"][\"objects\"];\nlet result = {};\nlet counter = 1;\n\nvar object_confidence = flow.get(\"object_confidence\");\nvar target_object = flow.get(\"target_object\");\nvar stop_duration = flow.get(\"stop_duration\");\n\nvar date = new Date();\nvar localDate = new Date();\n\nvar years = localDate.getFullYear();\nvar months = (localDate.getMonth() + 1).toString().padStart(2, \"0\");\nvar dates = (localDate.getDate()).toString().padStart(2, \"0\");\nvar hours = localDate.getHours();\nvar minutes = localDate.getMinutes();\nvar seconds = localDate.getSeconds();\n\nhours = (\"0\" + hours).slice(-2);\nminutes = (\"0\" + minutes).slice(-2);\nseconds = (\"0\" + seconds).slice(-2);\n\nmsg.nodered_timestamp = years + \"/\" + months + \"/\" + dates + \" \" + hours + \":\" + minutes + \":\" + seconds;\nmsg.object_timestamp = new Date().getTime() / 1000;\n\nfor (let key in payload) {\n if (payload.hasOwnProperty(key) && payload[key].hasOwnProperty(\"detection\")) {\n if (!(target_object.length == 1 && target_object[0] == \"\")) {\n var skip = true;\n for (let i = 0; i < target_object.length; i++) {\n if (payload[key][\"roi_type\"] == target_object[i]) {\n skip = false;\n break;\n }\n }\n if (skip == true) { continue; }\n }\n if (payload[key][\"detection\"][\"confidence\"] > object_confidence) {\n var current_object_data = {\n \"bbox\": {\n \"vertex1\": { \"x\": payload[key][\"x\"], \"y\": payload[key][\"y\"] },\n \"vertex2\": { \"x\": payload[key][\"x\"] + payload[key][\"w\"], \"y\": payload[key][\"y\"] },\n \"vertex3\": { \"x\": payload[key][\"x\"] + payload[key][\"w\"], \"y\": payload[key][\"y\"] + payload[key][\"h\"] },\n \"vertex4\": { \"x\": payload[key][\"x\"], \"y\": payload[key][\"y\"] + payload[key][\"h\"] },\n \"vertex_0\": { \"x\": payload[key][\"x\"], \"y\": payload[key][\"y\"] }\n }\n }\n if (payload[key].hasOwnProperty(\"type\") && payload[key][\"type\"].hasOwnProperty(\"label\")) {\n current_object_data[\"type\"] = payload[key][\"type\"][\"label\"];\n }\n if (payload[key].hasOwnProperty(\"color\") && payload[key][\"color\"].hasOwnProperty(\"label\")) {\n current_object_data[\"color\"] = payload[key][\"color\"][\"label\"];\n }\n if (payload[key].hasOwnProperty(\"license_plate\") && payload[key][\"license_plate\"].hasOwnProperty(\"label\")) {\n current_object_data[\"license_plate\"] = payload[key][\"license_plate\"][\"label\"];\n }\n if (payload[key].hasOwnProperty(\"id\")) {\n current_object_data[\"id\"] = payload[key][\"id\"];\n }\n if (payload[key].hasOwnProperty(\"roi_type\")) {\n current_object_data[\"roi_type\"] = payload[key][\"roi_type\"];\n }\n result[`object_${counter++}`] = current_object_data;\n }\n }\n}\n\nif (Object.keys(result).length > 0) {\n msg.payload = result;\n return msg;\n}", "outputs": 1, "timeout": 0, "noerr": 0, @@ -1218,7 +1218,7 @@ "type": "data extraction", "z": "2720733497fe05f3", "name": "data extraction 4", - "func": "let payload = msg.payload[\"metadata\"][\"objects\"];\nlet result = {};\nlet counter = 1;\n\nvar object_confidence = flow.get(\"object_confidence\");\nvar target_object = flow.get(\"target_object\");\nvar stop_duration = flow.get(\"stop_duration\");\n\nvar date = new Date();\nvar localDate = new Date(date.toLocaleString(\"en-US\", { timeZone: \"Asia/Kuala_Lumpur\" }));\n\nvar years = localDate.getFullYear();\nvar months = (localDate.getMonth() + 1).toString().padStart(2, \"0\");\nvar dates = (localDate.getDate()).toString().padStart(2, \"0\");\nvar hours = localDate.getHours();\nvar minutes = localDate.getMinutes();\nvar seconds = localDate.getSeconds();\n\nhours = (\"0\" + hours).slice(-2);\nminutes = (\"0\" + minutes).slice(-2);\nseconds = (\"0\" + seconds).slice(-2);\n\nmsg.nodered_timestamp = years + \"/\" + months + \"/\" + dates + \" \" + hours + \":\" + minutes + \":\" + seconds;\nmsg.object_timestamp = new Date().getTime() / 1000;\n\nfor (let key in payload) {\n if (payload.hasOwnProperty(key) && payload[key].hasOwnProperty(\"detection\")) {\n if (!(target_object.length == 1 && target_object[0] == \"\")) {\n var skip = true;\n for (let i = 0; i < target_object.length; i++) {\n if (payload[key][\"roi_type\"] == target_object[i]) {\n skip = false;\n break;\n }\n }\n if (skip == true) { continue; }\n }\n if (payload[key][\"detection\"][\"confidence\"] > object_confidence) {\n var current_object_data = {\n \"bbox\": {\n \"vertex1\": { \"x\": payload[key][\"x\"], \"y\": payload[key][\"y\"] },\n \"vertex2\": { \"x\": payload[key][\"x\"] + payload[key][\"w\"], \"y\": payload[key][\"y\"] },\n \"vertex3\": { \"x\": payload[key][\"x\"] + payload[key][\"w\"], \"y\": payload[key][\"y\"] + payload[key][\"h\"] },\n \"vertex4\": { \"x\": payload[key][\"x\"], \"y\": payload[key][\"y\"] + payload[key][\"h\"] },\n \"vertex_0\": { \"x\": payload[key][\"x\"], \"y\": payload[key][\"y\"] }\n }\n }\n if (payload[key].hasOwnProperty(\"type\") && payload[key][\"type\"].hasOwnProperty(\"label\")) {\n current_object_data[\"type\"] = payload[key][\"type\"][\"label\"];\n }\n if (payload[key].hasOwnProperty(\"color\") && payload[key][\"color\"].hasOwnProperty(\"label\")) {\n current_object_data[\"color\"] = payload[key][\"color\"][\"label\"];\n }\n if (payload[key].hasOwnProperty(\"license_plate\") && payload[key][\"license_plate\"].hasOwnProperty(\"label\")) {\n current_object_data[\"license_plate\"] = payload[key][\"license_plate\"][\"label\"];\n }\n if (payload[key].hasOwnProperty(\"id\")) {\n current_object_data[\"id\"] = payload[key][\"id\"];\n }\n if (payload[key].hasOwnProperty(\"roi_type\")) {\n current_object_data[\"roi_type\"] = payload[key][\"roi_type\"];\n }\n result[`object_${counter++}`] = current_object_data;\n }\n }\n}\n\nif (Object.keys(result).length > 0) {\n msg.payload = result;\n return msg;\n}", + "func": "let payload = msg.payload[\"metadata\"][\"objects\"];\nlet result = {};\nlet counter = 1;\n\nvar object_confidence = flow.get(\"object_confidence\");\nvar target_object = flow.get(\"target_object\");\nvar stop_duration = flow.get(\"stop_duration\");\n\nvar date = new Date();\nvar localDate = new Date();\n\nvar years = localDate.getFullYear();\nvar months = (localDate.getMonth() + 1).toString().padStart(2, \"0\");\nvar dates = (localDate.getDate()).toString().padStart(2, \"0\");\nvar hours = localDate.getHours();\nvar minutes = localDate.getMinutes();\nvar seconds = localDate.getSeconds();\n\nhours = (\"0\" + hours).slice(-2);\nminutes = (\"0\" + minutes).slice(-2);\nseconds = (\"0\" + seconds).slice(-2);\n\nmsg.nodered_timestamp = years + \"/\" + months + \"/\" + dates + \" \" + hours + \":\" + minutes + \":\" + seconds;\nmsg.object_timestamp = new Date().getTime() / 1000;\n\nfor (let key in payload) {\n if (payload.hasOwnProperty(key) && payload[key].hasOwnProperty(\"detection\")) {\n if (!(target_object.length == 1 && target_object[0] == \"\")) {\n var skip = true;\n for (let i = 0; i < target_object.length; i++) {\n if (payload[key][\"roi_type\"] == target_object[i]) {\n skip = false;\n break;\n }\n }\n if (skip == true) { continue; }\n }\n if (payload[key][\"detection\"][\"confidence\"] > object_confidence) {\n var current_object_data = {\n \"bbox\": {\n \"vertex1\": { \"x\": payload[key][\"x\"], \"y\": payload[key][\"y\"] },\n \"vertex2\": { \"x\": payload[key][\"x\"] + payload[key][\"w\"], \"y\": payload[key][\"y\"] },\n \"vertex3\": { \"x\": payload[key][\"x\"] + payload[key][\"w\"], \"y\": payload[key][\"y\"] + payload[key][\"h\"] },\n \"vertex4\": { \"x\": payload[key][\"x\"], \"y\": payload[key][\"y\"] + payload[key][\"h\"] },\n \"vertex_0\": { \"x\": payload[key][\"x\"], \"y\": payload[key][\"y\"] }\n }\n }\n if (payload[key].hasOwnProperty(\"type\") && payload[key][\"type\"].hasOwnProperty(\"label\")) {\n current_object_data[\"type\"] = payload[key][\"type\"][\"label\"];\n }\n if (payload[key].hasOwnProperty(\"color\") && payload[key][\"color\"].hasOwnProperty(\"label\")) {\n current_object_data[\"color\"] = payload[key][\"color\"][\"label\"];\n }\n if (payload[key].hasOwnProperty(\"license_plate\") && payload[key][\"license_plate\"].hasOwnProperty(\"label\")) {\n current_object_data[\"license_plate\"] = payload[key][\"license_plate\"][\"label\"];\n }\n if (payload[key].hasOwnProperty(\"id\")) {\n current_object_data[\"id\"] = payload[key][\"id\"];\n }\n if (payload[key].hasOwnProperty(\"roi_type\")) {\n current_object_data[\"roi_type\"] = payload[key][\"roi_type\"];\n }\n result[`object_${counter++}`] = current_object_data;\n }\n }\n}\n\nif (Object.keys(result).length > 0) {\n msg.payload = result;\n return msg;\n}", "outputs": 1, "timeout": 0, "noerr": 0, @@ -1379,7 +1379,7 @@ "type": "delay", "z": "2720733497fe05f3", "name": "", - "pauseType": "delay", + "pauseType": "rate", "timeout": "0", "timeoutUnits": "seconds", "rate": "1", @@ -1388,7 +1388,7 @@ "randomFirst": "1", "randomLast": "5", "randomUnits": "seconds", - "drop": false, + "drop": true, "allowrate": false, "outputs": 1, "x": 1100, @@ -1404,7 +1404,7 @@ "type": "delay", "z": "2720733497fe05f3", "name": "", - "pauseType": "delay", + "pauseType": "rate", "timeout": "0", "timeoutUnits": "seconds", "rate": "1", @@ -1413,7 +1413,7 @@ "randomFirst": "1", "randomLast": "5", "randomUnits": "seconds", - "drop": false, + "drop": true, "allowrate": false, "outputs": 1, "x": 1100, @@ -1429,7 +1429,7 @@ "type": "delay", "z": "2720733497fe05f3", "name": "", - "pauseType": "delay", + "pauseType": "rate", "timeout": "0", "timeoutUnits": "seconds", "rate": "1", @@ -1438,7 +1438,7 @@ "randomFirst": "1", "randomLast": "5", "randomUnits": "seconds", - "drop": false, + "drop": true, "allowrate": false, "outputs": 1, "x": 1100, @@ -1454,7 +1454,7 @@ "type": "delay", "z": "2720733497fe05f3", "name": "", - "pauseType": "delay", + "pauseType": "rate", "timeout": "0", "timeoutUnits": "seconds", "rate": "1", @@ -1463,7 +1463,7 @@ "randomFirst": "1", "randomLast": "5", "randomUnits": "seconds", - "drop": false, + "drop": true, "allowrate": false, "outputs": 1, "x": 1100, @@ -1517,7 +1517,7 @@ "type": "delay", "z": "2720733497fe05f3", "name": "", - "pauseType": "delay", + "pauseType": "rate", "timeout": "0", "timeoutUnits": "seconds", "rate": "1", @@ -1526,7 +1526,7 @@ "randomFirst": "1", "randomLast": "5", "randomUnits": "seconds", - "drop": false, + "drop": true, "allowrate": false, "outputs": 1, "x": 1320, diff --git a/metro-ai-suite/metro-vision-ai-app-recipe/loitering-detection/src/node-red/nodes/core/function/zc1-ri-data-extraction.html b/metro-ai-suite/metro-vision-ai-app-recipe/loitering-detection/src/node-red/nodes/core/function/zc1-ri-data-extraction.html index 1fe64f520d..0e62c72adf 100644 --- a/metro-ai-suite/metro-vision-ai-app-recipe/loitering-detection/src/node-red/nodes/core/function/zc1-ri-data-extraction.html +++ b/metro-ai-suite/metro-vision-ai-app-recipe/loitering-detection/src/node-red/nodes/core/function/zc1-ri-data-extraction.html @@ -364,7 +364,7 @@ category: 'rapid ri', defaults: { name: {value:"_DEFAULT_"}, - func: {value:"let payload = msg.payload[\"objects\"];\nlet result = {};\nlet counter = 1;\n\nvar object_confidence = flow.get(\"object_confidence\");\nvar target_object = flow.get(\"target_object\");\nvar stop_duration = flow.get(\"stop_duration\");\n\nvar date = new Date();\nvar localDate = new Date(date.toLocaleString(\"en-US\", { timeZone: \"Asia/Kuala_Lumpur\" }));\n\nvar years = localDate.getFullYear();\nvar months = (localDate.getMonth() + 1).toString().padStart(2, \"0\");\nvar dates = (localDate.getDate()).toString().padStart(2, \"0\");\nvar hours = localDate.getHours();\nvar minutes = localDate.getMinutes();\nvar seconds = localDate.getSeconds();\n\nhours = (\"0\" + hours).slice(-2);\nminutes = (\"0\" + minutes).slice(-2);\nseconds = (\"0\" + seconds).slice(-2);\n\nmsg.nodered_timestamp = years + \"/\" + months + \"/\" + dates + \" \" + hours + \":\" + minutes + \":\" + seconds;\nmsg.object_timestamp = new Date().getTime() / 1000;\n\nfor (let key in payload) {\n if (payload.hasOwnProperty(key) && payload[key].hasOwnProperty(\"detection\")) {\n if (!(target_object.length == 1 && target_object[0] == \"\")) {\n var skip = true;\n for (let i = 0; i < target_object.length; i++) {\n if (payload[key][\"roi_type\"] == target_object[i]) {\n skip = false;\n break;\n }\n }\n if (skip == true) { continue; }\n }\n if (payload[key][\"detection\"][\"confidence\"] > object_confidence) {\n var current_object_data = {\n \"bbox\": {\n \"vertex1\": { \"x\": payload[key][\"x\"], \"y\": payload[key][\"y\"] },\n \"vertex2\": { \"x\": payload[key][\"x\"] + payload[key][\"w\"], \"y\": payload[key][\"y\"] },\n \"vertex3\": { \"x\": payload[key][\"x\"] + payload[key][\"w\"], \"y\": payload[key][\"y\"] + payload[key][\"h\"] },\n \"vertex4\": { \"x\": payload[key][\"x\"], \"y\": payload[key][\"y\"] + payload[key][\"h\"] },\n \"vertex_0\": { \"x\": payload[key][\"x\"], \"y\": payload[key][\"y\"] }\n }\n }\n if (payload[key].hasOwnProperty(\"type\") && payload[key][\"type\"].hasOwnProperty(\"label\")) {\n current_object_data[\"type\"] = payload[key][\"type\"][\"label\"];\n }\n if (payload[key].hasOwnProperty(\"color\") && payload[key][\"color\"].hasOwnProperty(\"label\")) {\n current_object_data[\"color\"] = payload[key][\"color\"][\"label\"];\n }\n if (payload[key].hasOwnProperty(\"license_plate\") && payload[key][\"license_plate\"].hasOwnProperty(\"label\")) {\n current_object_data[\"license_plate\"] = payload[key][\"license_plate\"][\"label\"];\n }\n if (payload[key].hasOwnProperty(\"id\")) {\n current_object_data[\"id\"] = payload[key][\"id\"];\n }\n if (payload[key].hasOwnProperty(\"roi_type\")) {\n current_object_data[\"roi_type\"] = payload[key][\"roi_type\"];\n }\n result[`object_${counter++}`] = current_object_data;\n }\n }\n}\n\nif (Object.keys(result).length > 0) {\n msg.payload = result;\n return msg;\n}"}, + func: {value:"let payload = msg.payload[\"objects\"];\nlet result = {};\nlet counter = 1;\n\nvar object_confidence = flow.get(\"object_confidence\");\nvar target_object = flow.get(\"target_object\");\nvar stop_duration = flow.get(\"stop_duration\");\n\nvar date = new Date();\nvar localDate = new Date();\n\nvar years = localDate.getFullYear();\nvar months = (localDate.getMonth() + 1).toString().padStart(2, \"0\");\nvar dates = (localDate.getDate()).toString().padStart(2, \"0\");\nvar hours = localDate.getHours();\nvar minutes = localDate.getMinutes();\nvar seconds = localDate.getSeconds();\n\nhours = (\"0\" + hours).slice(-2);\nminutes = (\"0\" + minutes).slice(-2);\nseconds = (\"0\" + seconds).slice(-2);\n\nmsg.nodered_timestamp = years + \"/\" + months + \"/\" + dates + \" \" + hours + \":\" + minutes + \":\" + seconds;\nmsg.object_timestamp = new Date().getTime() / 1000;\n\nfor (let key in payload) {\n if (payload.hasOwnProperty(key) && payload[key].hasOwnProperty(\"detection\")) {\n if (!(target_object.length == 1 && target_object[0] == \"\")) {\n var skip = true;\n for (let i = 0; i < target_object.length; i++) {\n if (payload[key][\"roi_type\"] == target_object[i]) {\n skip = false;\n break;\n }\n }\n if (skip == true) { continue; }\n }\n if (payload[key][\"detection\"][\"confidence\"] > object_confidence) {\n var current_object_data = {\n \"bbox\": {\n \"vertex1\": { \"x\": payload[key][\"x\"], \"y\": payload[key][\"y\"] },\n \"vertex2\": { \"x\": payload[key][\"x\"] + payload[key][\"w\"], \"y\": payload[key][\"y\"] },\n \"vertex3\": { \"x\": payload[key][\"x\"] + payload[key][\"w\"], \"y\": payload[key][\"y\"] + payload[key][\"h\"] },\n \"vertex4\": { \"x\": payload[key][\"x\"], \"y\": payload[key][\"y\"] + payload[key][\"h\"] },\n \"vertex_0\": { \"x\": payload[key][\"x\"], \"y\": payload[key][\"y\"] }\n }\n }\n if (payload[key].hasOwnProperty(\"type\") && payload[key][\"type\"].hasOwnProperty(\"label\")) {\n current_object_data[\"type\"] = payload[key][\"type\"][\"label\"];\n }\n if (payload[key].hasOwnProperty(\"color\") && payload[key][\"color\"].hasOwnProperty(\"label\")) {\n current_object_data[\"color\"] = payload[key][\"color\"][\"label\"];\n }\n if (payload[key].hasOwnProperty(\"license_plate\") && payload[key][\"license_plate\"].hasOwnProperty(\"label\")) {\n current_object_data[\"license_plate\"] = payload[key][\"license_plate\"][\"label\"];\n }\n if (payload[key].hasOwnProperty(\"id\")) {\n current_object_data[\"id\"] = payload[key][\"id\"];\n }\n if (payload[key].hasOwnProperty(\"roi_type\")) {\n current_object_data[\"roi_type\"] = payload[key][\"roi_type\"];\n }\n result[`object_${counter++}`] = current_object_data;\n }\n }\n}\n\nif (Object.keys(result).length > 0) {\n msg.payload = result;\n return msg;\n}"}, outputs: {value:1}, timeout:{value:RED.settings.functionTimeout || 0}, noerr: {value:0,required:true, diff --git a/metro-ai-suite/metro-vision-ai-app-recipe/smart-parking/helm-chart/config/grafana/dashboards/visualizer.json b/metro-ai-suite/metro-vision-ai-app-recipe/smart-parking/helm-chart/config/grafana/dashboards/visualizer.json index 10699e96b2..c544548da3 100644 --- a/metro-ai-suite/metro-vision-ai-app-recipe/smart-parking/helm-chart/config/grafana/dashboards/visualizer.json +++ b/metro-ai-suite/metro-vision-ai-app-recipe/smart-parking/helm-chart/config/grafana/dashboards/visualizer.json @@ -39,7 +39,7 @@ "showLineNumbers": false, "showMiniMap": false }, - "content": " ", + "content": " ", "mode": "html" }, "pluginVersion": "11.5.4", @@ -64,7 +64,7 @@ "showLineNumbers": false, "showMiniMap": false }, - "content": " ", + "content": " ", "mode": "html" }, "pluginVersion": "11.5.4", @@ -89,7 +89,7 @@ "showLineNumbers": false, "showMiniMap": false }, - "content": " ", + "content": " ", "mode": "html" }, "pluginVersion": "11.5.4", @@ -114,7 +114,7 @@ "showLineNumbers": false, "showMiniMap": false }, - "content": " ", + "content": " ", "mode": "html" }, "pluginVersion": "11.5.4", @@ -189,9 +189,13 @@ "title": "Occupancy Status", "transformations": [ { - "id": "limit", + "id": "reduce", "options": { - "limitField": "1" + "reducers": [ + "lastNotNull" + ], + "mode": "reduceFields", + "includeTimeField": false } }, { diff --git a/metro-ai-suite/metro-vision-ai-app-recipe/smart-parking/src/grafana/dashboards/visualizer.json b/metro-ai-suite/metro-vision-ai-app-recipe/smart-parking/src/grafana/dashboards/visualizer.json index f3ebc5eff5..c544548da3 100644 --- a/metro-ai-suite/metro-vision-ai-app-recipe/smart-parking/src/grafana/dashboards/visualizer.json +++ b/metro-ai-suite/metro-vision-ai-app-recipe/smart-parking/src/grafana/dashboards/visualizer.json @@ -189,9 +189,13 @@ "title": "Occupancy Status", "transformations": [ { - "id": "limit", + "id": "reduce", "options": { - "limitField": "1" + "reducers": [ + "lastNotNull" + ], + "mode": "reduceFields", + "includeTimeField": false } }, { diff --git a/metro-ai-suite/metro-vision-ai-app-recipe/smart-parking/src/node-red/flows.json b/metro-ai-suite/metro-vision-ai-app-recipe/smart-parking/src/node-red/flows.json index 4f3448652f..c81b0fd234 100644 --- a/metro-ai-suite/metro-vision-ai-app-recipe/smart-parking/src/node-red/flows.json +++ b/metro-ai-suite/metro-vision-ai-app-recipe/smart-parking/src/node-red/flows.json @@ -1227,7 +1227,7 @@ "type": "data extraction", "z": "2720733497fe05f3", "name": "data extraction 1", - "func": "let payload = msg.payload[\"metadata\"][\"objects\"];\nlet result = {};\nlet counter = 1;\n\nvar object_confidence = flow.get(\"object_confidence\");\nvar target_object = flow.get(\"target_object\");\nvar stop_duration = flow.get(\"stop_duration\");\n\nvar date = new Date();\nvar localDate = new Date(date.toLocaleString(\"en-US\", { timeZone: \"Asia/Kuala_Lumpur\" }));\n\nvar years = localDate.getFullYear();\nvar months = (localDate.getMonth() + 1).toString().padStart(2, \"0\");\nvar dates = (localDate.getDate()).toString().padStart(2, \"0\");\nvar hours = localDate.getHours();\nvar minutes = localDate.getMinutes();\nvar seconds = localDate.getSeconds();\n\nhours = (\"0\" + hours).slice(-2);\nminutes = (\"0\" + minutes).slice(-2);\nseconds = (\"0\" + seconds).slice(-2);\n\nmsg.nodered_timestamp = years + \"/\" + months + \"/\" + dates + \" \" + hours + \":\" + minutes + \":\" + seconds;\nmsg.object_timestamp = new Date().getTime() / 1000;\n\nfor (let key in payload) {\n if (payload.hasOwnProperty(key) && payload[key].hasOwnProperty(\"detection\")) {\n if (!(target_object.length == 1 && target_object[0] == \"\")) {\n var skip = true;\n for (let i = 0; i < target_object.length; i++) {\n if (payload[key][\"roi_type\"] == target_object[i]) {\n skip = false;\n break;\n }\n }\n if (skip == true) { continue; }\n }\n if (payload[key][\"detection\"][\"confidence\"] > object_confidence) {\n var current_object_data = {\n \"bbox\": {\n \"vertex1\": { \"x\": payload[key][\"x\"], \"y\": payload[key][\"y\"] },\n \"vertex2\": { \"x\": payload[key][\"x\"] + payload[key][\"w\"], \"y\": payload[key][\"y\"] },\n \"vertex3\": { \"x\": payload[key][\"x\"] + payload[key][\"w\"], \"y\": payload[key][\"y\"] + payload[key][\"h\"] },\n \"vertex4\": { \"x\": payload[key][\"x\"], \"y\": payload[key][\"y\"] + payload[key][\"h\"] },\n \"vertex_0\": { \"x\": payload[key][\"x\"], \"y\": payload[key][\"y\"] }\n }\n }\n // Extract type from detection.label\n if (payload[key].hasOwnProperty(\"detection\") && payload[key][\"detection\"].hasOwnProperty(\"label\")) {\n current_object_data[\"type\"] = payload[key][\"detection\"][\"label\"];\n }\n // Extract color from classification_layer_name:output.label\n if (payload[key].hasOwnProperty(\"classification_layer_name:output\") && payload[key][\"classification_layer_name:output\"].hasOwnProperty(\"label\")) {\n current_object_data[\"color\"] = payload[key][\"classification_layer_name:output\"][\"label\"];\n }\n // Extract license plate (if exists in future)\n if (payload[key].hasOwnProperty(\"license_plate\") && payload[key][\"license_plate\"].hasOwnProperty(\"label\")) {\n current_object_data[\"license_plate\"] = payload[key][\"license_plate\"][\"label\"];\n }\n // Extract tracking ID (if exists)\n if (payload[key].hasOwnProperty(\"id\")) {\n current_object_data[\"id\"] = payload[key][\"id\"];\n }\n // Extract ROI type\n if (payload[key].hasOwnProperty(\"roi_type\")) {\n current_object_data[\"roi_type\"] = payload[key][\"roi_type\"];\n }\n result[`object_${counter++}`] = current_object_data;\n }\n }\n}\n\nif (Object.keys(result).length > 0) {\n msg.payload = result;\n return msg;\n}", + "func": "let payload = msg.payload[\"metadata\"][\"objects\"];\nlet result = {};\nlet counter = 1;\n\nvar object_confidence = flow.get(\"object_confidence\");\nvar target_object = flow.get(\"target_object\");\nvar stop_duration = flow.get(\"stop_duration\");\n\nvar date = new Date();\nvar localDate = new Date();\n\nvar years = localDate.getFullYear();\nvar months = (localDate.getMonth() + 1).toString().padStart(2, \"0\");\nvar dates = (localDate.getDate()).toString().padStart(2, \"0\");\nvar hours = localDate.getHours();\nvar minutes = localDate.getMinutes();\nvar seconds = localDate.getSeconds();\n\nhours = (\"0\" + hours).slice(-2);\nminutes = (\"0\" + minutes).slice(-2);\nseconds = (\"0\" + seconds).slice(-2);\n\nmsg.nodered_timestamp = years + \"/\" + months + \"/\" + dates + \" \" + hours + \":\" + minutes + \":\" + seconds;\nmsg.object_timestamp = new Date().getTime() / 1000;\n\nfor (let key in payload) {\n if (payload.hasOwnProperty(key) && payload[key].hasOwnProperty(\"detection\")) {\n if (!(target_object.length == 1 && target_object[0] == \"\")) {\n var skip = true;\n for (let i = 0; i < target_object.length; i++) {\n if (payload[key][\"roi_type\"] == target_object[i]) {\n skip = false;\n break;\n }\n }\n if (skip == true) { continue; }\n }\n if (payload[key][\"detection\"][\"confidence\"] > object_confidence) {\n var current_object_data = {\n \"bbox\": {\n \"vertex1\": { \"x\": payload[key][\"x\"], \"y\": payload[key][\"y\"] },\n \"vertex2\": { \"x\": payload[key][\"x\"] + payload[key][\"w\"], \"y\": payload[key][\"y\"] },\n \"vertex3\": { \"x\": payload[key][\"x\"] + payload[key][\"w\"], \"y\": payload[key][\"y\"] + payload[key][\"h\"] },\n \"vertex4\": { \"x\": payload[key][\"x\"], \"y\": payload[key][\"y\"] + payload[key][\"h\"] },\n \"vertex_0\": { \"x\": payload[key][\"x\"], \"y\": payload[key][\"y\"] }\n }\n }\n // Extract type from detection.label\n if (payload[key].hasOwnProperty(\"detection\") && payload[key][\"detection\"].hasOwnProperty(\"label\")) {\n current_object_data[\"type\"] = payload[key][\"detection\"][\"label\"];\n }\n // Extract color from classification_layer_name:output.label\n if (payload[key].hasOwnProperty(\"classification_layer_name:output\") && payload[key][\"classification_layer_name:output\"].hasOwnProperty(\"label\")) {\n current_object_data[\"color\"] = payload[key][\"classification_layer_name:output\"][\"label\"];\n }\n // Extract license plate (if exists in future)\n if (payload[key].hasOwnProperty(\"license_plate\") && payload[key][\"license_plate\"].hasOwnProperty(\"label\")) {\n current_object_data[\"license_plate\"] = payload[key][\"license_plate\"][\"label\"];\n }\n // Extract tracking ID (if exists)\n if (payload[key].hasOwnProperty(\"id\")) {\n current_object_data[\"id\"] = payload[key][\"id\"];\n }\n // Extract ROI type\n if (payload[key].hasOwnProperty(\"roi_type\")) {\n current_object_data[\"roi_type\"] = payload[key][\"roi_type\"];\n }\n result[`object_${counter++}`] = current_object_data;\n }\n }\n}\n\nif (Object.keys(result).length > 0) {\n msg.payload = result;\n return msg;\n}", "outputs": 1, "timeout": 0, "noerr": 0, @@ -1247,7 +1247,7 @@ "type": "data extraction", "z": "2720733497fe05f3", "name": "data extraction 2", - "func": "let payload = msg.payload[\"metadata\"][\"objects\"];\nlet result = {};\nlet counter = 1;\n\nvar object_confidence = flow.get(\"object_confidence\");\nvar target_object = flow.get(\"target_object\");\nvar stop_duration = flow.get(\"stop_duration\");\n\nvar date = new Date();\nvar localDate = new Date(date.toLocaleString(\"en-US\", { timeZone: \"Asia/Kuala_Lumpur\" }));\n\nvar years = localDate.getFullYear();\nvar months = (localDate.getMonth() + 1).toString().padStart(2, \"0\");\nvar dates = (localDate.getDate()).toString().padStart(2, \"0\");\nvar hours = localDate.getHours();\nvar minutes = localDate.getMinutes();\nvar seconds = localDate.getSeconds();\n\nhours = (\"0\" + hours).slice(-2);\nminutes = (\"0\" + minutes).slice(-2);\nseconds = (\"0\" + seconds).slice(-2);\n\nmsg.nodered_timestamp = years + \"/\" + months + \"/\" + dates + \" \" + hours + \":\" + minutes + \":\" + seconds;\nmsg.object_timestamp = new Date().getTime() / 1000;\n\nfor (let key in payload) {\n if (payload.hasOwnProperty(key) && payload[key].hasOwnProperty(\"detection\")) {\n if (!(target_object.length == 1 && target_object[0] == \"\")) {\n var skip = true;\n for (let i = 0; i < target_object.length; i++) {\n if (payload[key][\"roi_type\"] == target_object[i]) {\n skip = false;\n break;\n }\n }\n if (skip == true) { continue; }\n }\n if (payload[key][\"detection\"][\"confidence\"] > object_confidence) {\n var current_object_data = {\n \"bbox\": {\n \"vertex1\": { \"x\": payload[key][\"x\"], \"y\": payload[key][\"y\"] },\n \"vertex2\": { \"x\": payload[key][\"x\"] + payload[key][\"w\"], \"y\": payload[key][\"y\"] },\n \"vertex3\": { \"x\": payload[key][\"x\"] + payload[key][\"w\"], \"y\": payload[key][\"y\"] + payload[key][\"h\"] },\n \"vertex4\": { \"x\": payload[key][\"x\"], \"y\": payload[key][\"y\"] + payload[key][\"h\"] },\n \"vertex_0\": { \"x\": payload[key][\"x\"], \"y\": payload[key][\"y\"] }\n }\n }\n // Extract type from detection.label\n if (payload[key].hasOwnProperty(\"detection\") && payload[key][\"detection\"].hasOwnProperty(\"label\")) {\n current_object_data[\"type\"] = payload[key][\"detection\"][\"label\"];\n }\n // Extract color from classification_layer_name:output.label\n if (payload[key].hasOwnProperty(\"classification_layer_name:output\") && payload[key][\"classification_layer_name:output\"].hasOwnProperty(\"label\")) {\n current_object_data[\"color\"] = payload[key][\"classification_layer_name:output\"][\"label\"];\n }\n // Extract license plate (if exists in future)\n if (payload[key].hasOwnProperty(\"license_plate\") && payload[key][\"license_plate\"].hasOwnProperty(\"label\")) {\n current_object_data[\"license_plate\"] = payload[key][\"license_plate\"][\"label\"];\n }\n // Extract tracking ID (if exists)\n if (payload[key].hasOwnProperty(\"id\")) {\n current_object_data[\"id\"] = payload[key][\"id\"];\n }\n // Extract ROI type\n if (payload[key].hasOwnProperty(\"roi_type\")) {\n current_object_data[\"roi_type\"] = payload[key][\"roi_type\"];\n }\n result[`object_${counter++}`] = current_object_data;\n }\n }\n}\n\nif (Object.keys(result).length > 0) {\n msg.payload = result;\n return msg;\n}", + "func": "let payload = msg.payload[\"metadata\"][\"objects\"];\nlet result = {};\nlet counter = 1;\n\nvar object_confidence = flow.get(\"object_confidence\");\nvar target_object = flow.get(\"target_object\");\nvar stop_duration = flow.get(\"stop_duration\");\n\nvar date = new Date();\nvar localDate = new Date();\n\nvar years = localDate.getFullYear();\nvar months = (localDate.getMonth() + 1).toString().padStart(2, \"0\");\nvar dates = (localDate.getDate()).toString().padStart(2, \"0\");\nvar hours = localDate.getHours();\nvar minutes = localDate.getMinutes();\nvar seconds = localDate.getSeconds();\n\nhours = (\"0\" + hours).slice(-2);\nminutes = (\"0\" + minutes).slice(-2);\nseconds = (\"0\" + seconds).slice(-2);\n\nmsg.nodered_timestamp = years + \"/\" + months + \"/\" + dates + \" \" + hours + \":\" + minutes + \":\" + seconds;\nmsg.object_timestamp = new Date().getTime() / 1000;\n\nfor (let key in payload) {\n if (payload.hasOwnProperty(key) && payload[key].hasOwnProperty(\"detection\")) {\n if (!(target_object.length == 1 && target_object[0] == \"\")) {\n var skip = true;\n for (let i = 0; i < target_object.length; i++) {\n if (payload[key][\"roi_type\"] == target_object[i]) {\n skip = false;\n break;\n }\n }\n if (skip == true) { continue; }\n }\n if (payload[key][\"detection\"][\"confidence\"] > object_confidence) {\n var current_object_data = {\n \"bbox\": {\n \"vertex1\": { \"x\": payload[key][\"x\"], \"y\": payload[key][\"y\"] },\n \"vertex2\": { \"x\": payload[key][\"x\"] + payload[key][\"w\"], \"y\": payload[key][\"y\"] },\n \"vertex3\": { \"x\": payload[key][\"x\"] + payload[key][\"w\"], \"y\": payload[key][\"y\"] + payload[key][\"h\"] },\n \"vertex4\": { \"x\": payload[key][\"x\"], \"y\": payload[key][\"y\"] + payload[key][\"h\"] },\n \"vertex_0\": { \"x\": payload[key][\"x\"], \"y\": payload[key][\"y\"] }\n }\n }\n // Extract type from detection.label\n if (payload[key].hasOwnProperty(\"detection\") && payload[key][\"detection\"].hasOwnProperty(\"label\")) {\n current_object_data[\"type\"] = payload[key][\"detection\"][\"label\"];\n }\n // Extract color from classification_layer_name:output.label\n if (payload[key].hasOwnProperty(\"classification_layer_name:output\") && payload[key][\"classification_layer_name:output\"].hasOwnProperty(\"label\")) {\n current_object_data[\"color\"] = payload[key][\"classification_layer_name:output\"][\"label\"];\n }\n // Extract license plate (if exists in future)\n if (payload[key].hasOwnProperty(\"license_plate\") && payload[key][\"license_plate\"].hasOwnProperty(\"label\")) {\n current_object_data[\"license_plate\"] = payload[key][\"license_plate\"][\"label\"];\n }\n // Extract tracking ID (if exists)\n if (payload[key].hasOwnProperty(\"id\")) {\n current_object_data[\"id\"] = payload[key][\"id\"];\n }\n // Extract ROI type\n if (payload[key].hasOwnProperty(\"roi_type\")) {\n current_object_data[\"roi_type\"] = payload[key][\"roi_type\"];\n }\n result[`object_${counter++}`] = current_object_data;\n }\n }\n}\n\nif (Object.keys(result).length > 0) {\n msg.payload = result;\n return msg;\n}", "outputs": 1, "timeout": 0, "noerr": 0, @@ -1267,7 +1267,7 @@ "type": "data extraction", "z": "2720733497fe05f3", "name": "data extraction 3", - "func": "let payload = msg.payload[\"metadata\"][\"objects\"];\nlet result = {};\nlet counter = 1;\n\nvar object_confidence = flow.get(\"object_confidence\");\nvar target_object = flow.get(\"target_object\");\nvar stop_duration = flow.get(\"stop_duration\");\n\nvar date = new Date();\nvar localDate = new Date(date.toLocaleString(\"en-US\", { timeZone: \"Asia/Kuala_Lumpur\" }));\n\nvar years = localDate.getFullYear();\nvar months = (localDate.getMonth() + 1).toString().padStart(2, \"0\");\nvar dates = (localDate.getDate()).toString().padStart(2, \"0\");\nvar hours = localDate.getHours();\nvar minutes = localDate.getMinutes();\nvar seconds = localDate.getSeconds();\n\nhours = (\"0\" + hours).slice(-2);\nminutes = (\"0\" + minutes).slice(-2);\nseconds = (\"0\" + seconds).slice(-2);\n\nmsg.nodered_timestamp = years + \"/\" + months + \"/\" + dates + \" \" + hours + \":\" + minutes + \":\" + seconds;\nmsg.object_timestamp = new Date().getTime() / 1000;\n\nfor (let key in payload) {\n if (payload.hasOwnProperty(key) && payload[key].hasOwnProperty(\"detection\")) {\n if (!(target_object.length == 1 && target_object[0] == \"\")) {\n var skip = true;\n for (let i = 0; i < target_object.length; i++) {\n if (payload[key][\"roi_type\"] == target_object[i]) {\n skip = false;\n break;\n }\n }\n if (skip == true) { continue; }\n }\n if (payload[key][\"detection\"][\"confidence\"] > object_confidence) {\n var current_object_data = {\n \"bbox\": {\n \"vertex1\": { \"x\": payload[key][\"x\"], \"y\": payload[key][\"y\"] },\n \"vertex2\": { \"x\": payload[key][\"x\"] + payload[key][\"w\"], \"y\": payload[key][\"y\"] },\n \"vertex3\": { \"x\": payload[key][\"x\"] + payload[key][\"w\"], \"y\": payload[key][\"y\"] + payload[key][\"h\"] },\n \"vertex4\": { \"x\": payload[key][\"x\"], \"y\": payload[key][\"y\"] + payload[key][\"h\"] },\n \"vertex_0\": { \"x\": payload[key][\"x\"], \"y\": payload[key][\"y\"] }\n }\n }\n // Extract type from detection.label\n if (payload[key].hasOwnProperty(\"detection\") && payload[key][\"detection\"].hasOwnProperty(\"label\")) {\n current_object_data[\"type\"] = payload[key][\"detection\"][\"label\"];\n }\n // Extract color from classification_layer_name:output.label\n if (payload[key].hasOwnProperty(\"classification_layer_name:output\") && payload[key][\"classification_layer_name:output\"].hasOwnProperty(\"label\")) {\n current_object_data[\"color\"] = payload[key][\"classification_layer_name:output\"][\"label\"];\n }\n // Extract license plate (if exists in future)\n if (payload[key].hasOwnProperty(\"license_plate\") && payload[key][\"license_plate\"].hasOwnProperty(\"label\")) {\n current_object_data[\"license_plate\"] = payload[key][\"license_plate\"][\"label\"];\n }\n // Extract tracking ID (if exists)\n if (payload[key].hasOwnProperty(\"id\")) {\n current_object_data[\"id\"] = payload[key][\"id\"];\n }\n // Extract ROI type\n if (payload[key].hasOwnProperty(\"roi_type\")) {\n current_object_data[\"roi_type\"] = payload[key][\"roi_type\"];\n }\n result[`object_${counter++}`] = current_object_data;\n }\n }\n}\n\nif (Object.keys(result).length > 0) {\n msg.payload = result;\n return msg;\n}", + "func": "let payload = msg.payload[\"metadata\"][\"objects\"];\nlet result = {};\nlet counter = 1;\n\nvar object_confidence = flow.get(\"object_confidence\");\nvar target_object = flow.get(\"target_object\");\nvar stop_duration = flow.get(\"stop_duration\");\n\nvar date = new Date();\nvar localDate = new Date();\n\nvar years = localDate.getFullYear();\nvar months = (localDate.getMonth() + 1).toString().padStart(2, \"0\");\nvar dates = (localDate.getDate()).toString().padStart(2, \"0\");\nvar hours = localDate.getHours();\nvar minutes = localDate.getMinutes();\nvar seconds = localDate.getSeconds();\n\nhours = (\"0\" + hours).slice(-2);\nminutes = (\"0\" + minutes).slice(-2);\nseconds = (\"0\" + seconds).slice(-2);\n\nmsg.nodered_timestamp = years + \"/\" + months + \"/\" + dates + \" \" + hours + \":\" + minutes + \":\" + seconds;\nmsg.object_timestamp = new Date().getTime() / 1000;\n\nfor (let key in payload) {\n if (payload.hasOwnProperty(key) && payload[key].hasOwnProperty(\"detection\")) {\n if (!(target_object.length == 1 && target_object[0] == \"\")) {\n var skip = true;\n for (let i = 0; i < target_object.length; i++) {\n if (payload[key][\"roi_type\"] == target_object[i]) {\n skip = false;\n break;\n }\n }\n if (skip == true) { continue; }\n }\n if (payload[key][\"detection\"][\"confidence\"] > object_confidence) {\n var current_object_data = {\n \"bbox\": {\n \"vertex1\": { \"x\": payload[key][\"x\"], \"y\": payload[key][\"y\"] },\n \"vertex2\": { \"x\": payload[key][\"x\"] + payload[key][\"w\"], \"y\": payload[key][\"y\"] },\n \"vertex3\": { \"x\": payload[key][\"x\"] + payload[key][\"w\"], \"y\": payload[key][\"y\"] + payload[key][\"h\"] },\n \"vertex4\": { \"x\": payload[key][\"x\"], \"y\": payload[key][\"y\"] + payload[key][\"h\"] },\n \"vertex_0\": { \"x\": payload[key][\"x\"], \"y\": payload[key][\"y\"] }\n }\n }\n // Extract type from detection.label\n if (payload[key].hasOwnProperty(\"detection\") && payload[key][\"detection\"].hasOwnProperty(\"label\")) {\n current_object_data[\"type\"] = payload[key][\"detection\"][\"label\"];\n }\n // Extract color from classification_layer_name:output.label\n if (payload[key].hasOwnProperty(\"classification_layer_name:output\") && payload[key][\"classification_layer_name:output\"].hasOwnProperty(\"label\")) {\n current_object_data[\"color\"] = payload[key][\"classification_layer_name:output\"][\"label\"];\n }\n // Extract license plate (if exists in future)\n if (payload[key].hasOwnProperty(\"license_plate\") && payload[key][\"license_plate\"].hasOwnProperty(\"label\")) {\n current_object_data[\"license_plate\"] = payload[key][\"license_plate\"][\"label\"];\n }\n // Extract tracking ID (if exists)\n if (payload[key].hasOwnProperty(\"id\")) {\n current_object_data[\"id\"] = payload[key][\"id\"];\n }\n // Extract ROI type\n if (payload[key].hasOwnProperty(\"roi_type\")) {\n current_object_data[\"roi_type\"] = payload[key][\"roi_type\"];\n }\n result[`object_${counter++}`] = current_object_data;\n }\n }\n}\n\nif (Object.keys(result).length > 0) {\n msg.payload = result;\n return msg;\n}", "outputs": 1, "timeout": 0, "noerr": 0, @@ -1287,7 +1287,7 @@ "type": "data extraction", "z": "2720733497fe05f3", "name": "data extraction 4", - "func": "let payload = msg.payload[\"metadata\"][\"objects\"];\nlet result = {};\nlet counter = 1;\n\nvar object_confidence = flow.get(\"object_confidence\");\nvar target_object = flow.get(\"target_object\");\nvar stop_duration = flow.get(\"stop_duration\");\n\nvar date = new Date();\nvar localDate = new Date(date.toLocaleString(\"en-US\", { timeZone: \"Asia/Kuala_Lumpur\" }));\n\nvar years = localDate.getFullYear();\nvar months = (localDate.getMonth() + 1).toString().padStart(2, \"0\");\nvar dates = (localDate.getDate()).toString().padStart(2, \"0\");\nvar hours = localDate.getHours();\nvar minutes = localDate.getMinutes();\nvar seconds = localDate.getSeconds();\n\nhours = (\"0\" + hours).slice(-2);\nminutes = (\"0\" + minutes).slice(-2);\nseconds = (\"0\" + seconds).slice(-2);\n\nmsg.nodered_timestamp = years + \"/\" + months + \"/\" + dates + \" \" + hours + \":\" + minutes + \":\" + seconds;\nmsg.object_timestamp = new Date().getTime() / 1000;\n\nfor (let key in payload) {\n if (payload.hasOwnProperty(key) && payload[key].hasOwnProperty(\"detection\")) {\n if (!(target_object.length == 1 && target_object[0] == \"\")) {\n var skip = true;\n for (let i = 0; i < target_object.length; i++) {\n if (payload[key][\"roi_type\"] == target_object[i]) {\n skip = false;\n break;\n }\n }\n if (skip == true) { continue; }\n }\n if (payload[key][\"detection\"][\"confidence\"] > object_confidence) {\n var current_object_data = {\n \"bbox\": {\n \"vertex1\": { \"x\": payload[key][\"x\"], \"y\": payload[key][\"y\"] },\n \"vertex2\": { \"x\": payload[key][\"x\"] + payload[key][\"w\"], \"y\": payload[key][\"y\"] },\n \"vertex3\": { \"x\": payload[key][\"x\"] + payload[key][\"w\"], \"y\": payload[key][\"y\"] + payload[key][\"h\"] },\n \"vertex4\": { \"x\": payload[key][\"x\"], \"y\": payload[key][\"y\"] + payload[key][\"h\"] },\n \"vertex_0\": { \"x\": payload[key][\"x\"], \"y\": payload[key][\"y\"] }\n }\n }\n // Extract type from detection.label\n if (payload[key].hasOwnProperty(\"detection\") && payload[key][\"detection\"].hasOwnProperty(\"label\")) {\n current_object_data[\"type\"] = payload[key][\"detection\"][\"label\"];\n }\n // Extract color from classification_layer_name:output.label\n if (payload[key].hasOwnProperty(\"classification_layer_name:output\") && payload[key][\"classification_layer_name:output\"].hasOwnProperty(\"label\")) {\n current_object_data[\"color\"] = payload[key][\"classification_layer_name:output\"][\"label\"];\n }\n // Extract license plate (if exists in future)\n if (payload[key].hasOwnProperty(\"license_plate\") && payload[key][\"license_plate\"].hasOwnProperty(\"label\")) {\n current_object_data[\"license_plate\"] = payload[key][\"license_plate\"][\"label\"];\n }\n // Extract tracking ID (if exists)\n if (payload[key].hasOwnProperty(\"id\")) {\n current_object_data[\"id\"] = payload[key][\"id\"];\n }\n // Extract ROI type\n if (payload[key].hasOwnProperty(\"roi_type\")) {\n current_object_data[\"roi_type\"] = payload[key][\"roi_type\"];\n }\n result[`object_${counter++}`] = current_object_data;\n }\n }\n}\n\nif (Object.keys(result).length > 0) {\n msg.payload = result;\n return msg;\n}", + "func": "let payload = msg.payload[\"metadata\"][\"objects\"];\nlet result = {};\nlet counter = 1;\n\nvar object_confidence = flow.get(\"object_confidence\");\nvar target_object = flow.get(\"target_object\");\nvar stop_duration = flow.get(\"stop_duration\");\n\nvar date = new Date();\nvar localDate = new Date();\n\nvar years = localDate.getFullYear();\nvar months = (localDate.getMonth() + 1).toString().padStart(2, \"0\");\nvar dates = (localDate.getDate()).toString().padStart(2, \"0\");\nvar hours = localDate.getHours();\nvar minutes = localDate.getMinutes();\nvar seconds = localDate.getSeconds();\n\nhours = (\"0\" + hours).slice(-2);\nminutes = (\"0\" + minutes).slice(-2);\nseconds = (\"0\" + seconds).slice(-2);\n\nmsg.nodered_timestamp = years + \"/\" + months + \"/\" + dates + \" \" + hours + \":\" + minutes + \":\" + seconds;\nmsg.object_timestamp = new Date().getTime() / 1000;\n\nfor (let key in payload) {\n if (payload.hasOwnProperty(key) && payload[key].hasOwnProperty(\"detection\")) {\n if (!(target_object.length == 1 && target_object[0] == \"\")) {\n var skip = true;\n for (let i = 0; i < target_object.length; i++) {\n if (payload[key][\"roi_type\"] == target_object[i]) {\n skip = false;\n break;\n }\n }\n if (skip == true) { continue; }\n }\n if (payload[key][\"detection\"][\"confidence\"] > object_confidence) {\n var current_object_data = {\n \"bbox\": {\n \"vertex1\": { \"x\": payload[key][\"x\"], \"y\": payload[key][\"y\"] },\n \"vertex2\": { \"x\": payload[key][\"x\"] + payload[key][\"w\"], \"y\": payload[key][\"y\"] },\n \"vertex3\": { \"x\": payload[key][\"x\"] + payload[key][\"w\"], \"y\": payload[key][\"y\"] + payload[key][\"h\"] },\n \"vertex4\": { \"x\": payload[key][\"x\"], \"y\": payload[key][\"y\"] + payload[key][\"h\"] },\n \"vertex_0\": { \"x\": payload[key][\"x\"], \"y\": payload[key][\"y\"] }\n }\n }\n // Extract type from detection.label\n if (payload[key].hasOwnProperty(\"detection\") && payload[key][\"detection\"].hasOwnProperty(\"label\")) {\n current_object_data[\"type\"] = payload[key][\"detection\"][\"label\"];\n }\n // Extract color from classification_layer_name:output.label\n if (payload[key].hasOwnProperty(\"classification_layer_name:output\") && payload[key][\"classification_layer_name:output\"].hasOwnProperty(\"label\")) {\n current_object_data[\"color\"] = payload[key][\"classification_layer_name:output\"][\"label\"];\n }\n // Extract license plate (if exists in future)\n if (payload[key].hasOwnProperty(\"license_plate\") && payload[key][\"license_plate\"].hasOwnProperty(\"label\")) {\n current_object_data[\"license_plate\"] = payload[key][\"license_plate\"][\"label\"];\n }\n // Extract tracking ID (if exists)\n if (payload[key].hasOwnProperty(\"id\")) {\n current_object_data[\"id\"] = payload[key][\"id\"];\n }\n // Extract ROI type\n if (payload[key].hasOwnProperty(\"roi_type\")) {\n current_object_data[\"roi_type\"] = payload[key][\"roi_type\"];\n }\n result[`object_${counter++}`] = current_object_data;\n }\n }\n}\n\nif (Object.keys(result).length > 0) {\n msg.payload = result;\n return msg;\n}", "outputs": 1, "timeout": 0, "noerr": 0, @@ -1307,7 +1307,7 @@ "type": "configurations", "z": "2720733497fe05f3", "name": "configurations 1", - "func": "//Configurations\nlet target_object = [\"car\", \"truck\", \"bus\"];\nlet video_pipeline_width = 1280;\nlet video_pipeline_height = 720;\nlet object_confidence = 0.3;\nlet intersection_type = \"region\"; // \"object\"\nlet intersection_threshold = 0.8;\nlet distance_threshold = 400;\nlet object_counter = true; //true;\n\n// loitering\nlet stop_duration = 5;\n\n/***************************/\n//Don't touch section below//\n/***************************/\nflow.set(\"target_object\", target_object);\nflow.set(\"video_pipeline_width\", video_pipeline_width);\nflow.set(\"video_pipeline_height\", video_pipeline_height);\nflow.set(\"object_confidence\", object_confidence);\nflow.set(\"intersection_type\", intersection_type);\nflow.set(\"intersection_threshold\", intersection_threshold);\nflow.set(\"distance_threshold\", distance_threshold);\nflow.set(\"object_counter\", object_counter);\n\n// loitering\nflow.set(\"stop_duration\", stop_duration);\n\nreturn msg;", + "func": "//Configurations\nlet target_object = [\"car\", \"truck\", \"bus\"];\nlet video_pipeline_width = 640;\nlet video_pipeline_height = 480;\nlet object_confidence = 0.3;\nlet intersection_type = \"region\"; // \"object\"\nlet intersection_threshold = 0.8;\nlet distance_threshold = 400;\nlet object_counter = true; //true;\n\n// loitering\nlet stop_duration = 5;\n\n/***************************/\n//Don't touch section below//\n/***************************/\nflow.set(\"target_object\", target_object);\nflow.set(\"video_pipeline_width\", video_pipeline_width);\nflow.set(\"video_pipeline_height\", video_pipeline_height);\nflow.set(\"object_confidence\", object_confidence);\nflow.set(\"intersection_type\", intersection_type);\nflow.set(\"intersection_threshold\", intersection_threshold);\nflow.set(\"distance_threshold\", distance_threshold);\nflow.set(\"object_counter\", object_counter);\n\n// loitering\nflow.set(\"stop_duration\", stop_duration);\n\nreturn msg;", "outputs": 1, "timeout": 0, "noerr": 0, @@ -1325,7 +1325,7 @@ "type": "delay", "z": "2720733497fe05f3", "name": "", - "pauseType": "delay", + "pauseType": "rate", "timeout": "0", "timeoutUnits": "seconds", "rate": "1", @@ -1334,7 +1334,7 @@ "randomFirst": "1", "randomLast": "5", "randomUnits": "seconds", - "drop": false, + "drop": true, "allowrate": false, "outputs": 1, "x": 1380, @@ -1350,7 +1350,7 @@ "type": "euclidean and ior", "z": "2720733497fe05f3", "name": "euclidean and ior 1", - "func": "let region_name = \"predefined_regions_1\";\nlet detection_name = \"object_detection_1\";\nlet previous_data_name = \"previous_data_1\";\nlet occupancy_check_duration = 60;\nlet video_id = 1;\nlet region_id = -1; //Recommend to set this only when there is 1 predefined region in the video\n\nvar payload = msg.payload;\nvar previous_data = flow.get(previous_data_name);\nvar stop_duration = flow.get(\"stop_duration\");\n\n// preparing bboxes\nfunction preprocess_bboxes(payload) {\n function getObjectData(object_data) {\n var current_object_data = {};\n if (object_data.hasOwnProperty(\"type\")) {\n current_object_data[\"type\"] = object_data[\"type\"];\n }\n if (object_data.hasOwnProperty(\"color\")) {\n current_object_data[\"color\"] = object_data[\"color\"];\n }\n if (object_data.hasOwnProperty(\"license_plate\")) {\n current_object_data[\"license_plate\"] = object_data[\"license_plate\"];\n }\n if (object_data.hasOwnProperty(\"id\")) {\n current_object_data[\"id\"] = object_data[\"id\"];\n }\n if (object_data.hasOwnProperty(\"roi_type\")) {\n current_object_data[\"roi_type\"] = object_data[\"roi_type\"];\n }\n return current_object_data;\n }\n function getLotBbox(lot_data) {\n return { \"vertex1\": lot_data.vertex1, \"vertex2\": lot_data.vertex2, \"vertex3\": lot_data.vertex3, \"vertex4\": lot_data.vertex4, \"vertex_0\": lot_data.vertex1 };\n }\n\n var results = {};\n var objects = [];\n var objects_extra = [];\n var lots = [];\n // get all objects bboxes\n for (let key in payload) {\n if (payload.hasOwnProperty(key)) {\n var objectData = getObjectData(payload[key]);\n if (objectData !== null) {\n objects_extra.push(\n objectData\n );\n objects.push(\n payload[key][\"bbox\"]\n );\n }\n }\n }\n // get all lots bboxes\n var predefined_regions = flow.get(region_name);\n var keys = Object.keys(predefined_regions);\n\n for (let i = 0; i < keys.length; i++) {\n if (predefined_regions.hasOwnProperty(keys[i]) && keys[i] != \"_msgid\") {\n lots.push(\n getLotBbox(predefined_regions[keys[i]])\n );\n }\n }\n\n results[\"objects\"] = objects;\n results[\"objects_extra\"] = objects_extra;\n results[\"lots\"] = lots;\n return results;\n}\n\n// euclidean distance and iou check\nfunction euclidean_iou_function(payload, distance_threshold = 100, intersection_type = \"region\", intersection_threshold = 0.5, object_counter = false) {\n function getBboxCentroid(bbox) {\n var centroid_x = (bbox[\"vertex1\"].x + bbox[\"vertex2\"].x + bbox[\"vertex3\"].x + bbox[\"vertex4\"].x) / 4;\n var centroid_y = (bbox[\"vertex1\"].y + bbox[\"vertex2\"].y + bbox[\"vertex3\"].y + bbox[\"vertex4\"].y) / 4;\n return [centroid_x, centroid_y];\n }\n function calculateEuclideanDistance(centroid1, centroid2) {\n return Math.sqrt((centroid2[0] - centroid1[0]) ** 2 + (centroid2[1] - centroid1[1]) ** 2);\n }\n function calculateIntersectArea(bbox1, bbox2) { // objects, lots\n var bbox1_key = Object.keys(bbox1)\n var bbox2_key = Object.keys(bbox2)\n var intersection = [];\n\n for (let i = 0; i < bbox1_key.length - 1; i++) {\n for (let j = 0; j < bbox2_key.length - 1; j++) {\n var A1 = bbox1[bbox1_key[i + 1]].y - bbox1[bbox1_key[i]].y; // Ax + By + C = 0\n var B1 = bbox1[bbox1_key[i]].x - bbox1[bbox1_key[i + 1]].x;\n var C1 = - A1 * bbox1[bbox1_key[i]].x - B1 * bbox1[bbox1_key[i]].y;\n\n var A2 = bbox2[bbox2_key[j + 1]].y - bbox2[bbox2_key[j]].y;\n var B2 = bbox2[bbox2_key[j]].x - bbox2[bbox2_key[j + 1]].x;\n var C2 = - A2 * bbox2[bbox2_key[j]].x - B2 * bbox2[bbox2_key[j]].y;\n\n // parallel lines case\n if (A1 * B2 == A2 * B1) {\n // coincide case\n if ((A1 * C2 == A2 * C1) && (B1 * C2 == B2 * C1)) {\n if (B1 == 0 && A1 == 0) { // one point bbox case\n intersection.push([bbox1[bbox1_key[i]].x.toFixed(6), bbox1[bbox1_key[i]].y.toFixed(6)]);\n }\n else if (B2 == 0 && A2 == 0) { // one point bbox case\n intersection.push([bbox2[bbox2_key[i]].x.toFixed(6), bbox2[bbox2_key[i]].y.toFixed(6)]);\n }\n else if (B1 == 0) { // vertical lines case\n var y_min1 = (bbox1[bbox1_key[i]].y < bbox1[bbox1_key[i + 1]].y) ? bbox1[bbox1_key[i]].y : bbox1[bbox1_key[i + 1]].y;\n var y_max1 = (bbox1[bbox1_key[i]].y < bbox1[bbox1_key[i + 1]].y) ? bbox1[bbox1_key[i + 1]].y : bbox1[bbox1_key[i]].y;\n var y_min2 = (bbox2[bbox2_key[i]].y < bbox2[bbox2_key[i + 1]].y) ? bbox2[bbox2_key[i]].y : bbox2[bbox2_key[i + 1]].y;\n var y_max2 = (bbox2[bbox2_key[i]].y < bbox2[bbox2_key[i + 1]].y) ? bbox2[bbox2_key[i + 1]].y : bbox2[bbox2_key[i]].y;\n if ((y_min1 < y_max2) && (y_min2 < y_max1)) {\n var y_min = (y_min1 < y_min2) ? y_min2 : y_min1;\n var y_max = (y_max1 < y_max2) ? y_max1 : y_max2;\n intersection.push([bbox1[bbox1_key[i]].x.toFixed(6), y_min.toFixed(6)]);\n intersection.push([bbox1[bbox1_key[i]].x.toFixed(6), y_max.toFixed(6)]);\n }\n }\n else if (A1 == 0) { // horizontal lines case\n var x_min1 = (bbox1[bbox1_key[i]].x < bbox1[bbox1_key[i + 1]].x) ? bbox1[bbox1_key[i]].x : bbox1[bbox1_key[i + 1]].x;\n var x_max1 = (bbox1[bbox1_key[i]].x < bbox1[bbox1_key[i + 1]].x) ? bbox1[bbox1_key[i + 1]].x : bbox1[bbox1_key[i]].x;\n var x_min2 = (bbox2[bbox2_key[i]].x < bbox2[bbox2_key[i + 1]].x) ? bbox2[bbox2_key[i]].x : bbox2[bbox2_key[i + 1]].x;\n var x_max2 = (bbox2[bbox2_key[i]].x < bbox2[bbox2_key[i + 1]].x) ? bbox2[bbox2_key[i + 1]].x : bbox2[bbox2_key[i]].x;\n if ((x_min1 < x_max2) && (x_min2 < x_max1)) {\n var x_min = (x_min1 < x_min2) ? x_min2 : x_min1;\n var x_max = (x_max1 < x_max2) ? x_max1 : x_max2;\n intersection.push([x_min.toFixed(6), bbox1[bbox1_key[i]].y.toFixed(6)]);\n intersection.push([x_max.toFixed(6), bbox1[bbox1_key[i]].y.toFixed(6)]);\n }\n }\n else {\n var x_min1 = (bbox1[bbox1_key[i]].x < bbox1[bbox1_key[i + 1]].x) ? bbox1[bbox1_key[i]].x : bbox1[bbox1_key[i + 1]].x;\n var x_max1 = (bbox1[bbox1_key[i]].x < bbox1[bbox1_key[i + 1]].x) ? bbox1[bbox1_key[i + 1]].x : bbox1[bbox1_key[i]].x;\n var x_min2 = (bbox2[bbox2_key[i]].x < bbox2[bbox2_key[i + 1]].x) ? bbox2[bbox2_key[i]].x : bbox2[bbox2_key[i + 1]].x;\n var x_max2 = (bbox2[bbox2_key[i]].x < bbox2[bbox2_key[i + 1]].x) ? bbox2[bbox2_key[i + 1]].x : bbox2[bbox2_key[i]].x;\n if ((x_min1 < x_max2) && (x_min2 < x_max1) && (y_min1 < y_max2) && (y_min2 < y_max1)) {\n var x_min = (x_min1 < x_min2) ? x_min2 : x_min1;\n var x_max = (x_max1 < x_max2) ? x_max1 : x_max2;\n var xY_min = (-A1 * x_min - C1) / B1\n var xY_max = (-A1 * x_max - C1) / B1\n intersection.push([x_min.toFixed(6), xY_min.toFixed(6)]);\n intersection.push([x_max.toFixed(6), xY_max.toFixed(6)]);\n }\n }\n }\n }\n else {\n // get intersection coordinates\n var x1 = bbox1[bbox1_key[i]].x;\n var x2 = bbox1[bbox1_key[i + 1]].x;\n var x3 = bbox2[bbox2_key[j]].x;\n var x4 = bbox2[bbox2_key[j + 1]].x;\n var y1 = bbox1[bbox1_key[i]].y;\n var y2 = bbox1[bbox1_key[i + 1]].y;\n var y3 = bbox2[bbox2_key[j]].y;\n var y4 = bbox2[bbox2_key[j + 1]].y;\n\n var den = (x2 - x1) * (y3 - y4) - (y2 - y1) * (x3 - x4);\n var t = ((x3 - x1) * (y3 - y4) - (y3 - y1) * (x3 - x4)) / den;\n var u = ((x3 - x1) * (y1 - y2) - (y3 - y1) * (x1 - x2)) / den;\n if ((t >= 0) && (t <= 1) && (u >= 0) && (u <= 1)) { // intersect happens if this condition is fulfilled\n var ix = x1 + t * (x2 - x1);\n var iy = y1 + t * (y2 - y1);\n intersection.push([ix.toFixed(6), iy.toFixed(6)]);\n }\n }\n }\n }\n // get clipped coordinates\n var specialcase1 = true;\n var specialcase2 = false;\n var temp1 = bbox1[bbox1_key[0]];\n var temp2 = bbox2[bbox2_key[0]];\n for (let i = 1; i < bbox1_key.length - 1; i++) { // special case\n if (temp1 !== bbox1[bbox1_key[i]]) {\n specialcase1 = false;\n break;\n }\n }\n for (let i = 1; i < bbox2_key.length - 1; i++) {\n if (temp2 !== bbox2[bbox2_key[i]]) {\n specialcase2 = false;\n break;\n }\n }\n if (!(specialcase2)) {\n for (let i = 0; i < bbox1_key.length - 1; i++) {\n var sign_check = [];\n for (let j = 0; j < bbox2_key.length - 1; j++) {\n var x1 = bbox2[bbox2_key[j]].x\n var x2 = bbox2[bbox2_key[j + 1]].x\n var y1 = bbox2[bbox2_key[j]].y\n var y2 = bbox2[bbox2_key[j + 1]].y\n var cross_prod = (bbox1[bbox1_key[i]].x - x1) * (y2 - y1) - (bbox1[bbox1_key[i]].y - y1) * (x2 - x1);\n var sign = (cross_prod >= 0) ? true : false;\n sign_check.push(sign);\n }\n if (sign_check.length === 4 && (sign_check.every((val, index) => val === [true, true, true, true][index]) ||\n sign_check.every((val, index) => val === [false, false, false, false][index]))) { // similar signs indicate the coordinate is within another bbox\n intersection.push([bbox1[bbox1_key[i]].x.toFixed(6), bbox1[bbox1_key[i]].y.toFixed(6)]);\n //intersection.push([bbox1[bbox1_key[i]].x, bbox1[bbox1_key[i]].y]);\n }\n }\n }\n if (!(specialcase1)) {\n for (let i = 0; i < bbox2_key.length - 1; i++) { // similar purpose but for second bbox\n var sign_check = [];\n for (let j = 0; j < bbox1_key.length - 1; j++) {\n var x1 = bbox1[bbox1_key[j]].x\n var x2 = bbox1[bbox1_key[j + 1]].x\n var y1 = bbox1[bbox1_key[j]].y\n var y2 = bbox1[bbox1_key[j + 1]].y\n var cross_prod = (bbox2[bbox2_key[i]].x - x1) * (y2 - y1) - (bbox2[bbox2_key[i]].y - y1) * (x2 - x1);\n var sign = (cross_prod >= 0) ? true : false;\n sign_check.push(sign);\n }\n if (sign_check.length === 4 && (sign_check.every((val, index) => val === [true, true, true, true][index]) ||\n sign_check.every((val, index) => val === [false, false, false, false][index]))) {\n intersection.push([bbox2[bbox2_key[i]].x.toFixed(6), bbox2[bbox2_key[i]].y.toFixed(6)]);\n //intersection.push([bbox2[bbox2_key[i]].x, bbox2[bbox2_key[i]].y]);\n }\n }\n }\n intersection = intersection.filter((item, index, self) =>\n index === self.findIndex((t) => JSON.stringify(t) === JSON.stringify(item))\n );\n // sort intersection points\n if (intersection.length > 2) {\n var intersection_angle = [];\n var intersection_distance = [];\n\n let minIndex = intersection.reduce((minIdx, currentValue, currentIndex, array) => {\n if (currentValue[0] < array[minIdx][0]) {\n return currentIndex;\n } else if (currentValue[0] === array[minIdx][0]) {\n return currentValue[1] < array[minIdx][1] ? currentIndex : minIdx;\n } else {\n return minIdx;\n }\n }, 0);\n var temp_intersection = intersection.filter((_, index) => index !== minIndex);\n for (let i = 0; i < temp_intersection.length; i++) {\n intersection_angle.push(Math.atan2(temp_intersection[i][1] - intersection[minIndex][1], temp_intersection[i][0] - intersection[minIndex][0]));\n intersection_distance.push((temp_intersection[i][1] - intersection[minIndex][1]) ** 2 + (temp_intersection[i][0] - intersection[minIndex][0]) ** 2);\n }\n let combined = temp_intersection.map((point, index) => ({ // sort intersect coordinates based on angle and then distance\n point: point,\n angle: intersection_angle[index],\n distance: intersection_distance[index]\n }));\n combined.sort((a, b) => a.angle - b.angle || a.distance - b.distance);\n let sorted_intersection = combined.map(item => item.point);\n sorted_intersection.unshift(intersection[minIndex]);\n sorted_intersection.push(intersection[minIndex]);\n // shoelace algorithm to calculate the intersect area\n var area = 0;\n for (let i = 0; i < sorted_intersection.length - 1; i++) {\n area += ((sorted_intersection[i][0] * sorted_intersection[i + 1][1])\n - (sorted_intersection[i + 1][0] * sorted_intersection[i][1]))\n }\n return area;\n }\n return 0;\n }\n function calculateIOU(bbox1, bbox2, intersectArea) {\n if (intersectArea !== 0) {\n var lot_area1 = (bbox1[\"vertex1\"].x * bbox1[\"vertex2\"].y) + (bbox1[\"vertex2\"].x * bbox1[\"vertex3\"].y)\n + (bbox1[\"vertex3\"].x * bbox1[\"vertex4\"].y) + (bbox1[\"vertex4\"].x * bbox1[\"vertex_0\"].y)\n - ((bbox1[\"vertex2\"].x * bbox1[\"vertex1\"].y) + (bbox1[\"vertex3\"].x * bbox1[\"vertex2\"].y)\n + (bbox1[\"vertex4\"].x * bbox1[\"vertex3\"].y) + (bbox1[\"vertex_0\"].x * bbox1[\"vertex4\"].y));\n\n var lot_area2 = (bbox2[\"vertex1\"].x * bbox2[\"vertex2\"].y) + (bbox2[\"vertex2\"].x * bbox2[\"vertex3\"].y)\n + (bbox2[\"vertex3\"].x * bbox2[\"vertex4\"].y) + (bbox2[\"vertex4\"].x * bbox2[\"vertex_0\"].y)\n - ((bbox2[\"vertex2\"].x * bbox2[\"vertex1\"].y) + (bbox2[\"vertex3\"].x * bbox2[\"vertex2\"].y)\n + (bbox2[\"vertex4\"].x * bbox2[\"vertex3\"].y) + (bbox2[\"vertex_0\"].x * bbox2[\"vertex4\"].y));\n return intersectArea / (lot_area1 + lot_area2 - intersectArea)\n }\n return 0;\n }\n function calculateIOR(bbox1, intersectArea) {\n if (intersectArea !== 0) {\n var lot_area = (bbox1[\"vertex1\"].x * bbox1[\"vertex2\"].y) + (bbox1[\"vertex2\"].x * bbox1[\"vertex3\"].y)\n + (bbox1[\"vertex3\"].x * bbox1[\"vertex4\"].y) + (bbox1[\"vertex4\"].x * bbox1[\"vertex_0\"].y)\n - ((bbox1[\"vertex2\"].x * bbox1[\"vertex1\"].y) + (bbox1[\"vertex3\"].x * bbox1[\"vertex2\"].y)\n + (bbox1[\"vertex4\"].x * bbox1[\"vertex3\"].y) + (bbox1[\"vertex_0\"].x * bbox1[\"vertex4\"].y));\n return intersectArea / lot_area;\n }\n return 0;\n }\n\n var occupied = [];\n var current_index = -1;\n // initialize lots to no object\n for (let key1 in payload[\"lots\"]) {\n if (payload[\"lots\"].hasOwnProperty(key1)) {\n occupied.push({ \"occupied\": 0 });\n }\n }\n for (let key1 in payload[\"objects\"]) {\n current_index++;\n if (payload[\"objects\"].hasOwnProperty(key1)) { // find all lots within the object centroid distance threshold\n var objectCentroid = getBboxCentroid(payload[\"objects\"][key1]);\n var potential_region = [];\n for (let key2 in payload[\"lots\"]) {\n if (payload[\"lots\"].hasOwnProperty(key2)) {\n var regionCentroid = getBboxCentroid(payload[\"lots\"][key2]);\n var euclideanDistance = calculateEuclideanDistance(objectCentroid, regionCentroid);\n potential_region.push(euclideanDistance);\n }\n }\n // calculate IOU or IOR\n if (object_counter) {\n for (let i = 0; i < potential_region.length; i++) {\n if (potential_region[i] < distance_threshold) {\n var intersectRegion = calculateIntersectArea(payload[\"objects\"][key1], payload[\"lots\"][i]);\n var IOR = 0;\n if (intersection_type == \"region\") {\n IOR = calculateIOR(payload[\"lots\"][i], intersectRegion)\n }\n else if (intersection_type == \"object\") {\n IOR = calculateIOR(payload[\"objects\"][key1], intersectRegion)\n }\n if (IOR > intersection_threshold) {\n occupied[i][\"occupied\"] += 1;\n var keyData = Object.keys(payload[\"objects_extra\"][current_index]);\n var numberData = keyData.length;\n for (let j = 0; j < numberData; j++) {\n if (!occupied[i][\"extra\"]) {\n occupied[i][\"extra\"] = {};\n }\n if (!occupied[i][\"extra\"][keyData[j]]) {\n occupied[i][\"extra\"][keyData[j]] = [];\n }\n occupied[i][\"extra\"][keyData[j]].push(payload[\"objects_extra\"][current_index][keyData[j]]);\n }\n }\n }\n }\n }\n else { // consider the best lot only\n var best_region_index = potential_region.reduce((minIndex, currentValue, currentIndex, array) =>\n currentValue < array[minIndex] ? currentIndex : minIndex, 0);\n if (potential_region[best_region_index] < distance_threshold) {\n var intersectRegion = calculateIntersectArea(payload[\"objects\"][key1], payload[\"lots\"][best_region_index]);\n var IOR = 0;\n if (intersection_type == \"region\") {\n IOR = calculateIOR(payload[\"lots\"][best_region_index], intersectRegion)\n }\n else if (intersection_type == \"object\") {\n IOR = calculateIOR(payload[\"objects\"][key1], intersectRegion)\n }\n if (IOR > intersection_threshold) {\n occupied[best_region_index][\"occupied\"] += 1;\n var keyData = Object.keys(payload[\"objects_extra\"][current_index]);\n var numberData = keyData.length;\n for (let i = 0; i < numberData; i++) {\n if (!occupied[best_region_index][\"extra\"]) {\n occupied[best_region_index][\"extra\"] = {};\n }\n if (!occupied[best_region_index][\"extra\"][keyData[i]]) {\n occupied[best_region_index][\"extra\"][keyData[i]] = [];\n }\n occupied[best_region_index][\"extra\"][keyData[i]].push(payload[\"objects_extra\"][current_index][keyData[i]]);\n }\n }\n }\n }\n }\n }\n return occupied;\n}\n\n// turn into correct format\nfunction postprocess_output(occupied) {\n var results = {};\n\n for (let i = 0; i < occupied.length; i++) {\n results = initializeResults(results, occupied, i);\n previous_data = initializePreviousData(results, i);\n if (results[\"Region \" + (i + 1)].hasOwnProperty(\"id\") || previous_data[\"Region \" + (i + 1)].hasOwnProperty(\"id\")) {\n results = handleObjectWithID(results, i);\n }\n else if (results[\"Region \" + (i + 1)][\"occupied\"] > 0) {\n results = handleOccupiedRegion(results, i);\n }\n else if (previous_data[\"Region \" + (i + 1)][\"entry_time\"]) {\n results = handleNotOccupiedRegion(results, i);\n }\n }\n return results;\n}\n\nfunction initializeResults(results, occupied, i) {\n const regionKey = \"Region \" + (i + 1);\n\n if (!results[regionKey]) {\n results[regionKey] = {};\n }\n results[regionKey][\"video_id\"] = video_id;\n results[regionKey][\"region_id\"] = (region_id != -1) ? region_id : i + 1;\n\n if (occupied[i].hasOwnProperty(\"occupied\")) {\n results[regionKey][\"occupied\"] = occupied[i].occupied;\n }\n\n if (occupied[i].hasOwnProperty(\"extra\")) {\n for (let key in occupied[i].extra) {\n results[regionKey][key] = occupied[i].extra[key];\n }\n }\n if (results[regionKey].hasOwnProperty(\"id\") || (previous_data && previous_data.hasOwnProperty(regionKey) && previous_data[regionKey].hasOwnProperty(\"id\"))) {\n results[regionKey][\"actual_entry_time\"] = [];\n results[regionKey][\"actual_dwell_time\"] = [];\n results[regionKey][\"entry_time\"] = [];\n results[regionKey][\"dwell_time\"] = [];\n results[regionKey][\"exit_time\"] = [];\n }\n else {\n results[\"Region \" + (i + 1)][\"actual_entry_time\"] = \"\";\n results[\"Region \" + (i + 1)][\"actual_dwell_time\"] = \"\";\n results[\"Region \" + (i + 1)][\"entry_time\"] = \"\";\n results[\"Region \" + (i + 1)][\"dwell_time\"] = \"\";\n results[\"Region \" + (i + 1)][\"exit_time\"] = \"\";\n results[\"Region \" + (i + 1)][\"occupancy_check\"] = 0;\n }\n return results;\n}\n\nfunction initializePreviousData(results, i) {\n const regionKey = \"Region \" + (i + 1);\n\n // Ensure previous_data is set up for this region.\n if (!previous_data) {\n previous_data = {};\n }\n if (results[regionKey].hasOwnProperty(\"id\")) {\n if (!previous_data[regionKey]) {\n previous_data[regionKey] = {};\n previous_data[regionKey][\"id\"] = [];\n previous_data[regionKey][\"actual_entry_time\"] = [];\n previous_data[regionKey][\"actual_dwell_time\"] = [];\n previous_data[regionKey][\"entry_time\"] = [];\n previous_data[regionKey][\"dwell_time\"] = [];\n previous_data[regionKey][\"exit_time\"] = [];\n }\n }\n else {\n if (!previous_data.hasOwnProperty(regionKey)) {\n previous_data[regionKey] = results[regionKey];\n previous_data[regionKey][\"actual_entry_time\"] = \"\";\n previous_data[regionKey][\"actual_dwell_time\"] = \"\";\n previous_data[regionKey][\"entry_time\"] = \"\";\n previous_data[regionKey][\"dwell_time\"] = \"\";\n previous_data[regionKey][\"exit_time\"] = \"\";\n previous_data[regionKey][\"occupancy_check\"] = 0;\n }\n }\n\n return previous_data;\n}\n\nfunction handleObjectWithID(results, i) {\n const regionKey = \"Region \" + (i + 1);\n const resultIds = results[regionKey][\"id\"];\n const previousDataIds = previous_data[regionKey][\"id\"];\n\n if (!resultIds && !previousDataIds) {\n return results;\n }\n\n if (resultIds) {\n for (let j = 0; j < resultIds.length; j++) {\n // Check existing id data\n let prevIndex = -1;\n\n if (previous_data[regionKey][\"id\"] && previous_data[regionKey][\"id\"].length > 0) {\n prevIndex = previous_data[regionKey][\"id\"].indexOf(resultIds[j]);\n }\n\n if (prevIndex === -1) {\n results[regionKey][\"actual_entry_time\"].push(msg.nodered_timestamp);\n results[regionKey][\"entry_time\"].push(msg.object_timestamp);\n results[regionKey][\"actual_dwell_time\"].push(\"00:00:00\");\n results[regionKey][\"dwell_time\"].push(0);\n results[regionKey][\"exit_time\"].push(\"\");\n } else {\n results[regionKey][\"actual_entry_time\"].push(previous_data[regionKey][\"actual_entry_time\"][prevIndex]);\n results[regionKey][\"entry_time\"].push(previous_data[regionKey][\"entry_time\"][prevIndex]);\n\n // Calculate dwell time.\n const dwell_timestamp = (msg.object_timestamp - results[regionKey][\"entry_time\"][j]);\n const dwell_hours = Math.floor(dwell_timestamp / 3600).toString().padStart(2, \"0\");\n const dwell_mins = Math.floor((dwell_timestamp / 60) % 60).toString().padStart(2, \"0\");\n const dwell_seconds = Math.floor(dwell_timestamp % 60).toString().padStart(2, \"0\");\n results[regionKey][\"dwell_time\"].push(dwell_timestamp);\n results[regionKey][\"actual_dwell_time\"].push(dwell_hours + \":\" + dwell_mins + \":\" + dwell_seconds);\n results[regionKey][\"exit_time\"].push(\"\");\n }\n }\n }\n\n if (previousDataIds) {\n for (let j = 0; j < previousDataIds.length; j++) {\n let prevIndex = -1;\n\n if (!results[regionKey][\"id\"]) {\n results[regionKey] = previous_data[regionKey];\n results[regionKey][\"occupied\"] = 0;\n break;\n }\n else {\n if (!results[regionKey][\"dwell_time\"].some(val => val >= stop_duration)) {\n results[regionKey][\"occupied\"] = 0;\n }\n\n prevIndex = results[regionKey][\"id\"].indexOf(previousDataIds[j]);\n if (prevIndex === -1) {\n let keyList = Object.keys(previous_data[regionKey]);\n for (let k = 0; k < keyList.length; k++) {\n if (keyList[k] == \"video_id\" || keyList[k] == \"region_id\" || keyList[k] == \"occupied\") {\n continue;\n }\n results[regionKey][keyList[k]].push(previous_data[regionKey][keyList[k]][j]);\n }\n }\n }\n }\n }\n return results;\n}\n\nfunction handleOccupiedRegion(results, i) {\n const regionKey = \"Region \" + (i + 1);\n if (previous_data[regionKey][\"entry_time\"] == \"\") {\n results[regionKey][\"actual_entry_time\"] = msg.nodered_timestamp;\n results[regionKey][\"entry_time\"] = msg.object_timestamp;\n }\n else {\n if (previous_data[regionKey][\"occupancy_check\"] >= occupancy_check_duration) {\n results[regionKey][\"actual_dwell_time\"] = \"00:00:00\";\n results[regionKey][\"dwell_time\"] = 0;\n results[regionKey][\"actual_entry_time\"] = msg.nodered_timestamp;\n results[regionKey][\"entry_time\"] = msg.object_timestamp;\n }\n else {\n results[regionKey][\"actual_entry_time\"] = previous_data[regionKey][\"actual_entry_time\"];\n results[regionKey][\"entry_time\"] = previous_data[regionKey][\"entry_time\"];\n }\n\n var dwell_timestamp = msg.object_timestamp - results[regionKey][\"entry_time\"];\n var dwell_hours = Math.floor(dwell_timestamp / 3600).toString();\n dwell_hours = dwell_hours.toString().length == 1 ? dwell_hours.toString().padStart(2, \"0\") : dwell_hours.toString();\n var dwell_mins = Math.floor((dwell_timestamp / 60) % 60).toString().padStart(2, \"0\");\n var dwell_seconds = Math.floor(dwell_timestamp % 60).toString().padStart(2, \"0\");\n\n results[regionKey][\"dwell_time\"] = dwell_timestamp;\n results[regionKey][\"actual_dwell_time\"] = dwell_hours + \":\" + dwell_mins + \":\" + dwell_seconds;\n results[regionKey][\"occupancy_check\"] = 0;\n }\n\n return results;\n}\n\nfunction handleNotOccupiedRegion(results, i) {\n const regionKey = \"Region \" + (i + 1);\n\n results[regionKey][\"actual_entry_time\"] = previous_data[regionKey][\"actual_entry_time\"];\n results[regionKey][\"entry_time\"] = previous_data[regionKey][\"entry_time\"];\n results[regionKey][\"actual_dwell_time\"] = previous_data[regionKey][\"actual_dwell_time\"];\n results[regionKey][\"dwell_time\"] = previous_data[regionKey][\"dwell_time\"];\n results[regionKey][\"occupancy_check\"] = previous_data[regionKey][\"occupancy_check\"] + 1;\n\n if (previous_data[regionKey][\"exit_time\"]) {\n results[regionKey][\"exit_time\"] = previous_data[regionKey][\"exit_time\"];\n }\n else {\n results[regionKey][\"exit_time\"] = msg.nodered_timestamp;\n }\n return results;\n}\n\nfunction main(payload, distance_threshold = 100, intersection_type = \"region\", intersection_threshold = 0.5, object_counter = false) {\n var results = preprocess_bboxes(payload);\n var occupied = euclidean_iou_function(results, distance_threshold, intersection_type, intersection_threshold, object_counter);\n var out = postprocess_output(occupied);\n flow.set(previous_data_name, out);\n return out;\n}\n\n// user-defined parameters\nvar distance_threshold = flow.get(\"distance_threshold\");\nvar intersection_type = flow.get(\"intersection_type\");\nvar intersection_threshold = flow.get(\"intersection_threshold\");\nvar object_counter = flow.get(\"object_counter\");\n\nmsg.payload = main(payload, distance_threshold, intersection_type, intersection_threshold, object_counter);\nflow.set(detection_name, msg);\nreturn msg;", + "func": "let region_name = \"predefined_regions_1\";\nlet detection_name = \"object_detection_1\";\nlet previous_data_name = \"previous_data_1\";\nlet occupancy_check_duration = 60;\nlet min_entry_frames = 30;\nlet video_id = 1;\nlet region_id = -1; //Recommend to set this only when there is 1 predefined region in the video\n\nvar payload = msg.payload;\nvar previous_data = flow.get(previous_data_name);\nvar stop_duration = flow.get(\"stop_duration\");\n\n// preparing bboxes\nfunction preprocess_bboxes(payload) {\n function getObjectData(object_data) {\n var current_object_data = {};\n if (object_data.hasOwnProperty(\"type\")) {\n current_object_data[\"type\"] = object_data[\"type\"];\n }\n if (object_data.hasOwnProperty(\"color\")) {\n current_object_data[\"color\"] = object_data[\"color\"];\n }\n if (object_data.hasOwnProperty(\"license_plate\")) {\n current_object_data[\"license_plate\"] = object_data[\"license_plate\"];\n }\n if (object_data.hasOwnProperty(\"id\")) {\n current_object_data[\"id\"] = object_data[\"id\"];\n }\n if (object_data.hasOwnProperty(\"roi_type\")) {\n current_object_data[\"roi_type\"] = object_data[\"roi_type\"];\n }\n return current_object_data;\n }\n function getLotBbox(lot_data) {\n return { \"vertex1\": lot_data.vertex1, \"vertex2\": lot_data.vertex2, \"vertex3\": lot_data.vertex3, \"vertex4\": lot_data.vertex4, \"vertex_0\": lot_data.vertex1 };\n }\n\n var results = {};\n var objects = [];\n var objects_extra = [];\n var lots = [];\n // get all objects bboxes\n for (let key in payload) {\n if (payload.hasOwnProperty(key)) {\n var objectData = getObjectData(payload[key]);\n if (objectData !== null) {\n objects_extra.push(\n objectData\n );\n objects.push(\n payload[key][\"bbox\"]\n );\n }\n }\n }\n // get all lots bboxes\n var predefined_regions = flow.get(region_name);\n var keys = Object.keys(predefined_regions);\n\n for (let i = 0; i < keys.length; i++) {\n if (predefined_regions.hasOwnProperty(keys[i]) && keys[i] != \"_msgid\") {\n lots.push(\n getLotBbox(predefined_regions[keys[i]])\n );\n }\n }\n\n results[\"objects\"] = objects;\n results[\"objects_extra\"] = objects_extra;\n results[\"lots\"] = lots;\n return results;\n}\n\n// euclidean distance and iou check\nfunction euclidean_iou_function(payload, distance_threshold = 100, intersection_type = \"region\", intersection_threshold = 0.5, object_counter = false) {\n function getBboxCentroid(bbox) {\n var centroid_x = (bbox[\"vertex1\"].x + bbox[\"vertex2\"].x + bbox[\"vertex3\"].x + bbox[\"vertex4\"].x) / 4;\n var centroid_y = (bbox[\"vertex1\"].y + bbox[\"vertex2\"].y + bbox[\"vertex3\"].y + bbox[\"vertex4\"].y) / 4;\n return [centroid_x, centroid_y];\n }\n function calculateEuclideanDistance(centroid1, centroid2) {\n return Math.sqrt((centroid2[0] - centroid1[0]) ** 2 + (centroid2[1] - centroid1[1]) ** 2);\n }\n function calculateIntersectArea(bbox1, bbox2) { // objects, lots\n var bbox1_key = Object.keys(bbox1)\n var bbox2_key = Object.keys(bbox2)\n var intersection = [];\n\n for (let i = 0; i < bbox1_key.length - 1; i++) {\n for (let j = 0; j < bbox2_key.length - 1; j++) {\n var A1 = bbox1[bbox1_key[i + 1]].y - bbox1[bbox1_key[i]].y; // Ax + By + C = 0\n var B1 = bbox1[bbox1_key[i]].x - bbox1[bbox1_key[i + 1]].x;\n var C1 = - A1 * bbox1[bbox1_key[i]].x - B1 * bbox1[bbox1_key[i]].y;\n\n var A2 = bbox2[bbox2_key[j + 1]].y - bbox2[bbox2_key[j]].y;\n var B2 = bbox2[bbox2_key[j]].x - bbox2[bbox2_key[j + 1]].x;\n var C2 = - A2 * bbox2[bbox2_key[j]].x - B2 * bbox2[bbox2_key[j]].y;\n\n // parallel lines case\n if (A1 * B2 == A2 * B1) {\n // coincide case\n if ((A1 * C2 == A2 * C1) && (B1 * C2 == B2 * C1)) {\n if (B1 == 0 && A1 == 0) { // one point bbox case\n intersection.push([bbox1[bbox1_key[i]].x.toFixed(6), bbox1[bbox1_key[i]].y.toFixed(6)]);\n }\n else if (B2 == 0 && A2 == 0) { // one point bbox case\n intersection.push([bbox2[bbox2_key[i]].x.toFixed(6), bbox2[bbox2_key[i]].y.toFixed(6)]);\n }\n else if (B1 == 0) { // vertical lines case\n var y_min1 = (bbox1[bbox1_key[i]].y < bbox1[bbox1_key[i + 1]].y) ? bbox1[bbox1_key[i]].y : bbox1[bbox1_key[i + 1]].y;\n var y_max1 = (bbox1[bbox1_key[i]].y < bbox1[bbox1_key[i + 1]].y) ? bbox1[bbox1_key[i + 1]].y : bbox1[bbox1_key[i]].y;\n var y_min2 = (bbox2[bbox2_key[i]].y < bbox2[bbox2_key[i + 1]].y) ? bbox2[bbox2_key[i]].y : bbox2[bbox2_key[i + 1]].y;\n var y_max2 = (bbox2[bbox2_key[i]].y < bbox2[bbox2_key[i + 1]].y) ? bbox2[bbox2_key[i + 1]].y : bbox2[bbox2_key[i]].y;\n if ((y_min1 < y_max2) && (y_min2 < y_max1)) {\n var y_min = (y_min1 < y_min2) ? y_min2 : y_min1;\n var y_max = (y_max1 < y_max2) ? y_max1 : y_max2;\n intersection.push([bbox1[bbox1_key[i]].x.toFixed(6), y_min.toFixed(6)]);\n intersection.push([bbox1[bbox1_key[i]].x.toFixed(6), y_max.toFixed(6)]);\n }\n }\n else if (A1 == 0) { // horizontal lines case\n var x_min1 = (bbox1[bbox1_key[i]].x < bbox1[bbox1_key[i + 1]].x) ? bbox1[bbox1_key[i]].x : bbox1[bbox1_key[i + 1]].x;\n var x_max1 = (bbox1[bbox1_key[i]].x < bbox1[bbox1_key[i + 1]].x) ? bbox1[bbox1_key[i + 1]].x : bbox1[bbox1_key[i]].x;\n var x_min2 = (bbox2[bbox2_key[i]].x < bbox2[bbox2_key[i + 1]].x) ? bbox2[bbox2_key[i]].x : bbox2[bbox2_key[i + 1]].x;\n var x_max2 = (bbox2[bbox2_key[i]].x < bbox2[bbox2_key[i + 1]].x) ? bbox2[bbox2_key[i + 1]].x : bbox2[bbox2_key[i]].x;\n if ((x_min1 < x_max2) && (x_min2 < x_max1)) {\n var x_min = (x_min1 < x_min2) ? x_min2 : x_min1;\n var x_max = (x_max1 < x_max2) ? x_max1 : x_max2;\n intersection.push([x_min.toFixed(6), bbox1[bbox1_key[i]].y.toFixed(6)]);\n intersection.push([x_max.toFixed(6), bbox1[bbox1_key[i]].y.toFixed(6)]);\n }\n }\n else {\n var x_min1 = (bbox1[bbox1_key[i]].x < bbox1[bbox1_key[i + 1]].x) ? bbox1[bbox1_key[i]].x : bbox1[bbox1_key[i + 1]].x;\n var x_max1 = (bbox1[bbox1_key[i]].x < bbox1[bbox1_key[i + 1]].x) ? bbox1[bbox1_key[i + 1]].x : bbox1[bbox1_key[i]].x;\n var x_min2 = (bbox2[bbox2_key[i]].x < bbox2[bbox2_key[i + 1]].x) ? bbox2[bbox2_key[i]].x : bbox2[bbox2_key[i + 1]].x;\n var x_max2 = (bbox2[bbox2_key[i]].x < bbox2[bbox2_key[i + 1]].x) ? bbox2[bbox2_key[i + 1]].x : bbox2[bbox2_key[i]].x;\n if ((x_min1 < x_max2) && (x_min2 < x_max1) && (y_min1 < y_max2) && (y_min2 < y_max1)) {\n var x_min = (x_min1 < x_min2) ? x_min2 : x_min1;\n var x_max = (x_max1 < x_max2) ? x_max1 : x_max2;\n var xY_min = (-A1 * x_min - C1) / B1\n var xY_max = (-A1 * x_max - C1) / B1\n intersection.push([x_min.toFixed(6), xY_min.toFixed(6)]);\n intersection.push([x_max.toFixed(6), xY_max.toFixed(6)]);\n }\n }\n }\n }\n else {\n // get intersection coordinates\n var x1 = bbox1[bbox1_key[i]].x;\n var x2 = bbox1[bbox1_key[i + 1]].x;\n var x3 = bbox2[bbox2_key[j]].x;\n var x4 = bbox2[bbox2_key[j + 1]].x;\n var y1 = bbox1[bbox1_key[i]].y;\n var y2 = bbox1[bbox1_key[i + 1]].y;\n var y3 = bbox2[bbox2_key[j]].y;\n var y4 = bbox2[bbox2_key[j + 1]].y;\n\n var den = (x2 - x1) * (y3 - y4) - (y2 - y1) * (x3 - x4);\n var t = ((x3 - x1) * (y3 - y4) - (y3 - y1) * (x3 - x4)) / den;\n var u = ((x3 - x1) * (y1 - y2) - (y3 - y1) * (x1 - x2)) / den;\n if ((t >= 0) && (t <= 1) && (u >= 0) && (u <= 1)) { // intersect happens if this condition is fulfilled\n var ix = x1 + t * (x2 - x1);\n var iy = y1 + t * (y2 - y1);\n intersection.push([ix.toFixed(6), iy.toFixed(6)]);\n }\n }\n }\n }\n // get clipped coordinates\n var specialcase1 = true;\n var specialcase2 = false;\n var temp1 = bbox1[bbox1_key[0]];\n var temp2 = bbox2[bbox2_key[0]];\n for (let i = 1; i < bbox1_key.length - 1; i++) { // special case\n if (temp1 !== bbox1[bbox1_key[i]]) {\n specialcase1 = false;\n break;\n }\n }\n for (let i = 1; i < bbox2_key.length - 1; i++) {\n if (temp2 !== bbox2[bbox2_key[i]]) {\n specialcase2 = false;\n break;\n }\n }\n if (!(specialcase2)) {\n for (let i = 0; i < bbox1_key.length - 1; i++) {\n var sign_check = [];\n for (let j = 0; j < bbox2_key.length - 1; j++) {\n var x1 = bbox2[bbox2_key[j]].x\n var x2 = bbox2[bbox2_key[j + 1]].x\n var y1 = bbox2[bbox2_key[j]].y\n var y2 = bbox2[bbox2_key[j + 1]].y\n var cross_prod = (bbox1[bbox1_key[i]].x - x1) * (y2 - y1) - (bbox1[bbox1_key[i]].y - y1) * (x2 - x1);\n var sign = (cross_prod >= 0) ? true : false;\n sign_check.push(sign);\n }\n if (sign_check.length === 4 && (sign_check.every((val, index) => val === [true, true, true, true][index]) ||\n sign_check.every((val, index) => val === [false, false, false, false][index]))) { // similar signs indicate the coordinate is within another bbox\n intersection.push([bbox1[bbox1_key[i]].x.toFixed(6), bbox1[bbox1_key[i]].y.toFixed(6)]);\n //intersection.push([bbox1[bbox1_key[i]].x, bbox1[bbox1_key[i]].y]);\n }\n }\n }\n if (!(specialcase1)) {\n for (let i = 0; i < bbox2_key.length - 1; i++) { // similar purpose but for second bbox\n var sign_check = [];\n for (let j = 0; j < bbox1_key.length - 1; j++) {\n var x1 = bbox1[bbox1_key[j]].x\n var x2 = bbox1[bbox1_key[j + 1]].x\n var y1 = bbox1[bbox1_key[j]].y\n var y2 = bbox1[bbox1_key[j + 1]].y\n var cross_prod = (bbox2[bbox2_key[i]].x - x1) * (y2 - y1) - (bbox2[bbox2_key[i]].y - y1) * (x2 - x1);\n var sign = (cross_prod >= 0) ? true : false;\n sign_check.push(sign);\n }\n if (sign_check.length === 4 && (sign_check.every((val, index) => val === [true, true, true, true][index]) ||\n sign_check.every((val, index) => val === [false, false, false, false][index]))) {\n intersection.push([bbox2[bbox2_key[i]].x.toFixed(6), bbox2[bbox2_key[i]].y.toFixed(6)]);\n //intersection.push([bbox2[bbox2_key[i]].x, bbox2[bbox2_key[i]].y]);\n }\n }\n }\n intersection = intersection.filter((item, index, self) =>\n index === self.findIndex((t) => JSON.stringify(t) === JSON.stringify(item))\n );\n // sort intersection points\n if (intersection.length > 2) {\n var intersection_angle = [];\n var intersection_distance = [];\n\n let minIndex = intersection.reduce((minIdx, currentValue, currentIndex, array) => {\n if (currentValue[0] < array[minIdx][0]) {\n return currentIndex;\n } else if (currentValue[0] === array[minIdx][0]) {\n return currentValue[1] < array[minIdx][1] ? currentIndex : minIdx;\n } else {\n return minIdx;\n }\n }, 0);\n var temp_intersection = intersection.filter((_, index) => index !== minIndex);\n for (let i = 0; i < temp_intersection.length; i++) {\n intersection_angle.push(Math.atan2(temp_intersection[i][1] - intersection[minIndex][1], temp_intersection[i][0] - intersection[minIndex][0]));\n intersection_distance.push((temp_intersection[i][1] - intersection[minIndex][1]) ** 2 + (temp_intersection[i][0] - intersection[minIndex][0]) ** 2);\n }\n let combined = temp_intersection.map((point, index) => ({ // sort intersect coordinates based on angle and then distance\n point: point,\n angle: intersection_angle[index],\n distance: intersection_distance[index]\n }));\n combined.sort((a, b) => a.angle - b.angle || a.distance - b.distance);\n let sorted_intersection = combined.map(item => item.point);\n sorted_intersection.unshift(intersection[minIndex]);\n sorted_intersection.push(intersection[minIndex]);\n // shoelace algorithm to calculate the intersect area\n var area = 0;\n for (let i = 0; i < sorted_intersection.length - 1; i++) {\n area += ((sorted_intersection[i][0] * sorted_intersection[i + 1][1])\n - (sorted_intersection[i + 1][0] * sorted_intersection[i][1]))\n }\n return area;\n }\n return 0;\n }\n function calculateIOU(bbox1, bbox2, intersectArea) {\n if (intersectArea !== 0) {\n var lot_area1 = (bbox1[\"vertex1\"].x * bbox1[\"vertex2\"].y) + (bbox1[\"vertex2\"].x * bbox1[\"vertex3\"].y)\n + (bbox1[\"vertex3\"].x * bbox1[\"vertex4\"].y) + (bbox1[\"vertex4\"].x * bbox1[\"vertex_0\"].y)\n - ((bbox1[\"vertex2\"].x * bbox1[\"vertex1\"].y) + (bbox1[\"vertex3\"].x * bbox1[\"vertex2\"].y)\n + (bbox1[\"vertex4\"].x * bbox1[\"vertex3\"].y) + (bbox1[\"vertex_0\"].x * bbox1[\"vertex4\"].y));\n\n var lot_area2 = (bbox2[\"vertex1\"].x * bbox2[\"vertex2\"].y) + (bbox2[\"vertex2\"].x * bbox2[\"vertex3\"].y)\n + (bbox2[\"vertex3\"].x * bbox2[\"vertex4\"].y) + (bbox2[\"vertex4\"].x * bbox2[\"vertex_0\"].y)\n - ((bbox2[\"vertex2\"].x * bbox2[\"vertex1\"].y) + (bbox2[\"vertex3\"].x * bbox2[\"vertex2\"].y)\n + (bbox2[\"vertex4\"].x * bbox2[\"vertex3\"].y) + (bbox2[\"vertex_0\"].x * bbox2[\"vertex4\"].y));\n return intersectArea / (lot_area1 + lot_area2 - intersectArea)\n }\n return 0;\n }\n function calculateIOR(bbox1, intersectArea) {\n if (intersectArea !== 0) {\n var lot_area = (bbox1[\"vertex1\"].x * bbox1[\"vertex2\"].y) + (bbox1[\"vertex2\"].x * bbox1[\"vertex3\"].y)\n + (bbox1[\"vertex3\"].x * bbox1[\"vertex4\"].y) + (bbox1[\"vertex4\"].x * bbox1[\"vertex_0\"].y)\n - ((bbox1[\"vertex2\"].x * bbox1[\"vertex1\"].y) + (bbox1[\"vertex3\"].x * bbox1[\"vertex2\"].y)\n + (bbox1[\"vertex4\"].x * bbox1[\"vertex3\"].y) + (bbox1[\"vertex_0\"].x * bbox1[\"vertex4\"].y));\n return intersectArea / lot_area;\n }\n return 0;\n }\n\n var occupied = [];\n var current_index = -1;\n // initialize lots to no object\n for (let key1 in payload[\"lots\"]) {\n if (payload[\"lots\"].hasOwnProperty(key1)) {\n occupied.push({ \"occupied\": 0 });\n }\n }\n for (let key1 in payload[\"objects\"]) {\n current_index++;\n if (payload[\"objects\"].hasOwnProperty(key1)) { // find all lots within the object centroid distance threshold\n var objectCentroid = getBboxCentroid(payload[\"objects\"][key1]);\n var potential_region = [];\n for (let key2 in payload[\"lots\"]) {\n if (payload[\"lots\"].hasOwnProperty(key2)) {\n var regionCentroid = getBboxCentroid(payload[\"lots\"][key2]);\n var euclideanDistance = calculateEuclideanDistance(objectCentroid, regionCentroid);\n potential_region.push(euclideanDistance);\n }\n }\n // calculate IOU or IOR\n if (object_counter) {\n for (let i = 0; i < potential_region.length; i++) {\n if (potential_region[i] < distance_threshold) {\n var intersectRegion = calculateIntersectArea(payload[\"objects\"][key1], payload[\"lots\"][i]);\n var IOR = 0;\n if (intersection_type == \"region\") {\n IOR = calculateIOR(payload[\"lots\"][i], intersectRegion)\n }\n else if (intersection_type == \"object\") {\n IOR = calculateIOR(payload[\"objects\"][key1], intersectRegion)\n }\n if (IOR > intersection_threshold) {\n occupied[i][\"occupied\"] += 1;\n var keyData = Object.keys(payload[\"objects_extra\"][current_index]);\n var numberData = keyData.length;\n for (let j = 0; j < numberData; j++) {\n if (!occupied[i][\"extra\"]) {\n occupied[i][\"extra\"] = {};\n }\n if (!occupied[i][\"extra\"][keyData[j]]) {\n occupied[i][\"extra\"][keyData[j]] = [];\n }\n occupied[i][\"extra\"][keyData[j]].push(payload[\"objects_extra\"][current_index][keyData[j]]);\n }\n }\n }\n }\n }\n else { // consider the best lot only\n var best_region_index = potential_region.reduce((minIndex, currentValue, currentIndex, array) =>\n currentValue < array[minIndex] ? currentIndex : minIndex, 0);\n if (potential_region[best_region_index] < distance_threshold) {\n var intersectRegion = calculateIntersectArea(payload[\"objects\"][key1], payload[\"lots\"][best_region_index]);\n var IOR = 0;\n if (intersection_type == \"region\") {\n IOR = calculateIOR(payload[\"lots\"][best_region_index], intersectRegion)\n }\n else if (intersection_type == \"object\") {\n IOR = calculateIOR(payload[\"objects\"][key1], intersectRegion)\n }\n if (IOR > intersection_threshold) {\n occupied[best_region_index][\"occupied\"] += 1;\n var keyData = Object.keys(payload[\"objects_extra\"][current_index]);\n var numberData = keyData.length;\n for (let i = 0; i < numberData; i++) {\n if (!occupied[best_region_index][\"extra\"]) {\n occupied[best_region_index][\"extra\"] = {};\n }\n if (!occupied[best_region_index][\"extra\"][keyData[i]]) {\n occupied[best_region_index][\"extra\"][keyData[i]] = [];\n }\n occupied[best_region_index][\"extra\"][keyData[i]].push(payload[\"objects_extra\"][current_index][keyData[i]]);\n }\n }\n }\n }\n }\n }\n return occupied;\n}\n\n// turn into correct format\nfunction postprocess_output(occupied) {\n var results = {};\n\n for (let i = 0; i < occupied.length; i++) {\n results = initializeResults(results, occupied, i);\n previous_data = initializePreviousData(results, i);\n if (results[\"Region \" + (i + 1)].hasOwnProperty(\"id\") || previous_data[\"Region \" + (i + 1)].hasOwnProperty(\"id\")) {\n results = handleObjectWithID(results, i);\n }\n else if (results[\"Region \" + (i + 1)][\"occupied\"] > 0) {\n results = handleOccupiedRegion(results, i);\n }\n else if (previous_data[\"Region \" + (i + 1)][\"entry_time\"]) {\n results = handleNotOccupiedRegion(results, i);\n }\n }\n return results;\n}\n\nfunction initializeResults(results, occupied, i) {\n const regionKey = \"Region \" + (i + 1);\n\n if (!results[regionKey]) {\n results[regionKey] = {};\n }\n results[regionKey][\"video_id\"] = video_id;\n results[regionKey][\"region_id\"] = (region_id != -1) ? region_id : i + 1;\n\n if (occupied[i].hasOwnProperty(\"occupied\")) {\n results[regionKey][\"occupied\"] = occupied[i].occupied;\n }\n\n if (occupied[i].hasOwnProperty(\"extra\")) {\n for (let key in occupied[i].extra) {\n results[regionKey][key] = occupied[i].extra[key];\n }\n }\n if (results[regionKey].hasOwnProperty(\"id\") || (previous_data && previous_data.hasOwnProperty(regionKey) && previous_data[regionKey].hasOwnProperty(\"id\"))) {\n results[regionKey][\"actual_entry_time\"] = [];\n results[regionKey][\"actual_dwell_time\"] = [];\n results[regionKey][\"entry_time\"] = [];\n results[regionKey][\"dwell_time\"] = [];\n results[regionKey][\"exit_time\"] = [];\n }\n else {\n results[\"Region \" + (i + 1)][\"actual_entry_time\"] = \"\";\n results[\"Region \" + (i + 1)][\"actual_dwell_time\"] = \"\";\n results[\"Region \" + (i + 1)][\"entry_time\"] = \"\";\n results[\"Region \" + (i + 1)][\"dwell_time\"] = \"\";\n results[\"Region \" + (i + 1)][\"exit_time\"] = \"\";\n results[\"Region \" + (i + 1)][\"occupancy_check\"] = 0;\n }\n return results;\n}\n\nfunction initializePreviousData(results, i) {\n const regionKey = \"Region \" + (i + 1);\n\n // Ensure previous_data is set up for this region.\n if (!previous_data) {\n previous_data = {};\n }\n if (results[regionKey].hasOwnProperty(\"id\")) {\n if (!previous_data[regionKey]) {\n previous_data[regionKey] = {};\n previous_data[regionKey][\"id\"] = [];\n previous_data[regionKey][\"actual_entry_time\"] = [];\n previous_data[regionKey][\"actual_dwell_time\"] = [];\n previous_data[regionKey][\"entry_time\"] = [];\n previous_data[regionKey][\"dwell_time\"] = [];\n previous_data[regionKey][\"exit_time\"] = [];\n }\n }\n else {\n if (!previous_data.hasOwnProperty(regionKey)) {\n previous_data[regionKey] = results[regionKey];\n previous_data[regionKey][\"actual_entry_time\"] = \"\";\n previous_data[regionKey][\"actual_dwell_time\"] = \"\";\n previous_data[regionKey][\"entry_time\"] = \"\";\n previous_data[regionKey][\"dwell_time\"] = \"\";\n previous_data[regionKey][\"exit_time\"] = \"\";\n previous_data[regionKey][\"occupancy_check\"] = 0;\n }\n }\n\n return previous_data;\n}\n\nfunction handleObjectWithID(results, i) {\n const regionKey = \"Region \" + (i + 1);\n const resultIds = results[regionKey][\"id\"];\n const previousDataIds = previous_data[regionKey][\"id\"];\n\n if (!resultIds && !previousDataIds) {\n return results;\n }\n\n if (resultIds) {\n for (let j = 0; j < resultIds.length; j++) {\n // Check existing id data\n let prevIndex = -1;\n\n if (previous_data[regionKey][\"id\"] && previous_data[regionKey][\"id\"].length > 0) {\n prevIndex = previous_data[regionKey][\"id\"].indexOf(resultIds[j]);\n }\n\n if (prevIndex === -1) {\n results[regionKey][\"actual_entry_time\"].push(msg.nodered_timestamp);\n results[regionKey][\"entry_time\"].push(msg.object_timestamp);\n results[regionKey][\"actual_dwell_time\"].push(\"00:00:00\");\n results[regionKey][\"dwell_time\"].push(0);\n results[regionKey][\"exit_time\"].push(\"\");\n } else {\n results[regionKey][\"actual_entry_time\"].push(previous_data[regionKey][\"actual_entry_time\"][prevIndex]);\n results[regionKey][\"entry_time\"].push(previous_data[regionKey][\"entry_time\"][prevIndex]);\n\n // Calculate dwell time.\n const dwell_timestamp = (msg.object_timestamp - results[regionKey][\"entry_time\"][j]);\n const dwell_hours = Math.floor(dwell_timestamp / 3600).toString().padStart(2, \"0\");\n const dwell_mins = Math.floor((dwell_timestamp / 60) % 60).toString().padStart(2, \"0\");\n const dwell_seconds = Math.floor(dwell_timestamp % 60).toString().padStart(2, \"0\");\n results[regionKey][\"dwell_time\"].push(dwell_timestamp);\n results[regionKey][\"actual_dwell_time\"].push(dwell_hours + \":\" + dwell_mins + \":\" + dwell_seconds);\n results[regionKey][\"exit_time\"].push(\"\");\n }\n }\n }\n\n if (previousDataIds) {\n for (let j = 0; j < previousDataIds.length; j++) {\n let prevIndex = -1;\n\n if (!results[regionKey][\"id\"]) {\n results[regionKey] = previous_data[regionKey];\n results[regionKey][\"occupied\"] = 0;\n break;\n }\n else {\n if (!results[regionKey][\"dwell_time\"].some(val => val >= stop_duration)) {\n results[regionKey][\"occupied\"] = 0;\n }\n\n prevIndex = results[regionKey][\"id\"].indexOf(previousDataIds[j]);\n if (prevIndex === -1) {\n let keyList = Object.keys(previous_data[regionKey]);\n for (let k = 0; k < keyList.length; k++) {\n if (keyList[k] == \"video_id\" || keyList[k] == \"region_id\" || keyList[k] == \"occupied\") {\n continue;\n }\n results[regionKey][keyList[k]].push(previous_data[regionKey][keyList[k]][j]);\n }\n }\n }\n }\n }\n return results;\n}\n\nfunction handleOccupiedRegion(results, i) {\n const regionKey = \"Region \" + (i + 1);\n var prev_consecutive = (previous_data[regionKey][\"consecutive_occupied\"]) || 0;\n results[regionKey][\"consecutive_occupied\"] = prev_consecutive + 1;\n if (results[regionKey][\"consecutive_occupied\"] < min_entry_frames) {\n results[regionKey][\"occupied\"] = 0;\n results[regionKey][\"occupancy_check\"] = 0;\n return results;\n }\n if (previous_data[regionKey][\"entry_time\"] == \"\") {\n results[regionKey][\"actual_entry_time\"] = msg.nodered_timestamp;\n results[regionKey][\"entry_time\"] = msg.object_timestamp;\n }\n else {\n if (previous_data[regionKey][\"occupancy_check\"] >= occupancy_check_duration) {\n results[regionKey][\"actual_dwell_time\"] = \"00:00:00\";\n results[regionKey][\"dwell_time\"] = 0;\n results[regionKey][\"actual_entry_time\"] = msg.nodered_timestamp;\n results[regionKey][\"entry_time\"] = msg.object_timestamp;\n }\n else {\n results[regionKey][\"actual_entry_time\"] = previous_data[regionKey][\"actual_entry_time\"];\n results[regionKey][\"entry_time\"] = previous_data[regionKey][\"entry_time\"];\n }\n\n var dwell_timestamp = msg.object_timestamp - results[regionKey][\"entry_time\"];\n var dwell_hours = Math.floor(dwell_timestamp / 3600).toString();\n dwell_hours = dwell_hours.toString().length == 1 ? dwell_hours.toString().padStart(2, \"0\") : dwell_hours.toString();\n var dwell_mins = Math.floor((dwell_timestamp / 60) % 60).toString().padStart(2, \"0\");\n var dwell_seconds = Math.floor(dwell_timestamp % 60).toString().padStart(2, \"0\");\n\n results[regionKey][\"dwell_time\"] = dwell_timestamp;\n results[regionKey][\"actual_dwell_time\"] = dwell_hours + \":\" + dwell_mins + \":\" + dwell_seconds;\n results[regionKey][\"occupancy_check\"] = 0;\n }\n\n return results;\n}\n\nfunction handleNotOccupiedRegion(results, i) {\n const regionKey = \"Region \" + (i + 1);\n\n results[regionKey][\"actual_entry_time\"] = previous_data[regionKey][\"actual_entry_time\"];\n results[regionKey][\"entry_time\"] = previous_data[regionKey][\"entry_time\"];\n results[regionKey][\"actual_dwell_time\"] = previous_data[regionKey][\"actual_dwell_time\"];\n results[regionKey][\"dwell_time\"] = previous_data[regionKey][\"dwell_time\"];\n results[regionKey][\"occupancy_check\"] = previous_data[regionKey][\"occupancy_check\"] + 1;\n\n if (previous_data[regionKey][\"exit_time\"]) {\n results[regionKey][\"exit_time\"] = previous_data[regionKey][\"exit_time\"];\n }\n else {\n results[regionKey][\"exit_time\"] = msg.nodered_timestamp;\n }\n return results;\n}\n\nfunction main(payload, distance_threshold = 100, intersection_type = \"region\", intersection_threshold = 0.5, object_counter = false) {\n var results = preprocess_bboxes(payload);\n var occupied = euclidean_iou_function(results, distance_threshold, intersection_type, intersection_threshold, object_counter);\n var out = postprocess_output(occupied);\n flow.set(previous_data_name, out);\n return out;\n}\n\n// user-defined parameters\nvar distance_threshold = flow.get(\"distance_threshold\");\nvar intersection_type = flow.get(\"intersection_type\");\nvar intersection_threshold = flow.get(\"intersection_threshold\");\nvar object_counter = flow.get(\"object_counter\");\n\nmsg.payload = main(payload, distance_threshold, intersection_type, intersection_threshold, object_counter);\nflow.set(detection_name, msg);\nreturn msg;", "outputs": 1, "timeout": 0, "noerr": 0, @@ -1370,7 +1370,7 @@ "type": "euclidean and ior", "z": "2720733497fe05f3", "name": "euclidean and ior 2", - "func": "let region_name = \"predefined_regions_2\";\nlet detection_name = \"object_detection_2\";\nlet previous_data_name = \"previous_data_2\";\nlet occupancy_check_duration = 60;\nlet video_id = 2;\nlet region_id = -1; //Recommend to set this only when there is 1 predefined region in the video\n\nvar payload = msg.payload;\nvar previous_data = flow.get(previous_data_name);\nvar stop_duration = flow.get(\"stop_duration\");\n\n// preparing bboxes\nfunction preprocess_bboxes(payload) {\n function getObjectData(object_data) {\n var current_object_data = {};\n if (object_data.hasOwnProperty(\"type\")) {\n current_object_data[\"type\"] = object_data[\"type\"];\n }\n if (object_data.hasOwnProperty(\"color\")) {\n current_object_data[\"color\"] = object_data[\"color\"];\n }\n if (object_data.hasOwnProperty(\"license_plate\")) {\n current_object_data[\"license_plate\"] = object_data[\"license_plate\"];\n }\n if (object_data.hasOwnProperty(\"id\")) {\n current_object_data[\"id\"] = object_data[\"id\"];\n }\n if (object_data.hasOwnProperty(\"roi_type\")) {\n current_object_data[\"roi_type\"] = object_data[\"roi_type\"];\n }\n return current_object_data;\n }\n function getLotBbox(lot_data) {\n return { \"vertex1\": lot_data.vertex1, \"vertex2\": lot_data.vertex2, \"vertex3\": lot_data.vertex3, \"vertex4\": lot_data.vertex4, \"vertex_0\": lot_data.vertex1 };\n }\n\n var results = {};\n var objects = [];\n var objects_extra = [];\n var lots = [];\n // get all objects bboxes\n for (let key in payload) {\n if (payload.hasOwnProperty(key)) {\n var objectData = getObjectData(payload[key]);\n if (objectData !== null) {\n objects_extra.push(\n objectData\n );\n objects.push(\n payload[key][\"bbox\"]\n );\n }\n }\n }\n // get all lots bboxes\n var predefined_regions = flow.get(region_name);\n var keys = Object.keys(predefined_regions);\n\n for (let i = 0; i < keys.length; i++) {\n if (predefined_regions.hasOwnProperty(keys[i]) && keys[i] != \"_msgid\") {\n lots.push(\n getLotBbox(predefined_regions[keys[i]])\n );\n }\n }\n\n results[\"objects\"] = objects;\n results[\"objects_extra\"] = objects_extra;\n results[\"lots\"] = lots;\n return results;\n}\n\n// euclidean distance and iou check\nfunction euclidean_iou_function(payload, distance_threshold = 100, intersection_type = \"region\", intersection_threshold = 0.5, object_counter = false) {\n function getBboxCentroid(bbox) {\n var centroid_x = (bbox[\"vertex1\"].x + bbox[\"vertex2\"].x + bbox[\"vertex3\"].x + bbox[\"vertex4\"].x) / 4;\n var centroid_y = (bbox[\"vertex1\"].y + bbox[\"vertex2\"].y + bbox[\"vertex3\"].y + bbox[\"vertex4\"].y) / 4;\n return [centroid_x, centroid_y];\n }\n function calculateEuclideanDistance(centroid1, centroid2) {\n return Math.sqrt((centroid2[0] - centroid1[0]) ** 2 + (centroid2[1] - centroid1[1]) ** 2);\n }\n function calculateIntersectArea(bbox1, bbox2) { // objects, lots\n var bbox1_key = Object.keys(bbox1)\n var bbox2_key = Object.keys(bbox2)\n var intersection = [];\n\n for (let i = 0; i < bbox1_key.length - 1; i++) {\n for (let j = 0; j < bbox2_key.length - 1; j++) {\n var A1 = bbox1[bbox1_key[i + 1]].y - bbox1[bbox1_key[i]].y; // Ax + By + C = 0\n var B1 = bbox1[bbox1_key[i]].x - bbox1[bbox1_key[i + 1]].x;\n var C1 = - A1 * bbox1[bbox1_key[i]].x - B1 * bbox1[bbox1_key[i]].y;\n\n var A2 = bbox2[bbox2_key[j + 1]].y - bbox2[bbox2_key[j]].y;\n var B2 = bbox2[bbox2_key[j]].x - bbox2[bbox2_key[j + 1]].x;\n var C2 = - A2 * bbox2[bbox2_key[j]].x - B2 * bbox2[bbox2_key[j]].y;\n\n // parallel lines case\n if (A1 * B2 == A2 * B1) {\n // coincide case\n if ((A1 * C2 == A2 * C1) && (B1 * C2 == B2 * C1)) {\n if (B1 == 0 && A1 == 0) { // one point bbox case\n intersection.push([bbox1[bbox1_key[i]].x.toFixed(6), bbox1[bbox1_key[i]].y.toFixed(6)]);\n }\n else if (B2 == 0 && A2 == 0) { // one point bbox case\n intersection.push([bbox2[bbox2_key[i]].x.toFixed(6), bbox2[bbox2_key[i]].y.toFixed(6)]);\n }\n else if (B1 == 0) { // vertical lines case\n var y_min1 = (bbox1[bbox1_key[i]].y < bbox1[bbox1_key[i + 1]].y) ? bbox1[bbox1_key[i]].y : bbox1[bbox1_key[i + 1]].y;\n var y_max1 = (bbox1[bbox1_key[i]].y < bbox1[bbox1_key[i + 1]].y) ? bbox1[bbox1_key[i + 1]].y : bbox1[bbox1_key[i]].y;\n var y_min2 = (bbox2[bbox2_key[i]].y < bbox2[bbox2_key[i + 1]].y) ? bbox2[bbox2_key[i]].y : bbox2[bbox2_key[i + 1]].y;\n var y_max2 = (bbox2[bbox2_key[i]].y < bbox2[bbox2_key[i + 1]].y) ? bbox2[bbox2_key[i + 1]].y : bbox2[bbox2_key[i]].y;\n if ((y_min1 < y_max2) && (y_min2 < y_max1)) {\n var y_min = (y_min1 < y_min2) ? y_min2 : y_min1;\n var y_max = (y_max1 < y_max2) ? y_max1 : y_max2;\n intersection.push([bbox1[bbox1_key[i]].x.toFixed(6), y_min.toFixed(6)]);\n intersection.push([bbox1[bbox1_key[i]].x.toFixed(6), y_max.toFixed(6)]);\n }\n }\n else if (A1 == 0) { // horizontal lines case\n var x_min1 = (bbox1[bbox1_key[i]].x < bbox1[bbox1_key[i + 1]].x) ? bbox1[bbox1_key[i]].x : bbox1[bbox1_key[i + 1]].x;\n var x_max1 = (bbox1[bbox1_key[i]].x < bbox1[bbox1_key[i + 1]].x) ? bbox1[bbox1_key[i + 1]].x : bbox1[bbox1_key[i]].x;\n var x_min2 = (bbox2[bbox2_key[i]].x < bbox2[bbox2_key[i + 1]].x) ? bbox2[bbox2_key[i]].x : bbox2[bbox2_key[i + 1]].x;\n var x_max2 = (bbox2[bbox2_key[i]].x < bbox2[bbox2_key[i + 1]].x) ? bbox2[bbox2_key[i + 1]].x : bbox2[bbox2_key[i]].x;\n if ((x_min1 < x_max2) && (x_min2 < x_max1)) {\n var x_min = (x_min1 < x_min2) ? x_min2 : x_min1;\n var x_max = (x_max1 < x_max2) ? x_max1 : x_max2;\n intersection.push([x_min.toFixed(6), bbox1[bbox1_key[i]].y.toFixed(6)]);\n intersection.push([x_max.toFixed(6), bbox1[bbox1_key[i]].y.toFixed(6)]);\n }\n }\n else {\n var x_min1 = (bbox1[bbox1_key[i]].x < bbox1[bbox1_key[i + 1]].x) ? bbox1[bbox1_key[i]].x : bbox1[bbox1_key[i + 1]].x;\n var x_max1 = (bbox1[bbox1_key[i]].x < bbox1[bbox1_key[i + 1]].x) ? bbox1[bbox1_key[i + 1]].x : bbox1[bbox1_key[i]].x;\n var x_min2 = (bbox2[bbox2_key[i]].x < bbox2[bbox2_key[i + 1]].x) ? bbox2[bbox2_key[i]].x : bbox2[bbox2_key[i + 1]].x;\n var x_max2 = (bbox2[bbox2_key[i]].x < bbox2[bbox2_key[i + 1]].x) ? bbox2[bbox2_key[i + 1]].x : bbox2[bbox2_key[i]].x;\n if ((x_min1 < x_max2) && (x_min2 < x_max1) && (y_min1 < y_max2) && (y_min2 < y_max1)) {\n var x_min = (x_min1 < x_min2) ? x_min2 : x_min1;\n var x_max = (x_max1 < x_max2) ? x_max1 : x_max2;\n var xY_min = (-A1 * x_min - C1) / B1\n var xY_max = (-A1 * x_max - C1) / B1\n intersection.push([x_min.toFixed(6), xY_min.toFixed(6)]);\n intersection.push([x_max.toFixed(6), xY_max.toFixed(6)]);\n }\n }\n }\n }\n else {\n // get intersection coordinates\n var x1 = bbox1[bbox1_key[i]].x;\n var x2 = bbox1[bbox1_key[i + 1]].x;\n var x3 = bbox2[bbox2_key[j]].x;\n var x4 = bbox2[bbox2_key[j + 1]].x;\n var y1 = bbox1[bbox1_key[i]].y;\n var y2 = bbox1[bbox1_key[i + 1]].y;\n var y3 = bbox2[bbox2_key[j]].y;\n var y4 = bbox2[bbox2_key[j + 1]].y;\n\n var den = (x2 - x1) * (y3 - y4) - (y2 - y1) * (x3 - x4);\n var t = ((x3 - x1) * (y3 - y4) - (y3 - y1) * (x3 - x4)) / den;\n var u = ((x3 - x1) * (y1 - y2) - (y3 - y1) * (x1 - x2)) / den;\n if ((t >= 0) && (t <= 1) && (u >= 0) && (u <= 1)) { // intersect happens if this condition is fulfilled\n var ix = x1 + t * (x2 - x1);\n var iy = y1 + t * (y2 - y1);\n intersection.push([ix.toFixed(6), iy.toFixed(6)]);\n }\n }\n }\n }\n // get clipped coordinates\n var specialcase1 = true;\n var specialcase2 = false;\n var temp1 = bbox1[bbox1_key[0]];\n var temp2 = bbox2[bbox2_key[0]];\n for (let i = 1; i < bbox1_key.length - 1; i++) { // special case\n if (temp1 !== bbox1[bbox1_key[i]]) {\n specialcase1 = false;\n break;\n }\n }\n for (let i = 1; i < bbox2_key.length - 1; i++) {\n if (temp2 !== bbox2[bbox2_key[i]]) {\n specialcase2 = false;\n break;\n }\n }\n if (!(specialcase2)) {\n for (let i = 0; i < bbox1_key.length - 1; i++) {\n var sign_check = [];\n for (let j = 0; j < bbox2_key.length - 1; j++) {\n var x1 = bbox2[bbox2_key[j]].x\n var x2 = bbox2[bbox2_key[j + 1]].x\n var y1 = bbox2[bbox2_key[j]].y\n var y2 = bbox2[bbox2_key[j + 1]].y\n var cross_prod = (bbox1[bbox1_key[i]].x - x1) * (y2 - y1) - (bbox1[bbox1_key[i]].y - y1) * (x2 - x1);\n var sign = (cross_prod >= 0) ? true : false;\n sign_check.push(sign);\n }\n if (sign_check.length === 4 && (sign_check.every((val, index) => val === [true, true, true, true][index]) ||\n sign_check.every((val, index) => val === [false, false, false, false][index]))) { // similar signs indicate the coordinate is within another bbox\n intersection.push([bbox1[bbox1_key[i]].x.toFixed(6), bbox1[bbox1_key[i]].y.toFixed(6)]);\n //intersection.push([bbox1[bbox1_key[i]].x, bbox1[bbox1_key[i]].y]);\n }\n }\n }\n if (!(specialcase1)) {\n for (let i = 0; i < bbox2_key.length - 1; i++) { // similar purpose but for second bbox\n var sign_check = [];\n for (let j = 0; j < bbox1_key.length - 1; j++) {\n var x1 = bbox1[bbox1_key[j]].x\n var x2 = bbox1[bbox1_key[j + 1]].x\n var y1 = bbox1[bbox1_key[j]].y\n var y2 = bbox1[bbox1_key[j + 1]].y\n var cross_prod = (bbox2[bbox2_key[i]].x - x1) * (y2 - y1) - (bbox2[bbox2_key[i]].y - y1) * (x2 - x1);\n var sign = (cross_prod >= 0) ? true : false;\n sign_check.push(sign);\n }\n if (sign_check.length === 4 && (sign_check.every((val, index) => val === [true, true, true, true][index]) ||\n sign_check.every((val, index) => val === [false, false, false, false][index]))) {\n intersection.push([bbox2[bbox2_key[i]].x.toFixed(6), bbox2[bbox2_key[i]].y.toFixed(6)]);\n //intersection.push([bbox2[bbox2_key[i]].x, bbox2[bbox2_key[i]].y]);\n }\n }\n }\n intersection = intersection.filter((item, index, self) =>\n index === self.findIndex((t) => JSON.stringify(t) === JSON.stringify(item))\n );\n // sort intersection points\n if (intersection.length > 2) {\n var intersection_angle = [];\n var intersection_distance = [];\n\n let minIndex = intersection.reduce((minIdx, currentValue, currentIndex, array) => {\n if (currentValue[0] < array[minIdx][0]) {\n return currentIndex;\n } else if (currentValue[0] === array[minIdx][0]) {\n return currentValue[1] < array[minIdx][1] ? currentIndex : minIdx;\n } else {\n return minIdx;\n }\n }, 0);\n var temp_intersection = intersection.filter((_, index) => index !== minIndex);\n for (let i = 0; i < temp_intersection.length; i++) {\n intersection_angle.push(Math.atan2(temp_intersection[i][1] - intersection[minIndex][1], temp_intersection[i][0] - intersection[minIndex][0]));\n intersection_distance.push((temp_intersection[i][1] - intersection[minIndex][1]) ** 2 + (temp_intersection[i][0] - intersection[minIndex][0]) ** 2);\n }\n let combined = temp_intersection.map((point, index) => ({ // sort intersect coordinates based on angle and then distance\n point: point,\n angle: intersection_angle[index],\n distance: intersection_distance[index]\n }));\n combined.sort((a, b) => a.angle - b.angle || a.distance - b.distance);\n let sorted_intersection = combined.map(item => item.point);\n sorted_intersection.unshift(intersection[minIndex]);\n sorted_intersection.push(intersection[minIndex]);\n // shoelace algorithm to calculate the intersect area\n var area = 0;\n for (let i = 0; i < sorted_intersection.length - 1; i++) {\n area += ((sorted_intersection[i][0] * sorted_intersection[i + 1][1])\n - (sorted_intersection[i + 1][0] * sorted_intersection[i][1]))\n }\n return area;\n }\n return 0;\n }\n function calculateIOU(bbox1, bbox2, intersectArea) {\n if (intersectArea !== 0) {\n var lot_area1 = (bbox1[\"vertex1\"].x * bbox1[\"vertex2\"].y) + (bbox1[\"vertex2\"].x * bbox1[\"vertex3\"].y)\n + (bbox1[\"vertex3\"].x * bbox1[\"vertex4\"].y) + (bbox1[\"vertex4\"].x * bbox1[\"vertex_0\"].y)\n - ((bbox1[\"vertex2\"].x * bbox1[\"vertex1\"].y) + (bbox1[\"vertex3\"].x * bbox1[\"vertex2\"].y)\n + (bbox1[\"vertex4\"].x * bbox1[\"vertex3\"].y) + (bbox1[\"vertex_0\"].x * bbox1[\"vertex4\"].y));\n\n var lot_area2 = (bbox2[\"vertex1\"].x * bbox2[\"vertex2\"].y) + (bbox2[\"vertex2\"].x * bbox2[\"vertex3\"].y)\n + (bbox2[\"vertex3\"].x * bbox2[\"vertex4\"].y) + (bbox2[\"vertex4\"].x * bbox2[\"vertex_0\"].y)\n - ((bbox2[\"vertex2\"].x * bbox2[\"vertex1\"].y) + (bbox2[\"vertex3\"].x * bbox2[\"vertex2\"].y)\n + (bbox2[\"vertex4\"].x * bbox2[\"vertex3\"].y) + (bbox2[\"vertex_0\"].x * bbox2[\"vertex4\"].y));\n return intersectArea / (lot_area1 + lot_area2 - intersectArea)\n }\n return 0;\n }\n function calculateIOR(bbox1, intersectArea) {\n if (intersectArea !== 0) {\n var lot_area = (bbox1[\"vertex1\"].x * bbox1[\"vertex2\"].y) + (bbox1[\"vertex2\"].x * bbox1[\"vertex3\"].y)\n + (bbox1[\"vertex3\"].x * bbox1[\"vertex4\"].y) + (bbox1[\"vertex4\"].x * bbox1[\"vertex_0\"].y)\n - ((bbox1[\"vertex2\"].x * bbox1[\"vertex1\"].y) + (bbox1[\"vertex3\"].x * bbox1[\"vertex2\"].y)\n + (bbox1[\"vertex4\"].x * bbox1[\"vertex3\"].y) + (bbox1[\"vertex_0\"].x * bbox1[\"vertex4\"].y));\n return intersectArea / lot_area;\n }\n return 0;\n }\n\n var occupied = [];\n var current_index = -1;\n // initialize lots to no object\n for (let key1 in payload[\"lots\"]) {\n if (payload[\"lots\"].hasOwnProperty(key1)) {\n occupied.push({ \"occupied\": 0 });\n }\n }\n for (let key1 in payload[\"objects\"]) {\n current_index++;\n if (payload[\"objects\"].hasOwnProperty(key1)) { // find all lots within the object centroid distance threshold\n var objectCentroid = getBboxCentroid(payload[\"objects\"][key1]);\n var potential_region = [];\n for (let key2 in payload[\"lots\"]) {\n if (payload[\"lots\"].hasOwnProperty(key2)) {\n var regionCentroid = getBboxCentroid(payload[\"lots\"][key2]);\n var euclideanDistance = calculateEuclideanDistance(objectCentroid, regionCentroid);\n potential_region.push(euclideanDistance);\n }\n }\n // calculate IOU or IOR\n if (object_counter) {\n for (let i = 0; i < potential_region.length; i++) {\n if (potential_region[i] < distance_threshold) {\n var intersectRegion = calculateIntersectArea(payload[\"objects\"][key1], payload[\"lots\"][i]);\n var IOR = 0;\n if (intersection_type == \"region\") {\n IOR = calculateIOR(payload[\"lots\"][i], intersectRegion)\n }\n else if (intersection_type == \"object\") {\n IOR = calculateIOR(payload[\"objects\"][key1], intersectRegion)\n }\n if (IOR > intersection_threshold) {\n occupied[i][\"occupied\"] += 1;\n var keyData = Object.keys(payload[\"objects_extra\"][current_index]);\n var numberData = keyData.length;\n for (let j = 0; j < numberData; j++) {\n if (!occupied[i][\"extra\"]) {\n occupied[i][\"extra\"] = {};\n }\n if (!occupied[i][\"extra\"][keyData[j]]) {\n occupied[i][\"extra\"][keyData[j]] = [];\n }\n occupied[i][\"extra\"][keyData[j]].push(payload[\"objects_extra\"][current_index][keyData[j]]);\n }\n }\n }\n }\n }\n else { // consider the best lot only\n var best_region_index = potential_region.reduce((minIndex, currentValue, currentIndex, array) =>\n currentValue < array[minIndex] ? currentIndex : minIndex, 0);\n if (potential_region[best_region_index] < distance_threshold) {\n var intersectRegion = calculateIntersectArea(payload[\"objects\"][key1], payload[\"lots\"][best_region_index]);\n var IOR = 0;\n if (intersection_type == \"region\") {\n IOR = calculateIOR(payload[\"lots\"][best_region_index], intersectRegion)\n }\n else if (intersection_type == \"object\") {\n IOR = calculateIOR(payload[\"objects\"][key1], intersectRegion)\n }\n if (IOR > intersection_threshold) {\n occupied[best_region_index][\"occupied\"] += 1;\n var keyData = Object.keys(payload[\"objects_extra\"][current_index]);\n var numberData = keyData.length;\n for (let i = 0; i < numberData; i++) {\n if (!occupied[best_region_index][\"extra\"]) {\n occupied[best_region_index][\"extra\"] = {};\n }\n if (!occupied[best_region_index][\"extra\"][keyData[i]]) {\n occupied[best_region_index][\"extra\"][keyData[i]] = [];\n }\n occupied[best_region_index][\"extra\"][keyData[i]].push(payload[\"objects_extra\"][current_index][keyData[i]]);\n }\n }\n }\n }\n }\n }\n return occupied;\n}\n\n// turn into correct format\nfunction postprocess_output(occupied) {\n var results = {};\n\n for (let i = 0; i < occupied.length; i++) {\n results = initializeResults(results, occupied, i);\n previous_data = initializePreviousData(results, i);\n if (results[\"Region \" + (i + 1)].hasOwnProperty(\"id\") || previous_data[\"Region \" + (i + 1)].hasOwnProperty(\"id\")) {\n results = handleObjectWithID(results, i);\n }\n else if (results[\"Region \" + (i + 1)][\"occupied\"] > 0) {\n results = handleOccupiedRegion(results, i);\n }\n else if (previous_data[\"Region \" + (i + 1)][\"entry_time\"]) {\n results = handleNotOccupiedRegion(results, i);\n }\n }\n return results;\n}\n\nfunction initializeResults(results, occupied, i) {\n const regionKey = \"Region \" + (i + 1);\n\n if (!results[regionKey]) {\n results[regionKey] = {};\n }\n results[regionKey][\"video_id\"] = video_id;\n results[regionKey][\"region_id\"] = (region_id != -1) ? region_id : i + 1;\n\n if (occupied[i].hasOwnProperty(\"occupied\")) {\n results[regionKey][\"occupied\"] = occupied[i].occupied;\n }\n\n if (occupied[i].hasOwnProperty(\"extra\")) {\n for (let key in occupied[i].extra) {\n results[regionKey][key] = occupied[i].extra[key];\n }\n }\n if (results[regionKey].hasOwnProperty(\"id\") || (previous_data && previous_data.hasOwnProperty(regionKey) && previous_data[regionKey].hasOwnProperty(\"id\"))) {\n results[regionKey][\"actual_entry_time\"] = [];\n results[regionKey][\"actual_dwell_time\"] = [];\n results[regionKey][\"entry_time\"] = [];\n results[regionKey][\"dwell_time\"] = [];\n results[regionKey][\"exit_time\"] = [];\n }\n else {\n results[\"Region \" + (i + 1)][\"actual_entry_time\"] = \"\";\n results[\"Region \" + (i + 1)][\"actual_dwell_time\"] = \"\";\n results[\"Region \" + (i + 1)][\"entry_time\"] = \"\";\n results[\"Region \" + (i + 1)][\"dwell_time\"] = \"\";\n results[\"Region \" + (i + 1)][\"exit_time\"] = \"\";\n results[\"Region \" + (i + 1)][\"occupancy_check\"] = 0;\n }\n return results;\n}\n\nfunction initializePreviousData(results, i) {\n const regionKey = \"Region \" + (i + 1);\n\n // Ensure previous_data is set up for this region.\n if (!previous_data) {\n previous_data = {};\n }\n if (results[regionKey].hasOwnProperty(\"id\")) {\n if (!previous_data[regionKey]) {\n previous_data[regionKey] = {};\n previous_data[regionKey][\"id\"] = [];\n previous_data[regionKey][\"actual_entry_time\"] = [];\n previous_data[regionKey][\"actual_dwell_time\"] = [];\n previous_data[regionKey][\"entry_time\"] = [];\n previous_data[regionKey][\"dwell_time\"] = [];\n previous_data[regionKey][\"exit_time\"] = [];\n }\n }\n else {\n if (!previous_data.hasOwnProperty(regionKey)) {\n previous_data[regionKey] = results[regionKey];\n previous_data[regionKey][\"actual_entry_time\"] = \"\";\n previous_data[regionKey][\"actual_dwell_time\"] = \"\";\n previous_data[regionKey][\"entry_time\"] = \"\";\n previous_data[regionKey][\"dwell_time\"] = \"\";\n previous_data[regionKey][\"exit_time\"] = \"\";\n previous_data[regionKey][\"occupancy_check\"] = 0;\n }\n }\n\n return previous_data;\n}\n\nfunction handleObjectWithID(results, i) {\n const regionKey = \"Region \" + (i + 1);\n const resultIds = results[regionKey][\"id\"];\n const previousDataIds = previous_data[regionKey][\"id\"];\n\n if (!resultIds && !previousDataIds) {\n return results;\n }\n\n if (resultIds) {\n for (let j = 0; j < resultIds.length; j++) {\n // Check existing id data\n let prevIndex = -1;\n\n if (previous_data[regionKey][\"id\"] && previous_data[regionKey][\"id\"].length > 0) {\n prevIndex = previous_data[regionKey][\"id\"].indexOf(resultIds[j]);\n }\n\n if (prevIndex === -1) {\n results[regionKey][\"actual_entry_time\"].push(msg.nodered_timestamp);\n results[regionKey][\"entry_time\"].push(msg.object_timestamp);\n results[regionKey][\"actual_dwell_time\"].push(\"00:00:00\");\n results[regionKey][\"dwell_time\"].push(0);\n results[regionKey][\"exit_time\"].push(\"\");\n } else {\n results[regionKey][\"actual_entry_time\"].push(previous_data[regionKey][\"actual_entry_time\"][prevIndex]);\n results[regionKey][\"entry_time\"].push(previous_data[regionKey][\"entry_time\"][prevIndex]);\n\n // Calculate dwell time.\n const dwell_timestamp = (msg.object_timestamp - results[regionKey][\"entry_time\"][j]);\n const dwell_hours = Math.floor(dwell_timestamp / 3600).toString().padStart(2, \"0\");\n const dwell_mins = Math.floor((dwell_timestamp / 60) % 60).toString().padStart(2, \"0\");\n const dwell_seconds = Math.floor(dwell_timestamp % 60).toString().padStart(2, \"0\");\n results[regionKey][\"dwell_time\"].push(dwell_timestamp);\n results[regionKey][\"actual_dwell_time\"].push(dwell_hours + \":\" + dwell_mins + \":\" + dwell_seconds);\n results[regionKey][\"exit_time\"].push(\"\");\n }\n }\n }\n\n if (previousDataIds) {\n for (let j = 0; j < previousDataIds.length; j++) {\n let prevIndex = -1;\n\n if (!results[regionKey][\"id\"]) {\n results[regionKey] = previous_data[regionKey];\n results[regionKey][\"occupied\"] = 0;\n break;\n }\n else {\n if (!results[regionKey][\"dwell_time\"].some(val => val >= stop_duration)) {\n results[regionKey][\"occupied\"] = 0;\n }\n\n prevIndex = results[regionKey][\"id\"].indexOf(previousDataIds[j]);\n if (prevIndex === -1) {\n let keyList = Object.keys(previous_data[regionKey]);\n for (let k = 0; k < keyList.length; k++) {\n if (keyList[k] == \"video_id\" || keyList[k] == \"region_id\" || keyList[k] == \"occupied\") {\n continue;\n }\n results[regionKey][keyList[k]].push(previous_data[regionKey][keyList[k]][j]);\n }\n }\n }\n }\n }\n return results;\n}\n\nfunction handleOccupiedRegion(results, i) {\n const regionKey = \"Region \" + (i + 1);\n if (previous_data[regionKey][\"entry_time\"] == \"\") {\n results[regionKey][\"actual_entry_time\"] = msg.nodered_timestamp;\n results[regionKey][\"entry_time\"] = msg.object_timestamp;\n }\n else {\n if (previous_data[regionKey][\"occupancy_check\"] >= occupancy_check_duration) {\n results[regionKey][\"actual_dwell_time\"] = \"00:00:00\";\n results[regionKey][\"dwell_time\"] = 0;\n results[regionKey][\"actual_entry_time\"] = msg.nodered_timestamp;\n results[regionKey][\"entry_time\"] = msg.object_timestamp;\n }\n else {\n results[regionKey][\"actual_entry_time\"] = previous_data[regionKey][\"actual_entry_time\"];\n results[regionKey][\"entry_time\"] = previous_data[regionKey][\"entry_time\"];\n }\n\n var dwell_timestamp = msg.object_timestamp - results[regionKey][\"entry_time\"];\n var dwell_hours = Math.floor(dwell_timestamp / 3600).toString();\n dwell_hours = dwell_hours.toString().length == 1 ? dwell_hours.toString().padStart(2, \"0\") : dwell_hours.toString();\n var dwell_mins = Math.floor((dwell_timestamp / 60) % 60).toString().padStart(2, \"0\");\n var dwell_seconds = Math.floor(dwell_timestamp % 60).toString().padStart(2, \"0\");\n\n results[regionKey][\"dwell_time\"] = dwell_timestamp;\n results[regionKey][\"actual_dwell_time\"] = dwell_hours + \":\" + dwell_mins + \":\" + dwell_seconds;\n results[regionKey][\"occupancy_check\"] = 0;\n }\n\n return results;\n}\n\nfunction handleNotOccupiedRegion(results, i) {\n const regionKey = \"Region \" + (i + 1);\n\n results[regionKey][\"actual_entry_time\"] = previous_data[regionKey][\"actual_entry_time\"];\n results[regionKey][\"entry_time\"] = previous_data[regionKey][\"entry_time\"];\n results[regionKey][\"actual_dwell_time\"] = previous_data[regionKey][\"actual_dwell_time\"];\n results[regionKey][\"dwell_time\"] = previous_data[regionKey][\"dwell_time\"];\n results[regionKey][\"occupancy_check\"] = previous_data[regionKey][\"occupancy_check\"] + 1;\n\n if (previous_data[regionKey][\"exit_time\"]) {\n results[regionKey][\"exit_time\"] = previous_data[regionKey][\"exit_time\"];\n }\n else {\n results[regionKey][\"exit_time\"] = msg.nodered_timestamp;\n }\n return results;\n}\n\nfunction main(payload, distance_threshold = 100, intersection_type = \"region\", intersection_threshold = 0.5, object_counter = false) {\n var results = preprocess_bboxes(payload);\n var occupied = euclidean_iou_function(results, distance_threshold, intersection_type, intersection_threshold, object_counter);\n var out = postprocess_output(occupied);\n flow.set(previous_data_name, out);\n return out;\n}\n\n// user-defined parameters\nvar distance_threshold = flow.get(\"distance_threshold\");\nvar intersection_type = flow.get(\"intersection_type\");\nvar intersection_threshold = flow.get(\"intersection_threshold\");\nvar object_counter = flow.get(\"object_counter\");\n\nmsg.payload = main(payload, distance_threshold, intersection_type, intersection_threshold, object_counter);\nflow.set(detection_name, msg);\nreturn msg;", + "func": "let region_name = \"predefined_regions_2\";\nlet detection_name = \"object_detection_2\";\nlet previous_data_name = \"previous_data_2\";\nlet occupancy_check_duration = 60;\nlet min_entry_frames = 30;\nlet video_id = 2;\nlet region_id = -1; //Recommend to set this only when there is 1 predefined region in the video\n\nvar payload = msg.payload;\nvar previous_data = flow.get(previous_data_name);\nvar stop_duration = flow.get(\"stop_duration\");\n\n// preparing bboxes\nfunction preprocess_bboxes(payload) {\n function getObjectData(object_data) {\n var current_object_data = {};\n if (object_data.hasOwnProperty(\"type\")) {\n current_object_data[\"type\"] = object_data[\"type\"];\n }\n if (object_data.hasOwnProperty(\"color\")) {\n current_object_data[\"color\"] = object_data[\"color\"];\n }\n if (object_data.hasOwnProperty(\"license_plate\")) {\n current_object_data[\"license_plate\"] = object_data[\"license_plate\"];\n }\n if (object_data.hasOwnProperty(\"id\")) {\n current_object_data[\"id\"] = object_data[\"id\"];\n }\n if (object_data.hasOwnProperty(\"roi_type\")) {\n current_object_data[\"roi_type\"] = object_data[\"roi_type\"];\n }\n return current_object_data;\n }\n function getLotBbox(lot_data) {\n return { \"vertex1\": lot_data.vertex1, \"vertex2\": lot_data.vertex2, \"vertex3\": lot_data.vertex3, \"vertex4\": lot_data.vertex4, \"vertex_0\": lot_data.vertex1 };\n }\n\n var results = {};\n var objects = [];\n var objects_extra = [];\n var lots = [];\n // get all objects bboxes\n for (let key in payload) {\n if (payload.hasOwnProperty(key)) {\n var objectData = getObjectData(payload[key]);\n if (objectData !== null) {\n objects_extra.push(\n objectData\n );\n objects.push(\n payload[key][\"bbox\"]\n );\n }\n }\n }\n // get all lots bboxes\n var predefined_regions = flow.get(region_name);\n var keys = Object.keys(predefined_regions);\n\n for (let i = 0; i < keys.length; i++) {\n if (predefined_regions.hasOwnProperty(keys[i]) && keys[i] != \"_msgid\") {\n lots.push(\n getLotBbox(predefined_regions[keys[i]])\n );\n }\n }\n\n results[\"objects\"] = objects;\n results[\"objects_extra\"] = objects_extra;\n results[\"lots\"] = lots;\n return results;\n}\n\n// euclidean distance and iou check\nfunction euclidean_iou_function(payload, distance_threshold = 100, intersection_type = \"region\", intersection_threshold = 0.5, object_counter = false) {\n function getBboxCentroid(bbox) {\n var centroid_x = (bbox[\"vertex1\"].x + bbox[\"vertex2\"].x + bbox[\"vertex3\"].x + bbox[\"vertex4\"].x) / 4;\n var centroid_y = (bbox[\"vertex1\"].y + bbox[\"vertex2\"].y + bbox[\"vertex3\"].y + bbox[\"vertex4\"].y) / 4;\n return [centroid_x, centroid_y];\n }\n function calculateEuclideanDistance(centroid1, centroid2) {\n return Math.sqrt((centroid2[0] - centroid1[0]) ** 2 + (centroid2[1] - centroid1[1]) ** 2);\n }\n function calculateIntersectArea(bbox1, bbox2) { // objects, lots\n var bbox1_key = Object.keys(bbox1)\n var bbox2_key = Object.keys(bbox2)\n var intersection = [];\n\n for (let i = 0; i < bbox1_key.length - 1; i++) {\n for (let j = 0; j < bbox2_key.length - 1; j++) {\n var A1 = bbox1[bbox1_key[i + 1]].y - bbox1[bbox1_key[i]].y; // Ax + By + C = 0\n var B1 = bbox1[bbox1_key[i]].x - bbox1[bbox1_key[i + 1]].x;\n var C1 = - A1 * bbox1[bbox1_key[i]].x - B1 * bbox1[bbox1_key[i]].y;\n\n var A2 = bbox2[bbox2_key[j + 1]].y - bbox2[bbox2_key[j]].y;\n var B2 = bbox2[bbox2_key[j]].x - bbox2[bbox2_key[j + 1]].x;\n var C2 = - A2 * bbox2[bbox2_key[j]].x - B2 * bbox2[bbox2_key[j]].y;\n\n // parallel lines case\n if (A1 * B2 == A2 * B1) {\n // coincide case\n if ((A1 * C2 == A2 * C1) && (B1 * C2 == B2 * C1)) {\n if (B1 == 0 && A1 == 0) { // one point bbox case\n intersection.push([bbox1[bbox1_key[i]].x.toFixed(6), bbox1[bbox1_key[i]].y.toFixed(6)]);\n }\n else if (B2 == 0 && A2 == 0) { // one point bbox case\n intersection.push([bbox2[bbox2_key[i]].x.toFixed(6), bbox2[bbox2_key[i]].y.toFixed(6)]);\n }\n else if (B1 == 0) { // vertical lines case\n var y_min1 = (bbox1[bbox1_key[i]].y < bbox1[bbox1_key[i + 1]].y) ? bbox1[bbox1_key[i]].y : bbox1[bbox1_key[i + 1]].y;\n var y_max1 = (bbox1[bbox1_key[i]].y < bbox1[bbox1_key[i + 1]].y) ? bbox1[bbox1_key[i + 1]].y : bbox1[bbox1_key[i]].y;\n var y_min2 = (bbox2[bbox2_key[i]].y < bbox2[bbox2_key[i + 1]].y) ? bbox2[bbox2_key[i]].y : bbox2[bbox2_key[i + 1]].y;\n var y_max2 = (bbox2[bbox2_key[i]].y < bbox2[bbox2_key[i + 1]].y) ? bbox2[bbox2_key[i + 1]].y : bbox2[bbox2_key[i]].y;\n if ((y_min1 < y_max2) && (y_min2 < y_max1)) {\n var y_min = (y_min1 < y_min2) ? y_min2 : y_min1;\n var y_max = (y_max1 < y_max2) ? y_max1 : y_max2;\n intersection.push([bbox1[bbox1_key[i]].x.toFixed(6), y_min.toFixed(6)]);\n intersection.push([bbox1[bbox1_key[i]].x.toFixed(6), y_max.toFixed(6)]);\n }\n }\n else if (A1 == 0) { // horizontal lines case\n var x_min1 = (bbox1[bbox1_key[i]].x < bbox1[bbox1_key[i + 1]].x) ? bbox1[bbox1_key[i]].x : bbox1[bbox1_key[i + 1]].x;\n var x_max1 = (bbox1[bbox1_key[i]].x < bbox1[bbox1_key[i + 1]].x) ? bbox1[bbox1_key[i + 1]].x : bbox1[bbox1_key[i]].x;\n var x_min2 = (bbox2[bbox2_key[i]].x < bbox2[bbox2_key[i + 1]].x) ? bbox2[bbox2_key[i]].x : bbox2[bbox2_key[i + 1]].x;\n var x_max2 = (bbox2[bbox2_key[i]].x < bbox2[bbox2_key[i + 1]].x) ? bbox2[bbox2_key[i + 1]].x : bbox2[bbox2_key[i]].x;\n if ((x_min1 < x_max2) && (x_min2 < x_max1)) {\n var x_min = (x_min1 < x_min2) ? x_min2 : x_min1;\n var x_max = (x_max1 < x_max2) ? x_max1 : x_max2;\n intersection.push([x_min.toFixed(6), bbox1[bbox1_key[i]].y.toFixed(6)]);\n intersection.push([x_max.toFixed(6), bbox1[bbox1_key[i]].y.toFixed(6)]);\n }\n }\n else {\n var x_min1 = (bbox1[bbox1_key[i]].x < bbox1[bbox1_key[i + 1]].x) ? bbox1[bbox1_key[i]].x : bbox1[bbox1_key[i + 1]].x;\n var x_max1 = (bbox1[bbox1_key[i]].x < bbox1[bbox1_key[i + 1]].x) ? bbox1[bbox1_key[i + 1]].x : bbox1[bbox1_key[i]].x;\n var x_min2 = (bbox2[bbox2_key[i]].x < bbox2[bbox2_key[i + 1]].x) ? bbox2[bbox2_key[i]].x : bbox2[bbox2_key[i + 1]].x;\n var x_max2 = (bbox2[bbox2_key[i]].x < bbox2[bbox2_key[i + 1]].x) ? bbox2[bbox2_key[i + 1]].x : bbox2[bbox2_key[i]].x;\n if ((x_min1 < x_max2) && (x_min2 < x_max1) && (y_min1 < y_max2) && (y_min2 < y_max1)) {\n var x_min = (x_min1 < x_min2) ? x_min2 : x_min1;\n var x_max = (x_max1 < x_max2) ? x_max1 : x_max2;\n var xY_min = (-A1 * x_min - C1) / B1\n var xY_max = (-A1 * x_max - C1) / B1\n intersection.push([x_min.toFixed(6), xY_min.toFixed(6)]);\n intersection.push([x_max.toFixed(6), xY_max.toFixed(6)]);\n }\n }\n }\n }\n else {\n // get intersection coordinates\n var x1 = bbox1[bbox1_key[i]].x;\n var x2 = bbox1[bbox1_key[i + 1]].x;\n var x3 = bbox2[bbox2_key[j]].x;\n var x4 = bbox2[bbox2_key[j + 1]].x;\n var y1 = bbox1[bbox1_key[i]].y;\n var y2 = bbox1[bbox1_key[i + 1]].y;\n var y3 = bbox2[bbox2_key[j]].y;\n var y4 = bbox2[bbox2_key[j + 1]].y;\n\n var den = (x2 - x1) * (y3 - y4) - (y2 - y1) * (x3 - x4);\n var t = ((x3 - x1) * (y3 - y4) - (y3 - y1) * (x3 - x4)) / den;\n var u = ((x3 - x1) * (y1 - y2) - (y3 - y1) * (x1 - x2)) / den;\n if ((t >= 0) && (t <= 1) && (u >= 0) && (u <= 1)) { // intersect happens if this condition is fulfilled\n var ix = x1 + t * (x2 - x1);\n var iy = y1 + t * (y2 - y1);\n intersection.push([ix.toFixed(6), iy.toFixed(6)]);\n }\n }\n }\n }\n // get clipped coordinates\n var specialcase1 = true;\n var specialcase2 = false;\n var temp1 = bbox1[bbox1_key[0]];\n var temp2 = bbox2[bbox2_key[0]];\n for (let i = 1; i < bbox1_key.length - 1; i++) { // special case\n if (temp1 !== bbox1[bbox1_key[i]]) {\n specialcase1 = false;\n break;\n }\n }\n for (let i = 1; i < bbox2_key.length - 1; i++) {\n if (temp2 !== bbox2[bbox2_key[i]]) {\n specialcase2 = false;\n break;\n }\n }\n if (!(specialcase2)) {\n for (let i = 0; i < bbox1_key.length - 1; i++) {\n var sign_check = [];\n for (let j = 0; j < bbox2_key.length - 1; j++) {\n var x1 = bbox2[bbox2_key[j]].x\n var x2 = bbox2[bbox2_key[j + 1]].x\n var y1 = bbox2[bbox2_key[j]].y\n var y2 = bbox2[bbox2_key[j + 1]].y\n var cross_prod = (bbox1[bbox1_key[i]].x - x1) * (y2 - y1) - (bbox1[bbox1_key[i]].y - y1) * (x2 - x1);\n var sign = (cross_prod >= 0) ? true : false;\n sign_check.push(sign);\n }\n if (sign_check.length === 4 && (sign_check.every((val, index) => val === [true, true, true, true][index]) ||\n sign_check.every((val, index) => val === [false, false, false, false][index]))) { // similar signs indicate the coordinate is within another bbox\n intersection.push([bbox1[bbox1_key[i]].x.toFixed(6), bbox1[bbox1_key[i]].y.toFixed(6)]);\n //intersection.push([bbox1[bbox1_key[i]].x, bbox1[bbox1_key[i]].y]);\n }\n }\n }\n if (!(specialcase1)) {\n for (let i = 0; i < bbox2_key.length - 1; i++) { // similar purpose but for second bbox\n var sign_check = [];\n for (let j = 0; j < bbox1_key.length - 1; j++) {\n var x1 = bbox1[bbox1_key[j]].x\n var x2 = bbox1[bbox1_key[j + 1]].x\n var y1 = bbox1[bbox1_key[j]].y\n var y2 = bbox1[bbox1_key[j + 1]].y\n var cross_prod = (bbox2[bbox2_key[i]].x - x1) * (y2 - y1) - (bbox2[bbox2_key[i]].y - y1) * (x2 - x1);\n var sign = (cross_prod >= 0) ? true : false;\n sign_check.push(sign);\n }\n if (sign_check.length === 4 && (sign_check.every((val, index) => val === [true, true, true, true][index]) ||\n sign_check.every((val, index) => val === [false, false, false, false][index]))) {\n intersection.push([bbox2[bbox2_key[i]].x.toFixed(6), bbox2[bbox2_key[i]].y.toFixed(6)]);\n //intersection.push([bbox2[bbox2_key[i]].x, bbox2[bbox2_key[i]].y]);\n }\n }\n }\n intersection = intersection.filter((item, index, self) =>\n index === self.findIndex((t) => JSON.stringify(t) === JSON.stringify(item))\n );\n // sort intersection points\n if (intersection.length > 2) {\n var intersection_angle = [];\n var intersection_distance = [];\n\n let minIndex = intersection.reduce((minIdx, currentValue, currentIndex, array) => {\n if (currentValue[0] < array[minIdx][0]) {\n return currentIndex;\n } else if (currentValue[0] === array[minIdx][0]) {\n return currentValue[1] < array[minIdx][1] ? currentIndex : minIdx;\n } else {\n return minIdx;\n }\n }, 0);\n var temp_intersection = intersection.filter((_, index) => index !== minIndex);\n for (let i = 0; i < temp_intersection.length; i++) {\n intersection_angle.push(Math.atan2(temp_intersection[i][1] - intersection[minIndex][1], temp_intersection[i][0] - intersection[minIndex][0]));\n intersection_distance.push((temp_intersection[i][1] - intersection[minIndex][1]) ** 2 + (temp_intersection[i][0] - intersection[minIndex][0]) ** 2);\n }\n let combined = temp_intersection.map((point, index) => ({ // sort intersect coordinates based on angle and then distance\n point: point,\n angle: intersection_angle[index],\n distance: intersection_distance[index]\n }));\n combined.sort((a, b) => a.angle - b.angle || a.distance - b.distance);\n let sorted_intersection = combined.map(item => item.point);\n sorted_intersection.unshift(intersection[minIndex]);\n sorted_intersection.push(intersection[minIndex]);\n // shoelace algorithm to calculate the intersect area\n var area = 0;\n for (let i = 0; i < sorted_intersection.length - 1; i++) {\n area += ((sorted_intersection[i][0] * sorted_intersection[i + 1][1])\n - (sorted_intersection[i + 1][0] * sorted_intersection[i][1]))\n }\n return area;\n }\n return 0;\n }\n function calculateIOU(bbox1, bbox2, intersectArea) {\n if (intersectArea !== 0) {\n var lot_area1 = (bbox1[\"vertex1\"].x * bbox1[\"vertex2\"].y) + (bbox1[\"vertex2\"].x * bbox1[\"vertex3\"].y)\n + (bbox1[\"vertex3\"].x * bbox1[\"vertex4\"].y) + (bbox1[\"vertex4\"].x * bbox1[\"vertex_0\"].y)\n - ((bbox1[\"vertex2\"].x * bbox1[\"vertex1\"].y) + (bbox1[\"vertex3\"].x * bbox1[\"vertex2\"].y)\n + (bbox1[\"vertex4\"].x * bbox1[\"vertex3\"].y) + (bbox1[\"vertex_0\"].x * bbox1[\"vertex4\"].y));\n\n var lot_area2 = (bbox2[\"vertex1\"].x * bbox2[\"vertex2\"].y) + (bbox2[\"vertex2\"].x * bbox2[\"vertex3\"].y)\n + (bbox2[\"vertex3\"].x * bbox2[\"vertex4\"].y) + (bbox2[\"vertex4\"].x * bbox2[\"vertex_0\"].y)\n - ((bbox2[\"vertex2\"].x * bbox2[\"vertex1\"].y) + (bbox2[\"vertex3\"].x * bbox2[\"vertex2\"].y)\n + (bbox2[\"vertex4\"].x * bbox2[\"vertex3\"].y) + (bbox2[\"vertex_0\"].x * bbox2[\"vertex4\"].y));\n return intersectArea / (lot_area1 + lot_area2 - intersectArea)\n }\n return 0;\n }\n function calculateIOR(bbox1, intersectArea) {\n if (intersectArea !== 0) {\n var lot_area = (bbox1[\"vertex1\"].x * bbox1[\"vertex2\"].y) + (bbox1[\"vertex2\"].x * bbox1[\"vertex3\"].y)\n + (bbox1[\"vertex3\"].x * bbox1[\"vertex4\"].y) + (bbox1[\"vertex4\"].x * bbox1[\"vertex_0\"].y)\n - ((bbox1[\"vertex2\"].x * bbox1[\"vertex1\"].y) + (bbox1[\"vertex3\"].x * bbox1[\"vertex2\"].y)\n + (bbox1[\"vertex4\"].x * bbox1[\"vertex3\"].y) + (bbox1[\"vertex_0\"].x * bbox1[\"vertex4\"].y));\n return intersectArea / lot_area;\n }\n return 0;\n }\n\n var occupied = [];\n var current_index = -1;\n // initialize lots to no object\n for (let key1 in payload[\"lots\"]) {\n if (payload[\"lots\"].hasOwnProperty(key1)) {\n occupied.push({ \"occupied\": 0 });\n }\n }\n for (let key1 in payload[\"objects\"]) {\n current_index++;\n if (payload[\"objects\"].hasOwnProperty(key1)) { // find all lots within the object centroid distance threshold\n var objectCentroid = getBboxCentroid(payload[\"objects\"][key1]);\n var potential_region = [];\n for (let key2 in payload[\"lots\"]) {\n if (payload[\"lots\"].hasOwnProperty(key2)) {\n var regionCentroid = getBboxCentroid(payload[\"lots\"][key2]);\n var euclideanDistance = calculateEuclideanDistance(objectCentroid, regionCentroid);\n potential_region.push(euclideanDistance);\n }\n }\n // calculate IOU or IOR\n if (object_counter) {\n for (let i = 0; i < potential_region.length; i++) {\n if (potential_region[i] < distance_threshold) {\n var intersectRegion = calculateIntersectArea(payload[\"objects\"][key1], payload[\"lots\"][i]);\n var IOR = 0;\n if (intersection_type == \"region\") {\n IOR = calculateIOR(payload[\"lots\"][i], intersectRegion)\n }\n else if (intersection_type == \"object\") {\n IOR = calculateIOR(payload[\"objects\"][key1], intersectRegion)\n }\n if (IOR > intersection_threshold) {\n occupied[i][\"occupied\"] += 1;\n var keyData = Object.keys(payload[\"objects_extra\"][current_index]);\n var numberData = keyData.length;\n for (let j = 0; j < numberData; j++) {\n if (!occupied[i][\"extra\"]) {\n occupied[i][\"extra\"] = {};\n }\n if (!occupied[i][\"extra\"][keyData[j]]) {\n occupied[i][\"extra\"][keyData[j]] = [];\n }\n occupied[i][\"extra\"][keyData[j]].push(payload[\"objects_extra\"][current_index][keyData[j]]);\n }\n }\n }\n }\n }\n else { // consider the best lot only\n var best_region_index = potential_region.reduce((minIndex, currentValue, currentIndex, array) =>\n currentValue < array[minIndex] ? currentIndex : minIndex, 0);\n if (potential_region[best_region_index] < distance_threshold) {\n var intersectRegion = calculateIntersectArea(payload[\"objects\"][key1], payload[\"lots\"][best_region_index]);\n var IOR = 0;\n if (intersection_type == \"region\") {\n IOR = calculateIOR(payload[\"lots\"][best_region_index], intersectRegion)\n }\n else if (intersection_type == \"object\") {\n IOR = calculateIOR(payload[\"objects\"][key1], intersectRegion)\n }\n if (IOR > intersection_threshold) {\n occupied[best_region_index][\"occupied\"] += 1;\n var keyData = Object.keys(payload[\"objects_extra\"][current_index]);\n var numberData = keyData.length;\n for (let i = 0; i < numberData; i++) {\n if (!occupied[best_region_index][\"extra\"]) {\n occupied[best_region_index][\"extra\"] = {};\n }\n if (!occupied[best_region_index][\"extra\"][keyData[i]]) {\n occupied[best_region_index][\"extra\"][keyData[i]] = [];\n }\n occupied[best_region_index][\"extra\"][keyData[i]].push(payload[\"objects_extra\"][current_index][keyData[i]]);\n }\n }\n }\n }\n }\n }\n return occupied;\n}\n\n// turn into correct format\nfunction postprocess_output(occupied) {\n var results = {};\n\n for (let i = 0; i < occupied.length; i++) {\n results = initializeResults(results, occupied, i);\n previous_data = initializePreviousData(results, i);\n if (results[\"Region \" + (i + 1)].hasOwnProperty(\"id\") || previous_data[\"Region \" + (i + 1)].hasOwnProperty(\"id\")) {\n results = handleObjectWithID(results, i);\n }\n else if (results[\"Region \" + (i + 1)][\"occupied\"] > 0) {\n results = handleOccupiedRegion(results, i);\n }\n else if (previous_data[\"Region \" + (i + 1)][\"entry_time\"]) {\n results = handleNotOccupiedRegion(results, i);\n }\n }\n return results;\n}\n\nfunction initializeResults(results, occupied, i) {\n const regionKey = \"Region \" + (i + 1);\n\n if (!results[regionKey]) {\n results[regionKey] = {};\n }\n results[regionKey][\"video_id\"] = video_id;\n results[regionKey][\"region_id\"] = (region_id != -1) ? region_id : i + 1;\n\n if (occupied[i].hasOwnProperty(\"occupied\")) {\n results[regionKey][\"occupied\"] = occupied[i].occupied;\n }\n\n if (occupied[i].hasOwnProperty(\"extra\")) {\n for (let key in occupied[i].extra) {\n results[regionKey][key] = occupied[i].extra[key];\n }\n }\n if (results[regionKey].hasOwnProperty(\"id\") || (previous_data && previous_data.hasOwnProperty(regionKey) && previous_data[regionKey].hasOwnProperty(\"id\"))) {\n results[regionKey][\"actual_entry_time\"] = [];\n results[regionKey][\"actual_dwell_time\"] = [];\n results[regionKey][\"entry_time\"] = [];\n results[regionKey][\"dwell_time\"] = [];\n results[regionKey][\"exit_time\"] = [];\n }\n else {\n results[\"Region \" + (i + 1)][\"actual_entry_time\"] = \"\";\n results[\"Region \" + (i + 1)][\"actual_dwell_time\"] = \"\";\n results[\"Region \" + (i + 1)][\"entry_time\"] = \"\";\n results[\"Region \" + (i + 1)][\"dwell_time\"] = \"\";\n results[\"Region \" + (i + 1)][\"exit_time\"] = \"\";\n results[\"Region \" + (i + 1)][\"occupancy_check\"] = 0;\n }\n return results;\n}\n\nfunction initializePreviousData(results, i) {\n const regionKey = \"Region \" + (i + 1);\n\n // Ensure previous_data is set up for this region.\n if (!previous_data) {\n previous_data = {};\n }\n if (results[regionKey].hasOwnProperty(\"id\")) {\n if (!previous_data[regionKey]) {\n previous_data[regionKey] = {};\n previous_data[regionKey][\"id\"] = [];\n previous_data[regionKey][\"actual_entry_time\"] = [];\n previous_data[regionKey][\"actual_dwell_time\"] = [];\n previous_data[regionKey][\"entry_time\"] = [];\n previous_data[regionKey][\"dwell_time\"] = [];\n previous_data[regionKey][\"exit_time\"] = [];\n }\n }\n else {\n if (!previous_data.hasOwnProperty(regionKey)) {\n previous_data[regionKey] = results[regionKey];\n previous_data[regionKey][\"actual_entry_time\"] = \"\";\n previous_data[regionKey][\"actual_dwell_time\"] = \"\";\n previous_data[regionKey][\"entry_time\"] = \"\";\n previous_data[regionKey][\"dwell_time\"] = \"\";\n previous_data[regionKey][\"exit_time\"] = \"\";\n previous_data[regionKey][\"occupancy_check\"] = 0;\n }\n }\n\n return previous_data;\n}\n\nfunction handleObjectWithID(results, i) {\n const regionKey = \"Region \" + (i + 1);\n const resultIds = results[regionKey][\"id\"];\n const previousDataIds = previous_data[regionKey][\"id\"];\n\n if (!resultIds && !previousDataIds) {\n return results;\n }\n\n if (resultIds) {\n for (let j = 0; j < resultIds.length; j++) {\n // Check existing id data\n let prevIndex = -1;\n\n if (previous_data[regionKey][\"id\"] && previous_data[regionKey][\"id\"].length > 0) {\n prevIndex = previous_data[regionKey][\"id\"].indexOf(resultIds[j]);\n }\n\n if (prevIndex === -1) {\n results[regionKey][\"actual_entry_time\"].push(msg.nodered_timestamp);\n results[regionKey][\"entry_time\"].push(msg.object_timestamp);\n results[regionKey][\"actual_dwell_time\"].push(\"00:00:00\");\n results[regionKey][\"dwell_time\"].push(0);\n results[regionKey][\"exit_time\"].push(\"\");\n } else {\n results[regionKey][\"actual_entry_time\"].push(previous_data[regionKey][\"actual_entry_time\"][prevIndex]);\n results[regionKey][\"entry_time\"].push(previous_data[regionKey][\"entry_time\"][prevIndex]);\n\n // Calculate dwell time.\n const dwell_timestamp = (msg.object_timestamp - results[regionKey][\"entry_time\"][j]);\n const dwell_hours = Math.floor(dwell_timestamp / 3600).toString().padStart(2, \"0\");\n const dwell_mins = Math.floor((dwell_timestamp / 60) % 60).toString().padStart(2, \"0\");\n const dwell_seconds = Math.floor(dwell_timestamp % 60).toString().padStart(2, \"0\");\n results[regionKey][\"dwell_time\"].push(dwell_timestamp);\n results[regionKey][\"actual_dwell_time\"].push(dwell_hours + \":\" + dwell_mins + \":\" + dwell_seconds);\n results[regionKey][\"exit_time\"].push(\"\");\n }\n }\n }\n\n if (previousDataIds) {\n for (let j = 0; j < previousDataIds.length; j++) {\n let prevIndex = -1;\n\n if (!results[regionKey][\"id\"]) {\n results[regionKey] = previous_data[regionKey];\n results[regionKey][\"occupied\"] = 0;\n break;\n }\n else {\n if (!results[regionKey][\"dwell_time\"].some(val => val >= stop_duration)) {\n results[regionKey][\"occupied\"] = 0;\n }\n\n prevIndex = results[regionKey][\"id\"].indexOf(previousDataIds[j]);\n if (prevIndex === -1) {\n let keyList = Object.keys(previous_data[regionKey]);\n for (let k = 0; k < keyList.length; k++) {\n if (keyList[k] == \"video_id\" || keyList[k] == \"region_id\" || keyList[k] == \"occupied\") {\n continue;\n }\n results[regionKey][keyList[k]].push(previous_data[regionKey][keyList[k]][j]);\n }\n }\n }\n }\n }\n return results;\n}\n\nfunction handleOccupiedRegion(results, i) {\n const regionKey = \"Region \" + (i + 1);\n var prev_consecutive = (previous_data[regionKey][\"consecutive_occupied\"]) || 0;\n results[regionKey][\"consecutive_occupied\"] = prev_consecutive + 1;\n if (results[regionKey][\"consecutive_occupied\"] < min_entry_frames) {\n results[regionKey][\"occupied\"] = 0;\n results[regionKey][\"occupancy_check\"] = 0;\n return results;\n }\n if (previous_data[regionKey][\"entry_time\"] == \"\") {\n results[regionKey][\"actual_entry_time\"] = msg.nodered_timestamp;\n results[regionKey][\"entry_time\"] = msg.object_timestamp;\n }\n else {\n if (previous_data[regionKey][\"occupancy_check\"] >= occupancy_check_duration) {\n results[regionKey][\"actual_dwell_time\"] = \"00:00:00\";\n results[regionKey][\"dwell_time\"] = 0;\n results[regionKey][\"actual_entry_time\"] = msg.nodered_timestamp;\n results[regionKey][\"entry_time\"] = msg.object_timestamp;\n }\n else {\n results[regionKey][\"actual_entry_time\"] = previous_data[regionKey][\"actual_entry_time\"];\n results[regionKey][\"entry_time\"] = previous_data[regionKey][\"entry_time\"];\n }\n\n var dwell_timestamp = msg.object_timestamp - results[regionKey][\"entry_time\"];\n var dwell_hours = Math.floor(dwell_timestamp / 3600).toString();\n dwell_hours = dwell_hours.toString().length == 1 ? dwell_hours.toString().padStart(2, \"0\") : dwell_hours.toString();\n var dwell_mins = Math.floor((dwell_timestamp / 60) % 60).toString().padStart(2, \"0\");\n var dwell_seconds = Math.floor(dwell_timestamp % 60).toString().padStart(2, \"0\");\n\n results[regionKey][\"dwell_time\"] = dwell_timestamp;\n results[regionKey][\"actual_dwell_time\"] = dwell_hours + \":\" + dwell_mins + \":\" + dwell_seconds;\n results[regionKey][\"occupancy_check\"] = 0;\n }\n\n return results;\n}\n\nfunction handleNotOccupiedRegion(results, i) {\n const regionKey = \"Region \" + (i + 1);\n\n results[regionKey][\"actual_entry_time\"] = previous_data[regionKey][\"actual_entry_time\"];\n results[regionKey][\"entry_time\"] = previous_data[regionKey][\"entry_time\"];\n results[regionKey][\"actual_dwell_time\"] = previous_data[regionKey][\"actual_dwell_time\"];\n results[regionKey][\"dwell_time\"] = previous_data[regionKey][\"dwell_time\"];\n results[regionKey][\"occupancy_check\"] = previous_data[regionKey][\"occupancy_check\"] + 1;\n\n if (previous_data[regionKey][\"exit_time\"]) {\n results[regionKey][\"exit_time\"] = previous_data[regionKey][\"exit_time\"];\n }\n else {\n results[regionKey][\"exit_time\"] = msg.nodered_timestamp;\n }\n return results;\n}\n\nfunction main(payload, distance_threshold = 100, intersection_type = \"region\", intersection_threshold = 0.5, object_counter = false) {\n var results = preprocess_bboxes(payload);\n var occupied = euclidean_iou_function(results, distance_threshold, intersection_type, intersection_threshold, object_counter);\n var out = postprocess_output(occupied);\n flow.set(previous_data_name, out);\n return out;\n}\n\n// user-defined parameters\nvar distance_threshold = flow.get(\"distance_threshold\");\nvar intersection_type = flow.get(\"intersection_type\");\nvar intersection_threshold = flow.get(\"intersection_threshold\");\nvar object_counter = flow.get(\"object_counter\");\n\nmsg.payload = main(payload, distance_threshold, intersection_type, intersection_threshold, object_counter);\nflow.set(detection_name, msg);\nreturn msg;", "outputs": 1, "timeout": 0, "noerr": 0, @@ -1390,7 +1390,7 @@ "type": "euclidean and ior", "z": "2720733497fe05f3", "name": "euclidean and ior 3", - "func": "let region_name = \"predefined_regions_3\";\nlet detection_name = \"object_detection_3\";\nlet previous_data_name = \"previous_data_3\";\nlet occupancy_check_duration = 60;\nlet video_id = 3;\nlet region_id = -1; //Recommend to set this only when there is 1 predefined region in the video\n\nvar payload = msg.payload;\nvar previous_data = flow.get(previous_data_name);\nvar stop_duration = flow.get(\"stop_duration\");\n\n// preparing bboxes\nfunction preprocess_bboxes(payload) {\n function getObjectData(object_data) {\n var current_object_data = {};\n if (object_data.hasOwnProperty(\"type\")) {\n current_object_data[\"type\"] = object_data[\"type\"];\n }\n if (object_data.hasOwnProperty(\"color\")) {\n current_object_data[\"color\"] = object_data[\"color\"];\n }\n if (object_data.hasOwnProperty(\"license_plate\")) {\n current_object_data[\"license_plate\"] = object_data[\"license_plate\"];\n }\n if (object_data.hasOwnProperty(\"id\")) {\n current_object_data[\"id\"] = object_data[\"id\"];\n }\n if (object_data.hasOwnProperty(\"roi_type\")) {\n current_object_data[\"roi_type\"] = object_data[\"roi_type\"];\n }\n return current_object_data;\n }\n function getLotBbox(lot_data) {\n return { \"vertex1\": lot_data.vertex1, \"vertex2\": lot_data.vertex2, \"vertex3\": lot_data.vertex3, \"vertex4\": lot_data.vertex4, \"vertex_0\": lot_data.vertex1 };\n }\n\n var results = {};\n var objects = [];\n var objects_extra = [];\n var lots = [];\n // get all objects bboxes\n for (let key in payload) {\n if (payload.hasOwnProperty(key)) {\n var objectData = getObjectData(payload[key]);\n if (objectData !== null) {\n objects_extra.push(\n objectData\n );\n objects.push(\n payload[key][\"bbox\"]\n );\n }\n }\n }\n // get all lots bboxes\n var predefined_regions = flow.get(region_name);\n var keys = Object.keys(predefined_regions);\n\n for (let i = 0; i < keys.length; i++) {\n if (predefined_regions.hasOwnProperty(keys[i]) && keys[i] != \"_msgid\") {\n lots.push(\n getLotBbox(predefined_regions[keys[i]])\n );\n }\n }\n\n results[\"objects\"] = objects;\n results[\"objects_extra\"] = objects_extra;\n results[\"lots\"] = lots;\n return results;\n}\n\n// euclidean distance and iou check\nfunction euclidean_iou_function(payload, distance_threshold = 100, intersection_type = \"region\", intersection_threshold = 0.5, object_counter = false) {\n function getBboxCentroid(bbox) {\n var centroid_x = (bbox[\"vertex1\"].x + bbox[\"vertex2\"].x + bbox[\"vertex3\"].x + bbox[\"vertex4\"].x) / 4;\n var centroid_y = (bbox[\"vertex1\"].y + bbox[\"vertex2\"].y + bbox[\"vertex3\"].y + bbox[\"vertex4\"].y) / 4;\n return [centroid_x, centroid_y];\n }\n function calculateEuclideanDistance(centroid1, centroid2) {\n return Math.sqrt((centroid2[0] - centroid1[0]) ** 2 + (centroid2[1] - centroid1[1]) ** 2);\n }\n function calculateIntersectArea(bbox1, bbox2) { // objects, lots\n var bbox1_key = Object.keys(bbox1)\n var bbox2_key = Object.keys(bbox2)\n var intersection = [];\n\n for (let i = 0; i < bbox1_key.length - 1; i++) {\n for (let j = 0; j < bbox2_key.length - 1; j++) {\n var A1 = bbox1[bbox1_key[i + 1]].y - bbox1[bbox1_key[i]].y; // Ax + By + C = 0\n var B1 = bbox1[bbox1_key[i]].x - bbox1[bbox1_key[i + 1]].x;\n var C1 = - A1 * bbox1[bbox1_key[i]].x - B1 * bbox1[bbox1_key[i]].y;\n\n var A2 = bbox2[bbox2_key[j + 1]].y - bbox2[bbox2_key[j]].y;\n var B2 = bbox2[bbox2_key[j]].x - bbox2[bbox2_key[j + 1]].x;\n var C2 = - A2 * bbox2[bbox2_key[j]].x - B2 * bbox2[bbox2_key[j]].y;\n\n // parallel lines case\n if (A1 * B2 == A2 * B1) {\n // coincide case\n if ((A1 * C2 == A2 * C1) && (B1 * C2 == B2 * C1)) {\n if (B1 == 0 && A1 == 0) { // one point bbox case\n intersection.push([bbox1[bbox1_key[i]].x.toFixed(6), bbox1[bbox1_key[i]].y.toFixed(6)]);\n }\n else if (B2 == 0 && A2 == 0) { // one point bbox case\n intersection.push([bbox2[bbox2_key[i]].x.toFixed(6), bbox2[bbox2_key[i]].y.toFixed(6)]);\n }\n else if (B1 == 0) { // vertical lines case\n var y_min1 = (bbox1[bbox1_key[i]].y < bbox1[bbox1_key[i + 1]].y) ? bbox1[bbox1_key[i]].y : bbox1[bbox1_key[i + 1]].y;\n var y_max1 = (bbox1[bbox1_key[i]].y < bbox1[bbox1_key[i + 1]].y) ? bbox1[bbox1_key[i + 1]].y : bbox1[bbox1_key[i]].y;\n var y_min2 = (bbox2[bbox2_key[i]].y < bbox2[bbox2_key[i + 1]].y) ? bbox2[bbox2_key[i]].y : bbox2[bbox2_key[i + 1]].y;\n var y_max2 = (bbox2[bbox2_key[i]].y < bbox2[bbox2_key[i + 1]].y) ? bbox2[bbox2_key[i + 1]].y : bbox2[bbox2_key[i]].y;\n if ((y_min1 < y_max2) && (y_min2 < y_max1)) {\n var y_min = (y_min1 < y_min2) ? y_min2 : y_min1;\n var y_max = (y_max1 < y_max2) ? y_max1 : y_max2;\n intersection.push([bbox1[bbox1_key[i]].x.toFixed(6), y_min.toFixed(6)]);\n intersection.push([bbox1[bbox1_key[i]].x.toFixed(6), y_max.toFixed(6)]);\n }\n }\n else if (A1 == 0) { // horizontal lines case\n var x_min1 = (bbox1[bbox1_key[i]].x < bbox1[bbox1_key[i + 1]].x) ? bbox1[bbox1_key[i]].x : bbox1[bbox1_key[i + 1]].x;\n var x_max1 = (bbox1[bbox1_key[i]].x < bbox1[bbox1_key[i + 1]].x) ? bbox1[bbox1_key[i + 1]].x : bbox1[bbox1_key[i]].x;\n var x_min2 = (bbox2[bbox2_key[i]].x < bbox2[bbox2_key[i + 1]].x) ? bbox2[bbox2_key[i]].x : bbox2[bbox2_key[i + 1]].x;\n var x_max2 = (bbox2[bbox2_key[i]].x < bbox2[bbox2_key[i + 1]].x) ? bbox2[bbox2_key[i + 1]].x : bbox2[bbox2_key[i]].x;\n if ((x_min1 < x_max2) && (x_min2 < x_max1)) {\n var x_min = (x_min1 < x_min2) ? x_min2 : x_min1;\n var x_max = (x_max1 < x_max2) ? x_max1 : x_max2;\n intersection.push([x_min.toFixed(6), bbox1[bbox1_key[i]].y.toFixed(6)]);\n intersection.push([x_max.toFixed(6), bbox1[bbox1_key[i]].y.toFixed(6)]);\n }\n }\n else {\n var x_min1 = (bbox1[bbox1_key[i]].x < bbox1[bbox1_key[i + 1]].x) ? bbox1[bbox1_key[i]].x : bbox1[bbox1_key[i + 1]].x;\n var x_max1 = (bbox1[bbox1_key[i]].x < bbox1[bbox1_key[i + 1]].x) ? bbox1[bbox1_key[i + 1]].x : bbox1[bbox1_key[i]].x;\n var x_min2 = (bbox2[bbox2_key[i]].x < bbox2[bbox2_key[i + 1]].x) ? bbox2[bbox2_key[i]].x : bbox2[bbox2_key[i + 1]].x;\n var x_max2 = (bbox2[bbox2_key[i]].x < bbox2[bbox2_key[i + 1]].x) ? bbox2[bbox2_key[i + 1]].x : bbox2[bbox2_key[i]].x;\n if ((x_min1 < x_max2) && (x_min2 < x_max1) && (y_min1 < y_max2) && (y_min2 < y_max1)) {\n var x_min = (x_min1 < x_min2) ? x_min2 : x_min1;\n var x_max = (x_max1 < x_max2) ? x_max1 : x_max2;\n var xY_min = (-A1 * x_min - C1) / B1\n var xY_max = (-A1 * x_max - C1) / B1\n intersection.push([x_min.toFixed(6), xY_min.toFixed(6)]);\n intersection.push([x_max.toFixed(6), xY_max.toFixed(6)]);\n }\n }\n }\n }\n else {\n // get intersection coordinates\n var x1 = bbox1[bbox1_key[i]].x;\n var x2 = bbox1[bbox1_key[i + 1]].x;\n var x3 = bbox2[bbox2_key[j]].x;\n var x4 = bbox2[bbox2_key[j + 1]].x;\n var y1 = bbox1[bbox1_key[i]].y;\n var y2 = bbox1[bbox1_key[i + 1]].y;\n var y3 = bbox2[bbox2_key[j]].y;\n var y4 = bbox2[bbox2_key[j + 1]].y;\n\n var den = (x2 - x1) * (y3 - y4) - (y2 - y1) * (x3 - x4);\n var t = ((x3 - x1) * (y3 - y4) - (y3 - y1) * (x3 - x4)) / den;\n var u = ((x3 - x1) * (y1 - y2) - (y3 - y1) * (x1 - x2)) / den;\n if ((t >= 0) && (t <= 1) && (u >= 0) && (u <= 1)) { // intersect happens if this condition is fulfilled\n var ix = x1 + t * (x2 - x1);\n var iy = y1 + t * (y2 - y1);\n intersection.push([ix.toFixed(6), iy.toFixed(6)]);\n }\n }\n }\n }\n // get clipped coordinates\n var specialcase1 = true;\n var specialcase2 = false;\n var temp1 = bbox1[bbox1_key[0]];\n var temp2 = bbox2[bbox2_key[0]];\n for (let i = 1; i < bbox1_key.length - 1; i++) { // special case\n if (temp1 !== bbox1[bbox1_key[i]]) {\n specialcase1 = false;\n break;\n }\n }\n for (let i = 1; i < bbox2_key.length - 1; i++) {\n if (temp2 !== bbox2[bbox2_key[i]]) {\n specialcase2 = false;\n break;\n }\n }\n if (!(specialcase2)) {\n for (let i = 0; i < bbox1_key.length - 1; i++) {\n var sign_check = [];\n for (let j = 0; j < bbox2_key.length - 1; j++) {\n var x1 = bbox2[bbox2_key[j]].x\n var x2 = bbox2[bbox2_key[j + 1]].x\n var y1 = bbox2[bbox2_key[j]].y\n var y2 = bbox2[bbox2_key[j + 1]].y\n var cross_prod = (bbox1[bbox1_key[i]].x - x1) * (y2 - y1) - (bbox1[bbox1_key[i]].y - y1) * (x2 - x1);\n var sign = (cross_prod >= 0) ? true : false;\n sign_check.push(sign);\n }\n if (sign_check.length === 4 && (sign_check.every((val, index) => val === [true, true, true, true][index]) ||\n sign_check.every((val, index) => val === [false, false, false, false][index]))) { // similar signs indicate the coordinate is within another bbox\n intersection.push([bbox1[bbox1_key[i]].x.toFixed(6), bbox1[bbox1_key[i]].y.toFixed(6)]);\n //intersection.push([bbox1[bbox1_key[i]].x, bbox1[bbox1_key[i]].y]);\n }\n }\n }\n if (!(specialcase1)) {\n for (let i = 0; i < bbox2_key.length - 1; i++) { // similar purpose but for second bbox\n var sign_check = [];\n for (let j = 0; j < bbox1_key.length - 1; j++) {\n var x1 = bbox1[bbox1_key[j]].x\n var x2 = bbox1[bbox1_key[j + 1]].x\n var y1 = bbox1[bbox1_key[j]].y\n var y2 = bbox1[bbox1_key[j + 1]].y\n var cross_prod = (bbox2[bbox2_key[i]].x - x1) * (y2 - y1) - (bbox2[bbox2_key[i]].y - y1) * (x2 - x1);\n var sign = (cross_prod >= 0) ? true : false;\n sign_check.push(sign);\n }\n if (sign_check.length === 4 && (sign_check.every((val, index) => val === [true, true, true, true][index]) ||\n sign_check.every((val, index) => val === [false, false, false, false][index]))) {\n intersection.push([bbox2[bbox2_key[i]].x.toFixed(6), bbox2[bbox2_key[i]].y.toFixed(6)]);\n //intersection.push([bbox2[bbox2_key[i]].x, bbox2[bbox2_key[i]].y]);\n }\n }\n }\n intersection = intersection.filter((item, index, self) =>\n index === self.findIndex((t) => JSON.stringify(t) === JSON.stringify(item))\n );\n // sort intersection points\n if (intersection.length > 2) {\n var intersection_angle = [];\n var intersection_distance = [];\n\n let minIndex = intersection.reduce((minIdx, currentValue, currentIndex, array) => {\n if (currentValue[0] < array[minIdx][0]) {\n return currentIndex;\n } else if (currentValue[0] === array[minIdx][0]) {\n return currentValue[1] < array[minIdx][1] ? currentIndex : minIdx;\n } else {\n return minIdx;\n }\n }, 0);\n var temp_intersection = intersection.filter((_, index) => index !== minIndex);\n for (let i = 0; i < temp_intersection.length; i++) {\n intersection_angle.push(Math.atan2(temp_intersection[i][1] - intersection[minIndex][1], temp_intersection[i][0] - intersection[minIndex][0]));\n intersection_distance.push((temp_intersection[i][1] - intersection[minIndex][1]) ** 2 + (temp_intersection[i][0] - intersection[minIndex][0]) ** 2);\n }\n let combined = temp_intersection.map((point, index) => ({ // sort intersect coordinates based on angle and then distance\n point: point,\n angle: intersection_angle[index],\n distance: intersection_distance[index]\n }));\n combined.sort((a, b) => a.angle - b.angle || a.distance - b.distance);\n let sorted_intersection = combined.map(item => item.point);\n sorted_intersection.unshift(intersection[minIndex]);\n sorted_intersection.push(intersection[minIndex]);\n // shoelace algorithm to calculate the intersect area\n var area = 0;\n for (let i = 0; i < sorted_intersection.length - 1; i++) {\n area += ((sorted_intersection[i][0] * sorted_intersection[i + 1][1])\n - (sorted_intersection[i + 1][0] * sorted_intersection[i][1]))\n }\n return area;\n }\n return 0;\n }\n function calculateIOU(bbox1, bbox2, intersectArea) {\n if (intersectArea !== 0) {\n var lot_area1 = (bbox1[\"vertex1\"].x * bbox1[\"vertex2\"].y) + (bbox1[\"vertex2\"].x * bbox1[\"vertex3\"].y)\n + (bbox1[\"vertex3\"].x * bbox1[\"vertex4\"].y) + (bbox1[\"vertex4\"].x * bbox1[\"vertex_0\"].y)\n - ((bbox1[\"vertex2\"].x * bbox1[\"vertex1\"].y) + (bbox1[\"vertex3\"].x * bbox1[\"vertex2\"].y)\n + (bbox1[\"vertex4\"].x * bbox1[\"vertex3\"].y) + (bbox1[\"vertex_0\"].x * bbox1[\"vertex4\"].y));\n\n var lot_area2 = (bbox2[\"vertex1\"].x * bbox2[\"vertex2\"].y) + (bbox2[\"vertex2\"].x * bbox2[\"vertex3\"].y)\n + (bbox2[\"vertex3\"].x * bbox2[\"vertex4\"].y) + (bbox2[\"vertex4\"].x * bbox2[\"vertex_0\"].y)\n - ((bbox2[\"vertex2\"].x * bbox2[\"vertex1\"].y) + (bbox2[\"vertex3\"].x * bbox2[\"vertex2\"].y)\n + (bbox2[\"vertex4\"].x * bbox2[\"vertex3\"].y) + (bbox2[\"vertex_0\"].x * bbox2[\"vertex4\"].y));\n return intersectArea / (lot_area1 + lot_area2 - intersectArea)\n }\n return 0;\n }\n function calculateIOR(bbox1, intersectArea) {\n if (intersectArea !== 0) {\n var lot_area = (bbox1[\"vertex1\"].x * bbox1[\"vertex2\"].y) + (bbox1[\"vertex2\"].x * bbox1[\"vertex3\"].y)\n + (bbox1[\"vertex3\"].x * bbox1[\"vertex4\"].y) + (bbox1[\"vertex4\"].x * bbox1[\"vertex_0\"].y)\n - ((bbox1[\"vertex2\"].x * bbox1[\"vertex1\"].y) + (bbox1[\"vertex3\"].x * bbox1[\"vertex2\"].y)\n + (bbox1[\"vertex4\"].x * bbox1[\"vertex3\"].y) + (bbox1[\"vertex_0\"].x * bbox1[\"vertex4\"].y));\n return intersectArea / lot_area;\n }\n return 0;\n }\n\n var occupied = [];\n var current_index = -1;\n // initialize lots to no object\n for (let key1 in payload[\"lots\"]) {\n if (payload[\"lots\"].hasOwnProperty(key1)) {\n occupied.push({ \"occupied\": 0 });\n }\n }\n for (let key1 in payload[\"objects\"]) {\n current_index++;\n if (payload[\"objects\"].hasOwnProperty(key1)) { // find all lots within the object centroid distance threshold\n var objectCentroid = getBboxCentroid(payload[\"objects\"][key1]);\n var potential_region = [];\n for (let key2 in payload[\"lots\"]) {\n if (payload[\"lots\"].hasOwnProperty(key2)) {\n var regionCentroid = getBboxCentroid(payload[\"lots\"][key2]);\n var euclideanDistance = calculateEuclideanDistance(objectCentroid, regionCentroid);\n potential_region.push(euclideanDistance);\n }\n }\n // calculate IOU or IOR\n if (object_counter) {\n for (let i = 0; i < potential_region.length; i++) {\n if (potential_region[i] < distance_threshold) {\n var intersectRegion = calculateIntersectArea(payload[\"objects\"][key1], payload[\"lots\"][i]);\n var IOR = 0;\n if (intersection_type == \"region\") {\n IOR = calculateIOR(payload[\"lots\"][i], intersectRegion)\n }\n else if (intersection_type == \"object\") {\n IOR = calculateIOR(payload[\"objects\"][key1], intersectRegion)\n }\n if (IOR > intersection_threshold) {\n occupied[i][\"occupied\"] += 1;\n var keyData = Object.keys(payload[\"objects_extra\"][current_index]);\n var numberData = keyData.length;\n for (let j = 0; j < numberData; j++) {\n if (!occupied[i][\"extra\"]) {\n occupied[i][\"extra\"] = {};\n }\n if (!occupied[i][\"extra\"][keyData[j]]) {\n occupied[i][\"extra\"][keyData[j]] = [];\n }\n occupied[i][\"extra\"][keyData[j]].push(payload[\"objects_extra\"][current_index][keyData[j]]);\n }\n }\n }\n }\n }\n else { // consider the best lot only\n var best_region_index = potential_region.reduce((minIndex, currentValue, currentIndex, array) =>\n currentValue < array[minIndex] ? currentIndex : minIndex, 0);\n if (potential_region[best_region_index] < distance_threshold) {\n var intersectRegion = calculateIntersectArea(payload[\"objects\"][key1], payload[\"lots\"][best_region_index]);\n var IOR = 0;\n if (intersection_type == \"region\") {\n IOR = calculateIOR(payload[\"lots\"][best_region_index], intersectRegion)\n }\n else if (intersection_type == \"object\") {\n IOR = calculateIOR(payload[\"objects\"][key1], intersectRegion)\n }\n if (IOR > intersection_threshold) {\n occupied[best_region_index][\"occupied\"] += 1;\n var keyData = Object.keys(payload[\"objects_extra\"][current_index]);\n var numberData = keyData.length;\n for (let i = 0; i < numberData; i++) {\n if (!occupied[best_region_index][\"extra\"]) {\n occupied[best_region_index][\"extra\"] = {};\n }\n if (!occupied[best_region_index][\"extra\"][keyData[i]]) {\n occupied[best_region_index][\"extra\"][keyData[i]] = [];\n }\n occupied[best_region_index][\"extra\"][keyData[i]].push(payload[\"objects_extra\"][current_index][keyData[i]]);\n }\n }\n }\n }\n }\n }\n return occupied;\n}\n\n// turn into correct format\nfunction postprocess_output(occupied) {\n var results = {};\n\n for (let i = 0; i < occupied.length; i++) {\n results = initializeResults(results, occupied, i);\n previous_data = initializePreviousData(results, i);\n if (results[\"Region \" + (i + 1)].hasOwnProperty(\"id\") || previous_data[\"Region \" + (i + 1)].hasOwnProperty(\"id\")) {\n results = handleObjectWithID(results, i);\n }\n else if (results[\"Region \" + (i + 1)][\"occupied\"] > 0) {\n results = handleOccupiedRegion(results, i);\n }\n else if (previous_data[\"Region \" + (i + 1)][\"entry_time\"]) {\n results = handleNotOccupiedRegion(results, i);\n }\n }\n return results;\n}\n\nfunction initializeResults(results, occupied, i) {\n const regionKey = \"Region \" + (i + 1);\n\n if (!results[regionKey]) {\n results[regionKey] = {};\n }\n results[regionKey][\"video_id\"] = video_id;\n results[regionKey][\"region_id\"] = (region_id != -1) ? region_id : i + 1;\n\n if (occupied[i].hasOwnProperty(\"occupied\")) {\n results[regionKey][\"occupied\"] = occupied[i].occupied;\n }\n\n if (occupied[i].hasOwnProperty(\"extra\")) {\n for (let key in occupied[i].extra) {\n results[regionKey][key] = occupied[i].extra[key];\n }\n }\n if (results[regionKey].hasOwnProperty(\"id\") || (previous_data && previous_data.hasOwnProperty(regionKey) && previous_data[regionKey].hasOwnProperty(\"id\"))) {\n results[regionKey][\"actual_entry_time\"] = [];\n results[regionKey][\"actual_dwell_time\"] = [];\n results[regionKey][\"entry_time\"] = [];\n results[regionKey][\"dwell_time\"] = [];\n results[regionKey][\"exit_time\"] = [];\n }\n else {\n results[\"Region \" + (i + 1)][\"actual_entry_time\"] = \"\";\n results[\"Region \" + (i + 1)][\"actual_dwell_time\"] = \"\";\n results[\"Region \" + (i + 1)][\"entry_time\"] = \"\";\n results[\"Region \" + (i + 1)][\"dwell_time\"] = \"\";\n results[\"Region \" + (i + 1)][\"exit_time\"] = \"\";\n results[\"Region \" + (i + 1)][\"occupancy_check\"] = 0;\n }\n return results;\n}\n\nfunction initializePreviousData(results, i) {\n const regionKey = \"Region \" + (i + 1);\n\n // Ensure previous_data is set up for this region.\n if (!previous_data) {\n previous_data = {};\n }\n if (results[regionKey].hasOwnProperty(\"id\")) {\n if (!previous_data[regionKey]) {\n previous_data[regionKey] = {};\n previous_data[regionKey][\"id\"] = [];\n previous_data[regionKey][\"actual_entry_time\"] = [];\n previous_data[regionKey][\"actual_dwell_time\"] = [];\n previous_data[regionKey][\"entry_time\"] = [];\n previous_data[regionKey][\"dwell_time\"] = [];\n previous_data[regionKey][\"exit_time\"] = [];\n }\n }\n else {\n if (!previous_data.hasOwnProperty(regionKey)) {\n previous_data[regionKey] = results[regionKey];\n previous_data[regionKey][\"actual_entry_time\"] = \"\";\n previous_data[regionKey][\"actual_dwell_time\"] = \"\";\n previous_data[regionKey][\"entry_time\"] = \"\";\n previous_data[regionKey][\"dwell_time\"] = \"\";\n previous_data[regionKey][\"exit_time\"] = \"\";\n previous_data[regionKey][\"occupancy_check\"] = 0;\n }\n }\n\n return previous_data;\n}\n\nfunction handleObjectWithID(results, i) {\n const regionKey = \"Region \" + (i + 1);\n const resultIds = results[regionKey][\"id\"];\n const previousDataIds = previous_data[regionKey][\"id\"];\n\n if (!resultIds && !previousDataIds) {\n return results;\n }\n\n if (resultIds) {\n for (let j = 0; j < resultIds.length; j++) {\n // Check existing id data\n let prevIndex = -1;\n\n if (previous_data[regionKey][\"id\"] && previous_data[regionKey][\"id\"].length > 0) {\n prevIndex = previous_data[regionKey][\"id\"].indexOf(resultIds[j]);\n }\n\n if (prevIndex === -1) {\n results[regionKey][\"actual_entry_time\"].push(msg.nodered_timestamp);\n results[regionKey][\"entry_time\"].push(msg.object_timestamp);\n results[regionKey][\"actual_dwell_time\"].push(\"00:00:00\");\n results[regionKey][\"dwell_time\"].push(0);\n results[regionKey][\"exit_time\"].push(\"\");\n } else {\n results[regionKey][\"actual_entry_time\"].push(previous_data[regionKey][\"actual_entry_time\"][prevIndex]);\n results[regionKey][\"entry_time\"].push(previous_data[regionKey][\"entry_time\"][prevIndex]);\n\n // Calculate dwell time.\n const dwell_timestamp = (msg.object_timestamp - results[regionKey][\"entry_time\"][j]);\n const dwell_hours = Math.floor(dwell_timestamp / 3600).toString().padStart(2, \"0\");\n const dwell_mins = Math.floor((dwell_timestamp / 60) % 60).toString().padStart(2, \"0\");\n const dwell_seconds = Math.floor(dwell_timestamp % 60).toString().padStart(2, \"0\");\n results[regionKey][\"dwell_time\"].push(dwell_timestamp);\n results[regionKey][\"actual_dwell_time\"].push(dwell_hours + \":\" + dwell_mins + \":\" + dwell_seconds);\n results[regionKey][\"exit_time\"].push(\"\");\n }\n }\n }\n\n if (previousDataIds) {\n for (let j = 0; j < previousDataIds.length; j++) {\n let prevIndex = -1;\n\n if (!results[regionKey][\"id\"]) {\n results[regionKey] = previous_data[regionKey];\n results[regionKey][\"occupied\"] = 0;\n break;\n }\n else {\n if (!results[regionKey][\"dwell_time\"].some(val => val >= stop_duration)) {\n results[regionKey][\"occupied\"] = 0;\n }\n\n prevIndex = results[regionKey][\"id\"].indexOf(previousDataIds[j]);\n if (prevIndex === -1) {\n let keyList = Object.keys(previous_data[regionKey]);\n for (let k = 0; k < keyList.length; k++) {\n if (keyList[k] == \"video_id\" || keyList[k] == \"region_id\" || keyList[k] == \"occupied\") {\n continue;\n }\n results[regionKey][keyList[k]].push(previous_data[regionKey][keyList[k]][j]);\n }\n }\n }\n }\n }\n return results;\n}\n\nfunction handleOccupiedRegion(results, i) {\n const regionKey = \"Region \" + (i + 1);\n if (previous_data[regionKey][\"entry_time\"] == \"\") {\n results[regionKey][\"actual_entry_time\"] = msg.nodered_timestamp;\n results[regionKey][\"entry_time\"] = msg.object_timestamp;\n }\n else {\n if (previous_data[regionKey][\"occupancy_check\"] >= occupancy_check_duration) {\n results[regionKey][\"actual_dwell_time\"] = \"00:00:00\";\n results[regionKey][\"dwell_time\"] = 0;\n results[regionKey][\"actual_entry_time\"] = msg.nodered_timestamp;\n results[regionKey][\"entry_time\"] = msg.object_timestamp;\n }\n else {\n results[regionKey][\"actual_entry_time\"] = previous_data[regionKey][\"actual_entry_time\"];\n results[regionKey][\"entry_time\"] = previous_data[regionKey][\"entry_time\"];\n }\n\n var dwell_timestamp = msg.object_timestamp - results[regionKey][\"entry_time\"];\n var dwell_hours = Math.floor(dwell_timestamp / 3600).toString();\n dwell_hours = dwell_hours.toString().length == 1 ? dwell_hours.toString().padStart(2, \"0\") : dwell_hours.toString();\n var dwell_mins = Math.floor((dwell_timestamp / 60) % 60).toString().padStart(2, \"0\");\n var dwell_seconds = Math.floor(dwell_timestamp % 60).toString().padStart(2, \"0\");\n\n results[regionKey][\"dwell_time\"] = dwell_timestamp;\n results[regionKey][\"actual_dwell_time\"] = dwell_hours + \":\" + dwell_mins + \":\" + dwell_seconds;\n results[regionKey][\"occupancy_check\"] = 0;\n }\n\n return results;\n}\n\nfunction handleNotOccupiedRegion(results, i) {\n const regionKey = \"Region \" + (i + 1);\n\n results[regionKey][\"actual_entry_time\"] = previous_data[regionKey][\"actual_entry_time\"];\n results[regionKey][\"entry_time\"] = previous_data[regionKey][\"entry_time\"];\n results[regionKey][\"actual_dwell_time\"] = previous_data[regionKey][\"actual_dwell_time\"];\n results[regionKey][\"dwell_time\"] = previous_data[regionKey][\"dwell_time\"];\n results[regionKey][\"occupancy_check\"] = previous_data[regionKey][\"occupancy_check\"] + 1;\n\n if (previous_data[regionKey][\"exit_time\"]) {\n results[regionKey][\"exit_time\"] = previous_data[regionKey][\"exit_time\"];\n }\n else {\n results[regionKey][\"exit_time\"] = msg.nodered_timestamp;\n }\n return results;\n}\n\nfunction main(payload, distance_threshold = 100, intersection_type = \"region\", intersection_threshold = 0.5, object_counter = false) {\n var results = preprocess_bboxes(payload);\n var occupied = euclidean_iou_function(results, distance_threshold, intersection_type, intersection_threshold, object_counter);\n var out = postprocess_output(occupied);\n flow.set(previous_data_name, out);\n return out;\n}\n\n// user-defined parameters\nvar distance_threshold = flow.get(\"distance_threshold\");\nvar intersection_type = flow.get(\"intersection_type\");\nvar intersection_threshold = flow.get(\"intersection_threshold\");\nvar object_counter = flow.get(\"object_counter\");\n\nmsg.payload = main(payload, distance_threshold, intersection_type, intersection_threshold, object_counter);\nflow.set(detection_name, msg);\nreturn msg;", + "func": "let region_name = \"predefined_regions_3\";\nlet detection_name = \"object_detection_3\";\nlet previous_data_name = \"previous_data_3\";\nlet occupancy_check_duration = 60;\nlet min_entry_frames = 30;\nlet video_id = 3;\nlet region_id = -1; //Recommend to set this only when there is 1 predefined region in the video\n\nvar payload = msg.payload;\nvar previous_data = flow.get(previous_data_name);\nvar stop_duration = flow.get(\"stop_duration\");\n\n// preparing bboxes\nfunction preprocess_bboxes(payload) {\n function getObjectData(object_data) {\n var current_object_data = {};\n if (object_data.hasOwnProperty(\"type\")) {\n current_object_data[\"type\"] = object_data[\"type\"];\n }\n if (object_data.hasOwnProperty(\"color\")) {\n current_object_data[\"color\"] = object_data[\"color\"];\n }\n if (object_data.hasOwnProperty(\"license_plate\")) {\n current_object_data[\"license_plate\"] = object_data[\"license_plate\"];\n }\n if (object_data.hasOwnProperty(\"id\")) {\n current_object_data[\"id\"] = object_data[\"id\"];\n }\n if (object_data.hasOwnProperty(\"roi_type\")) {\n current_object_data[\"roi_type\"] = object_data[\"roi_type\"];\n }\n return current_object_data;\n }\n function getLotBbox(lot_data) {\n return { \"vertex1\": lot_data.vertex1, \"vertex2\": lot_data.vertex2, \"vertex3\": lot_data.vertex3, \"vertex4\": lot_data.vertex4, \"vertex_0\": lot_data.vertex1 };\n }\n\n var results = {};\n var objects = [];\n var objects_extra = [];\n var lots = [];\n // get all objects bboxes\n for (let key in payload) {\n if (payload.hasOwnProperty(key)) {\n var objectData = getObjectData(payload[key]);\n if (objectData !== null) {\n objects_extra.push(\n objectData\n );\n objects.push(\n payload[key][\"bbox\"]\n );\n }\n }\n }\n // get all lots bboxes\n var predefined_regions = flow.get(region_name);\n var keys = Object.keys(predefined_regions);\n\n for (let i = 0; i < keys.length; i++) {\n if (predefined_regions.hasOwnProperty(keys[i]) && keys[i] != \"_msgid\") {\n lots.push(\n getLotBbox(predefined_regions[keys[i]])\n );\n }\n }\n\n results[\"objects\"] = objects;\n results[\"objects_extra\"] = objects_extra;\n results[\"lots\"] = lots;\n return results;\n}\n\n// euclidean distance and iou check\nfunction euclidean_iou_function(payload, distance_threshold = 100, intersection_type = \"region\", intersection_threshold = 0.5, object_counter = false) {\n function getBboxCentroid(bbox) {\n var centroid_x = (bbox[\"vertex1\"].x + bbox[\"vertex2\"].x + bbox[\"vertex3\"].x + bbox[\"vertex4\"].x) / 4;\n var centroid_y = (bbox[\"vertex1\"].y + bbox[\"vertex2\"].y + bbox[\"vertex3\"].y + bbox[\"vertex4\"].y) / 4;\n return [centroid_x, centroid_y];\n }\n function calculateEuclideanDistance(centroid1, centroid2) {\n return Math.sqrt((centroid2[0] - centroid1[0]) ** 2 + (centroid2[1] - centroid1[1]) ** 2);\n }\n function calculateIntersectArea(bbox1, bbox2) { // objects, lots\n var bbox1_key = Object.keys(bbox1)\n var bbox2_key = Object.keys(bbox2)\n var intersection = [];\n\n for (let i = 0; i < bbox1_key.length - 1; i++) {\n for (let j = 0; j < bbox2_key.length - 1; j++) {\n var A1 = bbox1[bbox1_key[i + 1]].y - bbox1[bbox1_key[i]].y; // Ax + By + C = 0\n var B1 = bbox1[bbox1_key[i]].x - bbox1[bbox1_key[i + 1]].x;\n var C1 = - A1 * bbox1[bbox1_key[i]].x - B1 * bbox1[bbox1_key[i]].y;\n\n var A2 = bbox2[bbox2_key[j + 1]].y - bbox2[bbox2_key[j]].y;\n var B2 = bbox2[bbox2_key[j]].x - bbox2[bbox2_key[j + 1]].x;\n var C2 = - A2 * bbox2[bbox2_key[j]].x - B2 * bbox2[bbox2_key[j]].y;\n\n // parallel lines case\n if (A1 * B2 == A2 * B1) {\n // coincide case\n if ((A1 * C2 == A2 * C1) && (B1 * C2 == B2 * C1)) {\n if (B1 == 0 && A1 == 0) { // one point bbox case\n intersection.push([bbox1[bbox1_key[i]].x.toFixed(6), bbox1[bbox1_key[i]].y.toFixed(6)]);\n }\n else if (B2 == 0 && A2 == 0) { // one point bbox case\n intersection.push([bbox2[bbox2_key[i]].x.toFixed(6), bbox2[bbox2_key[i]].y.toFixed(6)]);\n }\n else if (B1 == 0) { // vertical lines case\n var y_min1 = (bbox1[bbox1_key[i]].y < bbox1[bbox1_key[i + 1]].y) ? bbox1[bbox1_key[i]].y : bbox1[bbox1_key[i + 1]].y;\n var y_max1 = (bbox1[bbox1_key[i]].y < bbox1[bbox1_key[i + 1]].y) ? bbox1[bbox1_key[i + 1]].y : bbox1[bbox1_key[i]].y;\n var y_min2 = (bbox2[bbox2_key[i]].y < bbox2[bbox2_key[i + 1]].y) ? bbox2[bbox2_key[i]].y : bbox2[bbox2_key[i + 1]].y;\n var y_max2 = (bbox2[bbox2_key[i]].y < bbox2[bbox2_key[i + 1]].y) ? bbox2[bbox2_key[i + 1]].y : bbox2[bbox2_key[i]].y;\n if ((y_min1 < y_max2) && (y_min2 < y_max1)) {\n var y_min = (y_min1 < y_min2) ? y_min2 : y_min1;\n var y_max = (y_max1 < y_max2) ? y_max1 : y_max2;\n intersection.push([bbox1[bbox1_key[i]].x.toFixed(6), y_min.toFixed(6)]);\n intersection.push([bbox1[bbox1_key[i]].x.toFixed(6), y_max.toFixed(6)]);\n }\n }\n else if (A1 == 0) { // horizontal lines case\n var x_min1 = (bbox1[bbox1_key[i]].x < bbox1[bbox1_key[i + 1]].x) ? bbox1[bbox1_key[i]].x : bbox1[bbox1_key[i + 1]].x;\n var x_max1 = (bbox1[bbox1_key[i]].x < bbox1[bbox1_key[i + 1]].x) ? bbox1[bbox1_key[i + 1]].x : bbox1[bbox1_key[i]].x;\n var x_min2 = (bbox2[bbox2_key[i]].x < bbox2[bbox2_key[i + 1]].x) ? bbox2[bbox2_key[i]].x : bbox2[bbox2_key[i + 1]].x;\n var x_max2 = (bbox2[bbox2_key[i]].x < bbox2[bbox2_key[i + 1]].x) ? bbox2[bbox2_key[i + 1]].x : bbox2[bbox2_key[i]].x;\n if ((x_min1 < x_max2) && (x_min2 < x_max1)) {\n var x_min = (x_min1 < x_min2) ? x_min2 : x_min1;\n var x_max = (x_max1 < x_max2) ? x_max1 : x_max2;\n intersection.push([x_min.toFixed(6), bbox1[bbox1_key[i]].y.toFixed(6)]);\n intersection.push([x_max.toFixed(6), bbox1[bbox1_key[i]].y.toFixed(6)]);\n }\n }\n else {\n var x_min1 = (bbox1[bbox1_key[i]].x < bbox1[bbox1_key[i + 1]].x) ? bbox1[bbox1_key[i]].x : bbox1[bbox1_key[i + 1]].x;\n var x_max1 = (bbox1[bbox1_key[i]].x < bbox1[bbox1_key[i + 1]].x) ? bbox1[bbox1_key[i + 1]].x : bbox1[bbox1_key[i]].x;\n var x_min2 = (bbox2[bbox2_key[i]].x < bbox2[bbox2_key[i + 1]].x) ? bbox2[bbox2_key[i]].x : bbox2[bbox2_key[i + 1]].x;\n var x_max2 = (bbox2[bbox2_key[i]].x < bbox2[bbox2_key[i + 1]].x) ? bbox2[bbox2_key[i + 1]].x : bbox2[bbox2_key[i]].x;\n if ((x_min1 < x_max2) && (x_min2 < x_max1) && (y_min1 < y_max2) && (y_min2 < y_max1)) {\n var x_min = (x_min1 < x_min2) ? x_min2 : x_min1;\n var x_max = (x_max1 < x_max2) ? x_max1 : x_max2;\n var xY_min = (-A1 * x_min - C1) / B1\n var xY_max = (-A1 * x_max - C1) / B1\n intersection.push([x_min.toFixed(6), xY_min.toFixed(6)]);\n intersection.push([x_max.toFixed(6), xY_max.toFixed(6)]);\n }\n }\n }\n }\n else {\n // get intersection coordinates\n var x1 = bbox1[bbox1_key[i]].x;\n var x2 = bbox1[bbox1_key[i + 1]].x;\n var x3 = bbox2[bbox2_key[j]].x;\n var x4 = bbox2[bbox2_key[j + 1]].x;\n var y1 = bbox1[bbox1_key[i]].y;\n var y2 = bbox1[bbox1_key[i + 1]].y;\n var y3 = bbox2[bbox2_key[j]].y;\n var y4 = bbox2[bbox2_key[j + 1]].y;\n\n var den = (x2 - x1) * (y3 - y4) - (y2 - y1) * (x3 - x4);\n var t = ((x3 - x1) * (y3 - y4) - (y3 - y1) * (x3 - x4)) / den;\n var u = ((x3 - x1) * (y1 - y2) - (y3 - y1) * (x1 - x2)) / den;\n if ((t >= 0) && (t <= 1) && (u >= 0) && (u <= 1)) { // intersect happens if this condition is fulfilled\n var ix = x1 + t * (x2 - x1);\n var iy = y1 + t * (y2 - y1);\n intersection.push([ix.toFixed(6), iy.toFixed(6)]);\n }\n }\n }\n }\n // get clipped coordinates\n var specialcase1 = true;\n var specialcase2 = false;\n var temp1 = bbox1[bbox1_key[0]];\n var temp2 = bbox2[bbox2_key[0]];\n for (let i = 1; i < bbox1_key.length - 1; i++) { // special case\n if (temp1 !== bbox1[bbox1_key[i]]) {\n specialcase1 = false;\n break;\n }\n }\n for (let i = 1; i < bbox2_key.length - 1; i++) {\n if (temp2 !== bbox2[bbox2_key[i]]) {\n specialcase2 = false;\n break;\n }\n }\n if (!(specialcase2)) {\n for (let i = 0; i < bbox1_key.length - 1; i++) {\n var sign_check = [];\n for (let j = 0; j < bbox2_key.length - 1; j++) {\n var x1 = bbox2[bbox2_key[j]].x\n var x2 = bbox2[bbox2_key[j + 1]].x\n var y1 = bbox2[bbox2_key[j]].y\n var y2 = bbox2[bbox2_key[j + 1]].y\n var cross_prod = (bbox1[bbox1_key[i]].x - x1) * (y2 - y1) - (bbox1[bbox1_key[i]].y - y1) * (x2 - x1);\n var sign = (cross_prod >= 0) ? true : false;\n sign_check.push(sign);\n }\n if (sign_check.length === 4 && (sign_check.every((val, index) => val === [true, true, true, true][index]) ||\n sign_check.every((val, index) => val === [false, false, false, false][index]))) { // similar signs indicate the coordinate is within another bbox\n intersection.push([bbox1[bbox1_key[i]].x.toFixed(6), bbox1[bbox1_key[i]].y.toFixed(6)]);\n //intersection.push([bbox1[bbox1_key[i]].x, bbox1[bbox1_key[i]].y]);\n }\n }\n }\n if (!(specialcase1)) {\n for (let i = 0; i < bbox2_key.length - 1; i++) { // similar purpose but for second bbox\n var sign_check = [];\n for (let j = 0; j < bbox1_key.length - 1; j++) {\n var x1 = bbox1[bbox1_key[j]].x\n var x2 = bbox1[bbox1_key[j + 1]].x\n var y1 = bbox1[bbox1_key[j]].y\n var y2 = bbox1[bbox1_key[j + 1]].y\n var cross_prod = (bbox2[bbox2_key[i]].x - x1) * (y2 - y1) - (bbox2[bbox2_key[i]].y - y1) * (x2 - x1);\n var sign = (cross_prod >= 0) ? true : false;\n sign_check.push(sign);\n }\n if (sign_check.length === 4 && (sign_check.every((val, index) => val === [true, true, true, true][index]) ||\n sign_check.every((val, index) => val === [false, false, false, false][index]))) {\n intersection.push([bbox2[bbox2_key[i]].x.toFixed(6), bbox2[bbox2_key[i]].y.toFixed(6)]);\n //intersection.push([bbox2[bbox2_key[i]].x, bbox2[bbox2_key[i]].y]);\n }\n }\n }\n intersection = intersection.filter((item, index, self) =>\n index === self.findIndex((t) => JSON.stringify(t) === JSON.stringify(item))\n );\n // sort intersection points\n if (intersection.length > 2) {\n var intersection_angle = [];\n var intersection_distance = [];\n\n let minIndex = intersection.reduce((minIdx, currentValue, currentIndex, array) => {\n if (currentValue[0] < array[minIdx][0]) {\n return currentIndex;\n } else if (currentValue[0] === array[minIdx][0]) {\n return currentValue[1] < array[minIdx][1] ? currentIndex : minIdx;\n } else {\n return minIdx;\n }\n }, 0);\n var temp_intersection = intersection.filter((_, index) => index !== minIndex);\n for (let i = 0; i < temp_intersection.length; i++) {\n intersection_angle.push(Math.atan2(temp_intersection[i][1] - intersection[minIndex][1], temp_intersection[i][0] - intersection[minIndex][0]));\n intersection_distance.push((temp_intersection[i][1] - intersection[minIndex][1]) ** 2 + (temp_intersection[i][0] - intersection[minIndex][0]) ** 2);\n }\n let combined = temp_intersection.map((point, index) => ({ // sort intersect coordinates based on angle and then distance\n point: point,\n angle: intersection_angle[index],\n distance: intersection_distance[index]\n }));\n combined.sort((a, b) => a.angle - b.angle || a.distance - b.distance);\n let sorted_intersection = combined.map(item => item.point);\n sorted_intersection.unshift(intersection[minIndex]);\n sorted_intersection.push(intersection[minIndex]);\n // shoelace algorithm to calculate the intersect area\n var area = 0;\n for (let i = 0; i < sorted_intersection.length - 1; i++) {\n area += ((sorted_intersection[i][0] * sorted_intersection[i + 1][1])\n - (sorted_intersection[i + 1][0] * sorted_intersection[i][1]))\n }\n return area;\n }\n return 0;\n }\n function calculateIOU(bbox1, bbox2, intersectArea) {\n if (intersectArea !== 0) {\n var lot_area1 = (bbox1[\"vertex1\"].x * bbox1[\"vertex2\"].y) + (bbox1[\"vertex2\"].x * bbox1[\"vertex3\"].y)\n + (bbox1[\"vertex3\"].x * bbox1[\"vertex4\"].y) + (bbox1[\"vertex4\"].x * bbox1[\"vertex_0\"].y)\n - ((bbox1[\"vertex2\"].x * bbox1[\"vertex1\"].y) + (bbox1[\"vertex3\"].x * bbox1[\"vertex2\"].y)\n + (bbox1[\"vertex4\"].x * bbox1[\"vertex3\"].y) + (bbox1[\"vertex_0\"].x * bbox1[\"vertex4\"].y));\n\n var lot_area2 = (bbox2[\"vertex1\"].x * bbox2[\"vertex2\"].y) + (bbox2[\"vertex2\"].x * bbox2[\"vertex3\"].y)\n + (bbox2[\"vertex3\"].x * bbox2[\"vertex4\"].y) + (bbox2[\"vertex4\"].x * bbox2[\"vertex_0\"].y)\n - ((bbox2[\"vertex2\"].x * bbox2[\"vertex1\"].y) + (bbox2[\"vertex3\"].x * bbox2[\"vertex2\"].y)\n + (bbox2[\"vertex4\"].x * bbox2[\"vertex3\"].y) + (bbox2[\"vertex_0\"].x * bbox2[\"vertex4\"].y));\n return intersectArea / (lot_area1 + lot_area2 - intersectArea)\n }\n return 0;\n }\n function calculateIOR(bbox1, intersectArea) {\n if (intersectArea !== 0) {\n var lot_area = (bbox1[\"vertex1\"].x * bbox1[\"vertex2\"].y) + (bbox1[\"vertex2\"].x * bbox1[\"vertex3\"].y)\n + (bbox1[\"vertex3\"].x * bbox1[\"vertex4\"].y) + (bbox1[\"vertex4\"].x * bbox1[\"vertex_0\"].y)\n - ((bbox1[\"vertex2\"].x * bbox1[\"vertex1\"].y) + (bbox1[\"vertex3\"].x * bbox1[\"vertex2\"].y)\n + (bbox1[\"vertex4\"].x * bbox1[\"vertex3\"].y) + (bbox1[\"vertex_0\"].x * bbox1[\"vertex4\"].y));\n return intersectArea / lot_area;\n }\n return 0;\n }\n\n var occupied = [];\n var current_index = -1;\n // initialize lots to no object\n for (let key1 in payload[\"lots\"]) {\n if (payload[\"lots\"].hasOwnProperty(key1)) {\n occupied.push({ \"occupied\": 0 });\n }\n }\n for (let key1 in payload[\"objects\"]) {\n current_index++;\n if (payload[\"objects\"].hasOwnProperty(key1)) { // find all lots within the object centroid distance threshold\n var objectCentroid = getBboxCentroid(payload[\"objects\"][key1]);\n var potential_region = [];\n for (let key2 in payload[\"lots\"]) {\n if (payload[\"lots\"].hasOwnProperty(key2)) {\n var regionCentroid = getBboxCentroid(payload[\"lots\"][key2]);\n var euclideanDistance = calculateEuclideanDistance(objectCentroid, regionCentroid);\n potential_region.push(euclideanDistance);\n }\n }\n // calculate IOU or IOR\n if (object_counter) {\n for (let i = 0; i < potential_region.length; i++) {\n if (potential_region[i] < distance_threshold) {\n var intersectRegion = calculateIntersectArea(payload[\"objects\"][key1], payload[\"lots\"][i]);\n var IOR = 0;\n if (intersection_type == \"region\") {\n IOR = calculateIOR(payload[\"lots\"][i], intersectRegion)\n }\n else if (intersection_type == \"object\") {\n IOR = calculateIOR(payload[\"objects\"][key1], intersectRegion)\n }\n if (IOR > intersection_threshold) {\n occupied[i][\"occupied\"] += 1;\n var keyData = Object.keys(payload[\"objects_extra\"][current_index]);\n var numberData = keyData.length;\n for (let j = 0; j < numberData; j++) {\n if (!occupied[i][\"extra\"]) {\n occupied[i][\"extra\"] = {};\n }\n if (!occupied[i][\"extra\"][keyData[j]]) {\n occupied[i][\"extra\"][keyData[j]] = [];\n }\n occupied[i][\"extra\"][keyData[j]].push(payload[\"objects_extra\"][current_index][keyData[j]]);\n }\n }\n }\n }\n }\n else { // consider the best lot only\n var best_region_index = potential_region.reduce((minIndex, currentValue, currentIndex, array) =>\n currentValue < array[minIndex] ? currentIndex : minIndex, 0);\n if (potential_region[best_region_index] < distance_threshold) {\n var intersectRegion = calculateIntersectArea(payload[\"objects\"][key1], payload[\"lots\"][best_region_index]);\n var IOR = 0;\n if (intersection_type == \"region\") {\n IOR = calculateIOR(payload[\"lots\"][best_region_index], intersectRegion)\n }\n else if (intersection_type == \"object\") {\n IOR = calculateIOR(payload[\"objects\"][key1], intersectRegion)\n }\n if (IOR > intersection_threshold) {\n occupied[best_region_index][\"occupied\"] += 1;\n var keyData = Object.keys(payload[\"objects_extra\"][current_index]);\n var numberData = keyData.length;\n for (let i = 0; i < numberData; i++) {\n if (!occupied[best_region_index][\"extra\"]) {\n occupied[best_region_index][\"extra\"] = {};\n }\n if (!occupied[best_region_index][\"extra\"][keyData[i]]) {\n occupied[best_region_index][\"extra\"][keyData[i]] = [];\n }\n occupied[best_region_index][\"extra\"][keyData[i]].push(payload[\"objects_extra\"][current_index][keyData[i]]);\n }\n }\n }\n }\n }\n }\n return occupied;\n}\n\n// turn into correct format\nfunction postprocess_output(occupied) {\n var results = {};\n\n for (let i = 0; i < occupied.length; i++) {\n results = initializeResults(results, occupied, i);\n previous_data = initializePreviousData(results, i);\n if (results[\"Region \" + (i + 1)].hasOwnProperty(\"id\") || previous_data[\"Region \" + (i + 1)].hasOwnProperty(\"id\")) {\n results = handleObjectWithID(results, i);\n }\n else if (results[\"Region \" + (i + 1)][\"occupied\"] > 0) {\n results = handleOccupiedRegion(results, i);\n }\n else if (previous_data[\"Region \" + (i + 1)][\"entry_time\"]) {\n results = handleNotOccupiedRegion(results, i);\n }\n }\n return results;\n}\n\nfunction initializeResults(results, occupied, i) {\n const regionKey = \"Region \" + (i + 1);\n\n if (!results[regionKey]) {\n results[regionKey] = {};\n }\n results[regionKey][\"video_id\"] = video_id;\n results[regionKey][\"region_id\"] = (region_id != -1) ? region_id : i + 1;\n\n if (occupied[i].hasOwnProperty(\"occupied\")) {\n results[regionKey][\"occupied\"] = occupied[i].occupied;\n }\n\n if (occupied[i].hasOwnProperty(\"extra\")) {\n for (let key in occupied[i].extra) {\n results[regionKey][key] = occupied[i].extra[key];\n }\n }\n if (results[regionKey].hasOwnProperty(\"id\") || (previous_data && previous_data.hasOwnProperty(regionKey) && previous_data[regionKey].hasOwnProperty(\"id\"))) {\n results[regionKey][\"actual_entry_time\"] = [];\n results[regionKey][\"actual_dwell_time\"] = [];\n results[regionKey][\"entry_time\"] = [];\n results[regionKey][\"dwell_time\"] = [];\n results[regionKey][\"exit_time\"] = [];\n }\n else {\n results[\"Region \" + (i + 1)][\"actual_entry_time\"] = \"\";\n results[\"Region \" + (i + 1)][\"actual_dwell_time\"] = \"\";\n results[\"Region \" + (i + 1)][\"entry_time\"] = \"\";\n results[\"Region \" + (i + 1)][\"dwell_time\"] = \"\";\n results[\"Region \" + (i + 1)][\"exit_time\"] = \"\";\n results[\"Region \" + (i + 1)][\"occupancy_check\"] = 0;\n }\n return results;\n}\n\nfunction initializePreviousData(results, i) {\n const regionKey = \"Region \" + (i + 1);\n\n // Ensure previous_data is set up for this region.\n if (!previous_data) {\n previous_data = {};\n }\n if (results[regionKey].hasOwnProperty(\"id\")) {\n if (!previous_data[regionKey]) {\n previous_data[regionKey] = {};\n previous_data[regionKey][\"id\"] = [];\n previous_data[regionKey][\"actual_entry_time\"] = [];\n previous_data[regionKey][\"actual_dwell_time\"] = [];\n previous_data[regionKey][\"entry_time\"] = [];\n previous_data[regionKey][\"dwell_time\"] = [];\n previous_data[regionKey][\"exit_time\"] = [];\n }\n }\n else {\n if (!previous_data.hasOwnProperty(regionKey)) {\n previous_data[regionKey] = results[regionKey];\n previous_data[regionKey][\"actual_entry_time\"] = \"\";\n previous_data[regionKey][\"actual_dwell_time\"] = \"\";\n previous_data[regionKey][\"entry_time\"] = \"\";\n previous_data[regionKey][\"dwell_time\"] = \"\";\n previous_data[regionKey][\"exit_time\"] = \"\";\n previous_data[regionKey][\"occupancy_check\"] = 0;\n }\n }\n\n return previous_data;\n}\n\nfunction handleObjectWithID(results, i) {\n const regionKey = \"Region \" + (i + 1);\n const resultIds = results[regionKey][\"id\"];\n const previousDataIds = previous_data[regionKey][\"id\"];\n\n if (!resultIds && !previousDataIds) {\n return results;\n }\n\n if (resultIds) {\n for (let j = 0; j < resultIds.length; j++) {\n // Check existing id data\n let prevIndex = -1;\n\n if (previous_data[regionKey][\"id\"] && previous_data[regionKey][\"id\"].length > 0) {\n prevIndex = previous_data[regionKey][\"id\"].indexOf(resultIds[j]);\n }\n\n if (prevIndex === -1) {\n results[regionKey][\"actual_entry_time\"].push(msg.nodered_timestamp);\n results[regionKey][\"entry_time\"].push(msg.object_timestamp);\n results[regionKey][\"actual_dwell_time\"].push(\"00:00:00\");\n results[regionKey][\"dwell_time\"].push(0);\n results[regionKey][\"exit_time\"].push(\"\");\n } else {\n results[regionKey][\"actual_entry_time\"].push(previous_data[regionKey][\"actual_entry_time\"][prevIndex]);\n results[regionKey][\"entry_time\"].push(previous_data[regionKey][\"entry_time\"][prevIndex]);\n\n // Calculate dwell time.\n const dwell_timestamp = (msg.object_timestamp - results[regionKey][\"entry_time\"][j]);\n const dwell_hours = Math.floor(dwell_timestamp / 3600).toString().padStart(2, \"0\");\n const dwell_mins = Math.floor((dwell_timestamp / 60) % 60).toString().padStart(2, \"0\");\n const dwell_seconds = Math.floor(dwell_timestamp % 60).toString().padStart(2, \"0\");\n results[regionKey][\"dwell_time\"].push(dwell_timestamp);\n results[regionKey][\"actual_dwell_time\"].push(dwell_hours + \":\" + dwell_mins + \":\" + dwell_seconds);\n results[regionKey][\"exit_time\"].push(\"\");\n }\n }\n }\n\n if (previousDataIds) {\n for (let j = 0; j < previousDataIds.length; j++) {\n let prevIndex = -1;\n\n if (!results[regionKey][\"id\"]) {\n results[regionKey] = previous_data[regionKey];\n results[regionKey][\"occupied\"] = 0;\n break;\n }\n else {\n if (!results[regionKey][\"dwell_time\"].some(val => val >= stop_duration)) {\n results[regionKey][\"occupied\"] = 0;\n }\n\n prevIndex = results[regionKey][\"id\"].indexOf(previousDataIds[j]);\n if (prevIndex === -1) {\n let keyList = Object.keys(previous_data[regionKey]);\n for (let k = 0; k < keyList.length; k++) {\n if (keyList[k] == \"video_id\" || keyList[k] == \"region_id\" || keyList[k] == \"occupied\") {\n continue;\n }\n results[regionKey][keyList[k]].push(previous_data[regionKey][keyList[k]][j]);\n }\n }\n }\n }\n }\n return results;\n}\n\nfunction handleOccupiedRegion(results, i) {\n const regionKey = \"Region \" + (i + 1);\n var prev_consecutive = (previous_data[regionKey][\"consecutive_occupied\"]) || 0;\n results[regionKey][\"consecutive_occupied\"] = prev_consecutive + 1;\n if (results[regionKey][\"consecutive_occupied\"] < min_entry_frames) {\n results[regionKey][\"occupied\"] = 0;\n results[regionKey][\"occupancy_check\"] = 0;\n return results;\n }\n if (previous_data[regionKey][\"entry_time\"] == \"\") {\n results[regionKey][\"actual_entry_time\"] = msg.nodered_timestamp;\n results[regionKey][\"entry_time\"] = msg.object_timestamp;\n }\n else {\n if (previous_data[regionKey][\"occupancy_check\"] >= occupancy_check_duration) {\n results[regionKey][\"actual_dwell_time\"] = \"00:00:00\";\n results[regionKey][\"dwell_time\"] = 0;\n results[regionKey][\"actual_entry_time\"] = msg.nodered_timestamp;\n results[regionKey][\"entry_time\"] = msg.object_timestamp;\n }\n else {\n results[regionKey][\"actual_entry_time\"] = previous_data[regionKey][\"actual_entry_time\"];\n results[regionKey][\"entry_time\"] = previous_data[regionKey][\"entry_time\"];\n }\n\n var dwell_timestamp = msg.object_timestamp - results[regionKey][\"entry_time\"];\n var dwell_hours = Math.floor(dwell_timestamp / 3600).toString();\n dwell_hours = dwell_hours.toString().length == 1 ? dwell_hours.toString().padStart(2, \"0\") : dwell_hours.toString();\n var dwell_mins = Math.floor((dwell_timestamp / 60) % 60).toString().padStart(2, \"0\");\n var dwell_seconds = Math.floor(dwell_timestamp % 60).toString().padStart(2, \"0\");\n\n results[regionKey][\"dwell_time\"] = dwell_timestamp;\n results[regionKey][\"actual_dwell_time\"] = dwell_hours + \":\" + dwell_mins + \":\" + dwell_seconds;\n results[regionKey][\"occupancy_check\"] = 0;\n }\n\n return results;\n}\n\nfunction handleNotOccupiedRegion(results, i) {\n const regionKey = \"Region \" + (i + 1);\n\n results[regionKey][\"actual_entry_time\"] = previous_data[regionKey][\"actual_entry_time\"];\n results[regionKey][\"entry_time\"] = previous_data[regionKey][\"entry_time\"];\n results[regionKey][\"actual_dwell_time\"] = previous_data[regionKey][\"actual_dwell_time\"];\n results[regionKey][\"dwell_time\"] = previous_data[regionKey][\"dwell_time\"];\n results[regionKey][\"occupancy_check\"] = previous_data[regionKey][\"occupancy_check\"] + 1;\n\n if (previous_data[regionKey][\"exit_time\"]) {\n results[regionKey][\"exit_time\"] = previous_data[regionKey][\"exit_time\"];\n }\n else {\n results[regionKey][\"exit_time\"] = msg.nodered_timestamp;\n }\n return results;\n}\n\nfunction main(payload, distance_threshold = 100, intersection_type = \"region\", intersection_threshold = 0.5, object_counter = false) {\n var results = preprocess_bboxes(payload);\n var occupied = euclidean_iou_function(results, distance_threshold, intersection_type, intersection_threshold, object_counter);\n var out = postprocess_output(occupied);\n flow.set(previous_data_name, out);\n return out;\n}\n\n// user-defined parameters\nvar distance_threshold = flow.get(\"distance_threshold\");\nvar intersection_type = flow.get(\"intersection_type\");\nvar intersection_threshold = flow.get(\"intersection_threshold\");\nvar object_counter = flow.get(\"object_counter\");\n\nmsg.payload = main(payload, distance_threshold, intersection_type, intersection_threshold, object_counter);\nflow.set(detection_name, msg);\nreturn msg;", "outputs": 1, "timeout": 0, "noerr": 0, @@ -1410,7 +1410,7 @@ "type": "euclidean and ior", "z": "2720733497fe05f3", "name": "euclidean and ior 4", - "func": "let region_name = \"predefined_regions_4\";\nlet detection_name = \"object_detection_4\";\nlet previous_data_name = \"previous_data_4\";\nlet occupancy_check_duration = 60;\nlet video_id = 4;\nlet region_id = -1; //Recommend to set this only when there is 1 predefined region in the video\n\nvar payload = msg.payload;\nvar previous_data = flow.get(previous_data_name);\nvar stop_duration = flow.get(\"stop_duration\");\n\n// preparing bboxes\nfunction preprocess_bboxes(payload) {\n function getObjectData(object_data) {\n var current_object_data = {};\n if (object_data.hasOwnProperty(\"type\")) {\n current_object_data[\"type\"] = object_data[\"type\"];\n }\n if (object_data.hasOwnProperty(\"color\")) {\n current_object_data[\"color\"] = object_data[\"color\"];\n }\n if (object_data.hasOwnProperty(\"license_plate\")) {\n current_object_data[\"license_plate\"] = object_data[\"license_plate\"];\n }\n if (object_data.hasOwnProperty(\"id\")) {\n current_object_data[\"id\"] = object_data[\"id\"];\n }\n if (object_data.hasOwnProperty(\"roi_type\")) {\n current_object_data[\"roi_type\"] = object_data[\"roi_type\"];\n }\n return current_object_data;\n }\n function getLotBbox(lot_data) {\n return { \"vertex1\": lot_data.vertex1, \"vertex2\": lot_data.vertex2, \"vertex3\": lot_data.vertex3, \"vertex4\": lot_data.vertex4, \"vertex_0\": lot_data.vertex1 };\n }\n\n var results = {};\n var objects = [];\n var objects_extra = [];\n var lots = [];\n // get all objects bboxes\n for (let key in payload) {\n if (payload.hasOwnProperty(key)) {\n var objectData = getObjectData(payload[key]);\n if (objectData !== null) {\n objects_extra.push(\n objectData\n );\n objects.push(\n payload[key][\"bbox\"]\n );\n }\n }\n }\n // get all lots bboxes\n var predefined_regions = flow.get(region_name);\n var keys = Object.keys(predefined_regions);\n\n for (let i = 0; i < keys.length; i++) {\n if (predefined_regions.hasOwnProperty(keys[i]) && keys[i] != \"_msgid\") {\n lots.push(\n getLotBbox(predefined_regions[keys[i]])\n );\n }\n }\n\n results[\"objects\"] = objects;\n results[\"objects_extra\"] = objects_extra;\n results[\"lots\"] = lots;\n return results;\n}\n\n// euclidean distance and iou check\nfunction euclidean_iou_function(payload, distance_threshold = 100, intersection_type = \"region\", intersection_threshold = 0.5, object_counter = false) {\n function getBboxCentroid(bbox) {\n var centroid_x = (bbox[\"vertex1\"].x + bbox[\"vertex2\"].x + bbox[\"vertex3\"].x + bbox[\"vertex4\"].x) / 4;\n var centroid_y = (bbox[\"vertex1\"].y + bbox[\"vertex2\"].y + bbox[\"vertex3\"].y + bbox[\"vertex4\"].y) / 4;\n return [centroid_x, centroid_y];\n }\n function calculateEuclideanDistance(centroid1, centroid2) {\n return Math.sqrt((centroid2[0] - centroid1[0]) ** 2 + (centroid2[1] - centroid1[1]) ** 2);\n }\n function calculateIntersectArea(bbox1, bbox2) { // objects, lots\n var bbox1_key = Object.keys(bbox1)\n var bbox2_key = Object.keys(bbox2)\n var intersection = [];\n\n for (let i = 0; i < bbox1_key.length - 1; i++) {\n for (let j = 0; j < bbox2_key.length - 1; j++) {\n var A1 = bbox1[bbox1_key[i + 1]].y - bbox1[bbox1_key[i]].y; // Ax + By + C = 0\n var B1 = bbox1[bbox1_key[i]].x - bbox1[bbox1_key[i + 1]].x;\n var C1 = - A1 * bbox1[bbox1_key[i]].x - B1 * bbox1[bbox1_key[i]].y;\n\n var A2 = bbox2[bbox2_key[j + 1]].y - bbox2[bbox2_key[j]].y;\n var B2 = bbox2[bbox2_key[j]].x - bbox2[bbox2_key[j + 1]].x;\n var C2 = - A2 * bbox2[bbox2_key[j]].x - B2 * bbox2[bbox2_key[j]].y;\n\n // parallel lines case\n if (A1 * B2 == A2 * B1) {\n // coincide case\n if ((A1 * C2 == A2 * C1) && (B1 * C2 == B2 * C1)) {\n if (B1 == 0 && A1 == 0) { // one point bbox case\n intersection.push([bbox1[bbox1_key[i]].x.toFixed(6), bbox1[bbox1_key[i]].y.toFixed(6)]);\n }\n else if (B2 == 0 && A2 == 0) { // one point bbox case\n intersection.push([bbox2[bbox2_key[i]].x.toFixed(6), bbox2[bbox2_key[i]].y.toFixed(6)]);\n }\n else if (B1 == 0) { // vertical lines case\n var y_min1 = (bbox1[bbox1_key[i]].y < bbox1[bbox1_key[i + 1]].y) ? bbox1[bbox1_key[i]].y : bbox1[bbox1_key[i + 1]].y;\n var y_max1 = (bbox1[bbox1_key[i]].y < bbox1[bbox1_key[i + 1]].y) ? bbox1[bbox1_key[i + 1]].y : bbox1[bbox1_key[i]].y;\n var y_min2 = (bbox2[bbox2_key[i]].y < bbox2[bbox2_key[i + 1]].y) ? bbox2[bbox2_key[i]].y : bbox2[bbox2_key[i + 1]].y;\n var y_max2 = (bbox2[bbox2_key[i]].y < bbox2[bbox2_key[i + 1]].y) ? bbox2[bbox2_key[i + 1]].y : bbox2[bbox2_key[i]].y;\n if ((y_min1 < y_max2) && (y_min2 < y_max1)) {\n var y_min = (y_min1 < y_min2) ? y_min2 : y_min1;\n var y_max = (y_max1 < y_max2) ? y_max1 : y_max2;\n intersection.push([bbox1[bbox1_key[i]].x.toFixed(6), y_min.toFixed(6)]);\n intersection.push([bbox1[bbox1_key[i]].x.toFixed(6), y_max.toFixed(6)]);\n }\n }\n else if (A1 == 0) { // horizontal lines case\n var x_min1 = (bbox1[bbox1_key[i]].x < bbox1[bbox1_key[i + 1]].x) ? bbox1[bbox1_key[i]].x : bbox1[bbox1_key[i + 1]].x;\n var x_max1 = (bbox1[bbox1_key[i]].x < bbox1[bbox1_key[i + 1]].x) ? bbox1[bbox1_key[i + 1]].x : bbox1[bbox1_key[i]].x;\n var x_min2 = (bbox2[bbox2_key[i]].x < bbox2[bbox2_key[i + 1]].x) ? bbox2[bbox2_key[i]].x : bbox2[bbox2_key[i + 1]].x;\n var x_max2 = (bbox2[bbox2_key[i]].x < bbox2[bbox2_key[i + 1]].x) ? bbox2[bbox2_key[i + 1]].x : bbox2[bbox2_key[i]].x;\n if ((x_min1 < x_max2) && (x_min2 < x_max1)) {\n var x_min = (x_min1 < x_min2) ? x_min2 : x_min1;\n var x_max = (x_max1 < x_max2) ? x_max1 : x_max2;\n intersection.push([x_min.toFixed(6), bbox1[bbox1_key[i]].y.toFixed(6)]);\n intersection.push([x_max.toFixed(6), bbox1[bbox1_key[i]].y.toFixed(6)]);\n }\n }\n else {\n var x_min1 = (bbox1[bbox1_key[i]].x < bbox1[bbox1_key[i + 1]].x) ? bbox1[bbox1_key[i]].x : bbox1[bbox1_key[i + 1]].x;\n var x_max1 = (bbox1[bbox1_key[i]].x < bbox1[bbox1_key[i + 1]].x) ? bbox1[bbox1_key[i + 1]].x : bbox1[bbox1_key[i]].x;\n var x_min2 = (bbox2[bbox2_key[i]].x < bbox2[bbox2_key[i + 1]].x) ? bbox2[bbox2_key[i]].x : bbox2[bbox2_key[i + 1]].x;\n var x_max2 = (bbox2[bbox2_key[i]].x < bbox2[bbox2_key[i + 1]].x) ? bbox2[bbox2_key[i + 1]].x : bbox2[bbox2_key[i]].x;\n if ((x_min1 < x_max2) && (x_min2 < x_max1) && (y_min1 < y_max2) && (y_min2 < y_max1)) {\n var x_min = (x_min1 < x_min2) ? x_min2 : x_min1;\n var x_max = (x_max1 < x_max2) ? x_max1 : x_max2;\n var xY_min = (-A1 * x_min - C1) / B1\n var xY_max = (-A1 * x_max - C1) / B1\n intersection.push([x_min.toFixed(6), xY_min.toFixed(6)]);\n intersection.push([x_max.toFixed(6), xY_max.toFixed(6)]);\n }\n }\n }\n }\n else {\n // get intersection coordinates\n var x1 = bbox1[bbox1_key[i]].x;\n var x2 = bbox1[bbox1_key[i + 1]].x;\n var x3 = bbox2[bbox2_key[j]].x;\n var x4 = bbox2[bbox2_key[j + 1]].x;\n var y1 = bbox1[bbox1_key[i]].y;\n var y2 = bbox1[bbox1_key[i + 1]].y;\n var y3 = bbox2[bbox2_key[j]].y;\n var y4 = bbox2[bbox2_key[j + 1]].y;\n\n var den = (x2 - x1) * (y3 - y4) - (y2 - y1) * (x3 - x4);\n var t = ((x3 - x1) * (y3 - y4) - (y3 - y1) * (x3 - x4)) / den;\n var u = ((x3 - x1) * (y1 - y2) - (y3 - y1) * (x1 - x2)) / den;\n if ((t >= 0) && (t <= 1) && (u >= 0) && (u <= 1)) { // intersect happens if this condition is fulfilled\n var ix = x1 + t * (x2 - x1);\n var iy = y1 + t * (y2 - y1);\n intersection.push([ix.toFixed(6), iy.toFixed(6)]);\n }\n }\n }\n }\n // get clipped coordinates\n var specialcase1 = true;\n var specialcase2 = false;\n var temp1 = bbox1[bbox1_key[0]];\n var temp2 = bbox2[bbox2_key[0]];\n for (let i = 1; i < bbox1_key.length - 1; i++) { // special case\n if (temp1 !== bbox1[bbox1_key[i]]) {\n specialcase1 = false;\n break;\n }\n }\n for (let i = 1; i < bbox2_key.length - 1; i++) {\n if (temp2 !== bbox2[bbox2_key[i]]) {\n specialcase2 = false;\n break;\n }\n }\n if (!(specialcase2)) {\n for (let i = 0; i < bbox1_key.length - 1; i++) {\n var sign_check = [];\n for (let j = 0; j < bbox2_key.length - 1; j++) {\n var x1 = bbox2[bbox2_key[j]].x\n var x2 = bbox2[bbox2_key[j + 1]].x\n var y1 = bbox2[bbox2_key[j]].y\n var y2 = bbox2[bbox2_key[j + 1]].y\n var cross_prod = (bbox1[bbox1_key[i]].x - x1) * (y2 - y1) - (bbox1[bbox1_key[i]].y - y1) * (x2 - x1);\n var sign = (cross_prod >= 0) ? true : false;\n sign_check.push(sign);\n }\n if (sign_check.length === 4 && (sign_check.every((val, index) => val === [true, true, true, true][index]) ||\n sign_check.every((val, index) => val === [false, false, false, false][index]))) { // similar signs indicate the coordinate is within another bbox\n intersection.push([bbox1[bbox1_key[i]].x.toFixed(6), bbox1[bbox1_key[i]].y.toFixed(6)]);\n //intersection.push([bbox1[bbox1_key[i]].x, bbox1[bbox1_key[i]].y]);\n }\n }\n }\n if (!(specialcase1)) {\n for (let i = 0; i < bbox2_key.length - 1; i++) { // similar purpose but for second bbox\n var sign_check = [];\n for (let j = 0; j < bbox1_key.length - 1; j++) {\n var x1 = bbox1[bbox1_key[j]].x\n var x2 = bbox1[bbox1_key[j + 1]].x\n var y1 = bbox1[bbox1_key[j]].y\n var y2 = bbox1[bbox1_key[j + 1]].y\n var cross_prod = (bbox2[bbox2_key[i]].x - x1) * (y2 - y1) - (bbox2[bbox2_key[i]].y - y1) * (x2 - x1);\n var sign = (cross_prod >= 0) ? true : false;\n sign_check.push(sign);\n }\n if (sign_check.length === 4 && (sign_check.every((val, index) => val === [true, true, true, true][index]) ||\n sign_check.every((val, index) => val === [false, false, false, false][index]))) {\n intersection.push([bbox2[bbox2_key[i]].x.toFixed(6), bbox2[bbox2_key[i]].y.toFixed(6)]);\n //intersection.push([bbox2[bbox2_key[i]].x, bbox2[bbox2_key[i]].y]);\n }\n }\n }\n intersection = intersection.filter((item, index, self) =>\n index === self.findIndex((t) => JSON.stringify(t) === JSON.stringify(item))\n );\n // sort intersection points\n if (intersection.length > 2) {\n var intersection_angle = [];\n var intersection_distance = [];\n\n let minIndex = intersection.reduce((minIdx, currentValue, currentIndex, array) => {\n if (currentValue[0] < array[minIdx][0]) {\n return currentIndex;\n } else if (currentValue[0] === array[minIdx][0]) {\n return currentValue[1] < array[minIdx][1] ? currentIndex : minIdx;\n } else {\n return minIdx;\n }\n }, 0);\n var temp_intersection = intersection.filter((_, index) => index !== minIndex);\n for (let i = 0; i < temp_intersection.length; i++) {\n intersection_angle.push(Math.atan2(temp_intersection[i][1] - intersection[minIndex][1], temp_intersection[i][0] - intersection[minIndex][0]));\n intersection_distance.push((temp_intersection[i][1] - intersection[minIndex][1]) ** 2 + (temp_intersection[i][0] - intersection[minIndex][0]) ** 2);\n }\n let combined = temp_intersection.map((point, index) => ({ // sort intersect coordinates based on angle and then distance\n point: point,\n angle: intersection_angle[index],\n distance: intersection_distance[index]\n }));\n combined.sort((a, b) => a.angle - b.angle || a.distance - b.distance);\n let sorted_intersection = combined.map(item => item.point);\n sorted_intersection.unshift(intersection[minIndex]);\n sorted_intersection.push(intersection[minIndex]);\n // shoelace algorithm to calculate the intersect area\n var area = 0;\n for (let i = 0; i < sorted_intersection.length - 1; i++) {\n area += ((sorted_intersection[i][0] * sorted_intersection[i + 1][1])\n - (sorted_intersection[i + 1][0] * sorted_intersection[i][1]))\n }\n return area;\n }\n return 0;\n }\n function calculateIOU(bbox1, bbox2, intersectArea) {\n if (intersectArea !== 0) {\n var lot_area1 = (bbox1[\"vertex1\"].x * bbox1[\"vertex2\"].y) + (bbox1[\"vertex2\"].x * bbox1[\"vertex3\"].y)\n + (bbox1[\"vertex3\"].x * bbox1[\"vertex4\"].y) + (bbox1[\"vertex4\"].x * bbox1[\"vertex_0\"].y)\n - ((bbox1[\"vertex2\"].x * bbox1[\"vertex1\"].y) + (bbox1[\"vertex3\"].x * bbox1[\"vertex2\"].y)\n + (bbox1[\"vertex4\"].x * bbox1[\"vertex3\"].y) + (bbox1[\"vertex_0\"].x * bbox1[\"vertex4\"].y));\n\n var lot_area2 = (bbox2[\"vertex1\"].x * bbox2[\"vertex2\"].y) + (bbox2[\"vertex2\"].x * bbox2[\"vertex3\"].y)\n + (bbox2[\"vertex3\"].x * bbox2[\"vertex4\"].y) + (bbox2[\"vertex4\"].x * bbox2[\"vertex_0\"].y)\n - ((bbox2[\"vertex2\"].x * bbox2[\"vertex1\"].y) + (bbox2[\"vertex3\"].x * bbox2[\"vertex2\"].y)\n + (bbox2[\"vertex4\"].x * bbox2[\"vertex3\"].y) + (bbox2[\"vertex_0\"].x * bbox2[\"vertex4\"].y));\n return intersectArea / (lot_area1 + lot_area2 - intersectArea)\n }\n return 0;\n }\n function calculateIOR(bbox1, intersectArea) {\n if (intersectArea !== 0) {\n var lot_area = (bbox1[\"vertex1\"].x * bbox1[\"vertex2\"].y) + (bbox1[\"vertex2\"].x * bbox1[\"vertex3\"].y)\n + (bbox1[\"vertex3\"].x * bbox1[\"vertex4\"].y) + (bbox1[\"vertex4\"].x * bbox1[\"vertex_0\"].y)\n - ((bbox1[\"vertex2\"].x * bbox1[\"vertex1\"].y) + (bbox1[\"vertex3\"].x * bbox1[\"vertex2\"].y)\n + (bbox1[\"vertex4\"].x * bbox1[\"vertex3\"].y) + (bbox1[\"vertex_0\"].x * bbox1[\"vertex4\"].y));\n return intersectArea / lot_area;\n }\n return 0;\n }\n\n var occupied = [];\n var current_index = -1;\n // initialize lots to no object\n for (let key1 in payload[\"lots\"]) {\n if (payload[\"lots\"].hasOwnProperty(key1)) {\n occupied.push({ \"occupied\": 0 });\n }\n }\n for (let key1 in payload[\"objects\"]) {\n current_index++;\n if (payload[\"objects\"].hasOwnProperty(key1)) { // find all lots within the object centroid distance threshold\n var objectCentroid = getBboxCentroid(payload[\"objects\"][key1]);\n var potential_region = [];\n for (let key2 in payload[\"lots\"]) {\n if (payload[\"lots\"].hasOwnProperty(key2)) {\n var regionCentroid = getBboxCentroid(payload[\"lots\"][key2]);\n var euclideanDistance = calculateEuclideanDistance(objectCentroid, regionCentroid);\n potential_region.push(euclideanDistance);\n }\n }\n // calculate IOU or IOR\n if (object_counter) {\n for (let i = 0; i < potential_region.length; i++) {\n if (potential_region[i] < distance_threshold) {\n var intersectRegion = calculateIntersectArea(payload[\"objects\"][key1], payload[\"lots\"][i]);\n var IOR = 0;\n if (intersection_type == \"region\") {\n IOR = calculateIOR(payload[\"lots\"][i], intersectRegion)\n }\n else if (intersection_type == \"object\") {\n IOR = calculateIOR(payload[\"objects\"][key1], intersectRegion)\n }\n if (IOR > intersection_threshold) {\n occupied[i][\"occupied\"] += 1;\n var keyData = Object.keys(payload[\"objects_extra\"][current_index]);\n var numberData = keyData.length;\n for (let j = 0; j < numberData; j++) {\n if (!occupied[i][\"extra\"]) {\n occupied[i][\"extra\"] = {};\n }\n if (!occupied[i][\"extra\"][keyData[j]]) {\n occupied[i][\"extra\"][keyData[j]] = [];\n }\n occupied[i][\"extra\"][keyData[j]].push(payload[\"objects_extra\"][current_index][keyData[j]]);\n }\n }\n }\n }\n }\n else { // consider the best lot only\n var best_region_index = potential_region.reduce((minIndex, currentValue, currentIndex, array) =>\n currentValue < array[minIndex] ? currentIndex : minIndex, 0);\n if (potential_region[best_region_index] < distance_threshold) {\n var intersectRegion = calculateIntersectArea(payload[\"objects\"][key1], payload[\"lots\"][best_region_index]);\n var IOR = 0;\n if (intersection_type == \"region\") {\n IOR = calculateIOR(payload[\"lots\"][best_region_index], intersectRegion)\n }\n else if (intersection_type == \"object\") {\n IOR = calculateIOR(payload[\"objects\"][key1], intersectRegion)\n }\n if (IOR > intersection_threshold) {\n occupied[best_region_index][\"occupied\"] += 1;\n var keyData = Object.keys(payload[\"objects_extra\"][current_index]);\n var numberData = keyData.length;\n for (let i = 0; i < numberData; i++) {\n if (!occupied[best_region_index][\"extra\"]) {\n occupied[best_region_index][\"extra\"] = {};\n }\n if (!occupied[best_region_index][\"extra\"][keyData[i]]) {\n occupied[best_region_index][\"extra\"][keyData[i]] = [];\n }\n occupied[best_region_index][\"extra\"][keyData[i]].push(payload[\"objects_extra\"][current_index][keyData[i]]);\n }\n }\n }\n }\n }\n }\n return occupied;\n}\n\n// turn into correct format\nfunction postprocess_output(occupied) {\n var results = {};\n\n for (let i = 0; i < occupied.length; i++) {\n results = initializeResults(results, occupied, i);\n previous_data = initializePreviousData(results, i);\n if (results[\"Region \" + (i + 1)].hasOwnProperty(\"id\") || previous_data[\"Region \" + (i + 1)].hasOwnProperty(\"id\")) {\n results = handleObjectWithID(results, i);\n }\n else if (results[\"Region \" + (i + 1)][\"occupied\"] > 0) {\n results = handleOccupiedRegion(results, i);\n }\n else if (previous_data[\"Region \" + (i + 1)][\"entry_time\"]) {\n results = handleNotOccupiedRegion(results, i);\n }\n }\n return results;\n}\n\nfunction initializeResults(results, occupied, i) {\n const regionKey = \"Region \" + (i + 1);\n\n if (!results[regionKey]) {\n results[regionKey] = {};\n }\n results[regionKey][\"video_id\"] = video_id;\n results[regionKey][\"region_id\"] = (region_id != -1) ? region_id : i + 1;\n\n if (occupied[i].hasOwnProperty(\"occupied\")) {\n results[regionKey][\"occupied\"] = occupied[i].occupied;\n }\n\n if (occupied[i].hasOwnProperty(\"extra\")) {\n for (let key in occupied[i].extra) {\n results[regionKey][key] = occupied[i].extra[key];\n }\n }\n if (results[regionKey].hasOwnProperty(\"id\") || (previous_data && previous_data.hasOwnProperty(regionKey) && previous_data[regionKey].hasOwnProperty(\"id\"))) {\n results[regionKey][\"actual_entry_time\"] = [];\n results[regionKey][\"actual_dwell_time\"] = [];\n results[regionKey][\"entry_time\"] = [];\n results[regionKey][\"dwell_time\"] = [];\n results[regionKey][\"exit_time\"] = [];\n }\n else {\n results[\"Region \" + (i + 1)][\"actual_entry_time\"] = \"\";\n results[\"Region \" + (i + 1)][\"actual_dwell_time\"] = \"\";\n results[\"Region \" + (i + 1)][\"entry_time\"] = \"\";\n results[\"Region \" + (i + 1)][\"dwell_time\"] = \"\";\n results[\"Region \" + (i + 1)][\"exit_time\"] = \"\";\n results[\"Region \" + (i + 1)][\"occupancy_check\"] = 0;\n }\n return results;\n}\n\nfunction initializePreviousData(results, i) {\n const regionKey = \"Region \" + (i + 1);\n\n // Ensure previous_data is set up for this region.\n if (!previous_data) {\n previous_data = {};\n }\n if (results[regionKey].hasOwnProperty(\"id\")) {\n if (!previous_data[regionKey]) {\n previous_data[regionKey] = {};\n previous_data[regionKey][\"id\"] = [];\n previous_data[regionKey][\"actual_entry_time\"] = [];\n previous_data[regionKey][\"actual_dwell_time\"] = [];\n previous_data[regionKey][\"entry_time\"] = [];\n previous_data[regionKey][\"dwell_time\"] = [];\n previous_data[regionKey][\"exit_time\"] = [];\n }\n }\n else {\n if (!previous_data.hasOwnProperty(regionKey)) {\n previous_data[regionKey] = results[regionKey];\n previous_data[regionKey][\"actual_entry_time\"] = \"\";\n previous_data[regionKey][\"actual_dwell_time\"] = \"\";\n previous_data[regionKey][\"entry_time\"] = \"\";\n previous_data[regionKey][\"dwell_time\"] = \"\";\n previous_data[regionKey][\"exit_time\"] = \"\";\n previous_data[regionKey][\"occupancy_check\"] = 0;\n }\n }\n\n return previous_data;\n}\n\nfunction handleObjectWithID(results, i) {\n const regionKey = \"Region \" + (i + 1);\n const resultIds = results[regionKey][\"id\"];\n const previousDataIds = previous_data[regionKey][\"id\"];\n\n if (!resultIds && !previousDataIds) {\n return results;\n }\n\n if (resultIds) {\n for (let j = 0; j < resultIds.length; j++) {\n // Check existing id data\n let prevIndex = -1;\n\n if (previous_data[regionKey][\"id\"] && previous_data[regionKey][\"id\"].length > 0) {\n prevIndex = previous_data[regionKey][\"id\"].indexOf(resultIds[j]);\n }\n\n if (prevIndex === -1) {\n results[regionKey][\"actual_entry_time\"].push(msg.nodered_timestamp);\n results[regionKey][\"entry_time\"].push(msg.object_timestamp);\n results[regionKey][\"actual_dwell_time\"].push(\"00:00:00\");\n results[regionKey][\"dwell_time\"].push(0);\n results[regionKey][\"exit_time\"].push(\"\");\n } else {\n results[regionKey][\"actual_entry_time\"].push(previous_data[regionKey][\"actual_entry_time\"][prevIndex]);\n results[regionKey][\"entry_time\"].push(previous_data[regionKey][\"entry_time\"][prevIndex]);\n\n // Calculate dwell time.\n const dwell_timestamp = (msg.object_timestamp - results[regionKey][\"entry_time\"][j]);\n const dwell_hours = Math.floor(dwell_timestamp / 3600).toString().padStart(2, \"0\");\n const dwell_mins = Math.floor((dwell_timestamp / 60) % 60).toString().padStart(2, \"0\");\n const dwell_seconds = Math.floor(dwell_timestamp % 60).toString().padStart(2, \"0\");\n results[regionKey][\"dwell_time\"].push(dwell_timestamp);\n results[regionKey][\"actual_dwell_time\"].push(dwell_hours + \":\" + dwell_mins + \":\" + dwell_seconds);\n results[regionKey][\"exit_time\"].push(\"\");\n }\n }\n }\n\n if (previousDataIds) {\n for (let j = 0; j < previousDataIds.length; j++) {\n let prevIndex = -1;\n\n if (!results[regionKey][\"id\"]) {\n results[regionKey] = previous_data[regionKey];\n results[regionKey][\"occupied\"] = 0;\n break;\n }\n else {\n if (!results[regionKey][\"dwell_time\"].some(val => val >= stop_duration)) {\n results[regionKey][\"occupied\"] = 0;\n }\n\n prevIndex = results[regionKey][\"id\"].indexOf(previousDataIds[j]);\n if (prevIndex === -1) {\n let keyList = Object.keys(previous_data[regionKey]);\n for (let k = 0; k < keyList.length; k++) {\n if (keyList[k] == \"video_id\" || keyList[k] == \"region_id\" || keyList[k] == \"occupied\") {\n continue;\n }\n results[regionKey][keyList[k]].push(previous_data[regionKey][keyList[k]][j]);\n }\n }\n }\n }\n }\n return results;\n}\n\nfunction handleOccupiedRegion(results, i) {\n const regionKey = \"Region \" + (i + 1);\n if (previous_data[regionKey][\"entry_time\"] == \"\") {\n results[regionKey][\"actual_entry_time\"] = msg.nodered_timestamp;\n results[regionKey][\"entry_time\"] = msg.object_timestamp;\n }\n else {\n if (previous_data[regionKey][\"occupancy_check\"] >= occupancy_check_duration) {\n results[regionKey][\"actual_dwell_time\"] = \"00:00:00\";\n results[regionKey][\"dwell_time\"] = 0;\n results[regionKey][\"actual_entry_time\"] = msg.nodered_timestamp;\n results[regionKey][\"entry_time\"] = msg.object_timestamp;\n }\n else {\n results[regionKey][\"actual_entry_time\"] = previous_data[regionKey][\"actual_entry_time\"];\n results[regionKey][\"entry_time\"] = previous_data[regionKey][\"entry_time\"];\n }\n\n var dwell_timestamp = msg.object_timestamp - results[regionKey][\"entry_time\"];\n var dwell_hours = Math.floor(dwell_timestamp / 3600).toString();\n dwell_hours = dwell_hours.toString().length == 1 ? dwell_hours.toString().padStart(2, \"0\") : dwell_hours.toString();\n var dwell_mins = Math.floor((dwell_timestamp / 60) % 60).toString().padStart(2, \"0\");\n var dwell_seconds = Math.floor(dwell_timestamp % 60).toString().padStart(2, \"0\");\n\n results[regionKey][\"dwell_time\"] = dwell_timestamp;\n results[regionKey][\"actual_dwell_time\"] = dwell_hours + \":\" + dwell_mins + \":\" + dwell_seconds;\n results[regionKey][\"occupancy_check\"] = 0;\n }\n\n return results;\n}\n\nfunction handleNotOccupiedRegion(results, i) {\n const regionKey = \"Region \" + (i + 1);\n\n results[regionKey][\"actual_entry_time\"] = previous_data[regionKey][\"actual_entry_time\"];\n results[regionKey][\"entry_time\"] = previous_data[regionKey][\"entry_time\"];\n results[regionKey][\"actual_dwell_time\"] = previous_data[regionKey][\"actual_dwell_time\"];\n results[regionKey][\"dwell_time\"] = previous_data[regionKey][\"dwell_time\"];\n results[regionKey][\"occupancy_check\"] = previous_data[regionKey][\"occupancy_check\"] + 1;\n\n if (previous_data[regionKey][\"exit_time\"]) {\n results[regionKey][\"exit_time\"] = previous_data[regionKey][\"exit_time\"];\n }\n else {\n results[regionKey][\"exit_time\"] = msg.nodered_timestamp;\n }\n return results;\n}\n\nfunction main(payload, distance_threshold = 100, intersection_type = \"region\", intersection_threshold = 0.5, object_counter = false) {\n var results = preprocess_bboxes(payload);\n var occupied = euclidean_iou_function(results, distance_threshold, intersection_type, intersection_threshold, object_counter);\n var out = postprocess_output(occupied);\n flow.set(previous_data_name, out);\n return out;\n}\n\n// user-defined parameters\nvar distance_threshold = flow.get(\"distance_threshold\");\nvar intersection_type = flow.get(\"intersection_type\");\nvar intersection_threshold = flow.get(\"intersection_threshold\");\nvar object_counter = flow.get(\"object_counter\");\n\nmsg.payload = main(payload, distance_threshold, intersection_type, intersection_threshold, object_counter);\nflow.set(detection_name, msg);\nreturn msg;", + "func": "let region_name = \"predefined_regions_4\";\nlet detection_name = \"object_detection_4\";\nlet previous_data_name = \"previous_data_4\";\nlet occupancy_check_duration = 60;\nlet min_entry_frames = 30;\nlet video_id = 4;\nlet region_id = -1; //Recommend to set this only when there is 1 predefined region in the video\n\nvar payload = msg.payload;\nvar previous_data = flow.get(previous_data_name);\nvar stop_duration = flow.get(\"stop_duration\");\n\n// preparing bboxes\nfunction preprocess_bboxes(payload) {\n function getObjectData(object_data) {\n var current_object_data = {};\n if (object_data.hasOwnProperty(\"type\")) {\n current_object_data[\"type\"] = object_data[\"type\"];\n }\n if (object_data.hasOwnProperty(\"color\")) {\n current_object_data[\"color\"] = object_data[\"color\"];\n }\n if (object_data.hasOwnProperty(\"license_plate\")) {\n current_object_data[\"license_plate\"] = object_data[\"license_plate\"];\n }\n if (object_data.hasOwnProperty(\"id\")) {\n current_object_data[\"id\"] = object_data[\"id\"];\n }\n if (object_data.hasOwnProperty(\"roi_type\")) {\n current_object_data[\"roi_type\"] = object_data[\"roi_type\"];\n }\n return current_object_data;\n }\n function getLotBbox(lot_data) {\n return { \"vertex1\": lot_data.vertex1, \"vertex2\": lot_data.vertex2, \"vertex3\": lot_data.vertex3, \"vertex4\": lot_data.vertex4, \"vertex_0\": lot_data.vertex1 };\n }\n\n var results = {};\n var objects = [];\n var objects_extra = [];\n var lots = [];\n // get all objects bboxes\n for (let key in payload) {\n if (payload.hasOwnProperty(key)) {\n var objectData = getObjectData(payload[key]);\n if (objectData !== null) {\n objects_extra.push(\n objectData\n );\n objects.push(\n payload[key][\"bbox\"]\n );\n }\n }\n }\n // get all lots bboxes\n var predefined_regions = flow.get(region_name);\n var keys = Object.keys(predefined_regions);\n\n for (let i = 0; i < keys.length; i++) {\n if (predefined_regions.hasOwnProperty(keys[i]) && keys[i] != \"_msgid\") {\n lots.push(\n getLotBbox(predefined_regions[keys[i]])\n );\n }\n }\n\n results[\"objects\"] = objects;\n results[\"objects_extra\"] = objects_extra;\n results[\"lots\"] = lots;\n return results;\n}\n\n// euclidean distance and iou check\nfunction euclidean_iou_function(payload, distance_threshold = 100, intersection_type = \"region\", intersection_threshold = 0.5, object_counter = false) {\n function getBboxCentroid(bbox) {\n var centroid_x = (bbox[\"vertex1\"].x + bbox[\"vertex2\"].x + bbox[\"vertex3\"].x + bbox[\"vertex4\"].x) / 4;\n var centroid_y = (bbox[\"vertex1\"].y + bbox[\"vertex2\"].y + bbox[\"vertex3\"].y + bbox[\"vertex4\"].y) / 4;\n return [centroid_x, centroid_y];\n }\n function calculateEuclideanDistance(centroid1, centroid2) {\n return Math.sqrt((centroid2[0] - centroid1[0]) ** 2 + (centroid2[1] - centroid1[1]) ** 2);\n }\n function calculateIntersectArea(bbox1, bbox2) { // objects, lots\n var bbox1_key = Object.keys(bbox1)\n var bbox2_key = Object.keys(bbox2)\n var intersection = [];\n\n for (let i = 0; i < bbox1_key.length - 1; i++) {\n for (let j = 0; j < bbox2_key.length - 1; j++) {\n var A1 = bbox1[bbox1_key[i + 1]].y - bbox1[bbox1_key[i]].y; // Ax + By + C = 0\n var B1 = bbox1[bbox1_key[i]].x - bbox1[bbox1_key[i + 1]].x;\n var C1 = - A1 * bbox1[bbox1_key[i]].x - B1 * bbox1[bbox1_key[i]].y;\n\n var A2 = bbox2[bbox2_key[j + 1]].y - bbox2[bbox2_key[j]].y;\n var B2 = bbox2[bbox2_key[j]].x - bbox2[bbox2_key[j + 1]].x;\n var C2 = - A2 * bbox2[bbox2_key[j]].x - B2 * bbox2[bbox2_key[j]].y;\n\n // parallel lines case\n if (A1 * B2 == A2 * B1) {\n // coincide case\n if ((A1 * C2 == A2 * C1) && (B1 * C2 == B2 * C1)) {\n if (B1 == 0 && A1 == 0) { // one point bbox case\n intersection.push([bbox1[bbox1_key[i]].x.toFixed(6), bbox1[bbox1_key[i]].y.toFixed(6)]);\n }\n else if (B2 == 0 && A2 == 0) { // one point bbox case\n intersection.push([bbox2[bbox2_key[i]].x.toFixed(6), bbox2[bbox2_key[i]].y.toFixed(6)]);\n }\n else if (B1 == 0) { // vertical lines case\n var y_min1 = (bbox1[bbox1_key[i]].y < bbox1[bbox1_key[i + 1]].y) ? bbox1[bbox1_key[i]].y : bbox1[bbox1_key[i + 1]].y;\n var y_max1 = (bbox1[bbox1_key[i]].y < bbox1[bbox1_key[i + 1]].y) ? bbox1[bbox1_key[i + 1]].y : bbox1[bbox1_key[i]].y;\n var y_min2 = (bbox2[bbox2_key[i]].y < bbox2[bbox2_key[i + 1]].y) ? bbox2[bbox2_key[i]].y : bbox2[bbox2_key[i + 1]].y;\n var y_max2 = (bbox2[bbox2_key[i]].y < bbox2[bbox2_key[i + 1]].y) ? bbox2[bbox2_key[i + 1]].y : bbox2[bbox2_key[i]].y;\n if ((y_min1 < y_max2) && (y_min2 < y_max1)) {\n var y_min = (y_min1 < y_min2) ? y_min2 : y_min1;\n var y_max = (y_max1 < y_max2) ? y_max1 : y_max2;\n intersection.push([bbox1[bbox1_key[i]].x.toFixed(6), y_min.toFixed(6)]);\n intersection.push([bbox1[bbox1_key[i]].x.toFixed(6), y_max.toFixed(6)]);\n }\n }\n else if (A1 == 0) { // horizontal lines case\n var x_min1 = (bbox1[bbox1_key[i]].x < bbox1[bbox1_key[i + 1]].x) ? bbox1[bbox1_key[i]].x : bbox1[bbox1_key[i + 1]].x;\n var x_max1 = (bbox1[bbox1_key[i]].x < bbox1[bbox1_key[i + 1]].x) ? bbox1[bbox1_key[i + 1]].x : bbox1[bbox1_key[i]].x;\n var x_min2 = (bbox2[bbox2_key[i]].x < bbox2[bbox2_key[i + 1]].x) ? bbox2[bbox2_key[i]].x : bbox2[bbox2_key[i + 1]].x;\n var x_max2 = (bbox2[bbox2_key[i]].x < bbox2[bbox2_key[i + 1]].x) ? bbox2[bbox2_key[i + 1]].x : bbox2[bbox2_key[i]].x;\n if ((x_min1 < x_max2) && (x_min2 < x_max1)) {\n var x_min = (x_min1 < x_min2) ? x_min2 : x_min1;\n var x_max = (x_max1 < x_max2) ? x_max1 : x_max2;\n intersection.push([x_min.toFixed(6), bbox1[bbox1_key[i]].y.toFixed(6)]);\n intersection.push([x_max.toFixed(6), bbox1[bbox1_key[i]].y.toFixed(6)]);\n }\n }\n else {\n var x_min1 = (bbox1[bbox1_key[i]].x < bbox1[bbox1_key[i + 1]].x) ? bbox1[bbox1_key[i]].x : bbox1[bbox1_key[i + 1]].x;\n var x_max1 = (bbox1[bbox1_key[i]].x < bbox1[bbox1_key[i + 1]].x) ? bbox1[bbox1_key[i + 1]].x : bbox1[bbox1_key[i]].x;\n var x_min2 = (bbox2[bbox2_key[i]].x < bbox2[bbox2_key[i + 1]].x) ? bbox2[bbox2_key[i]].x : bbox2[bbox2_key[i + 1]].x;\n var x_max2 = (bbox2[bbox2_key[i]].x < bbox2[bbox2_key[i + 1]].x) ? bbox2[bbox2_key[i + 1]].x : bbox2[bbox2_key[i]].x;\n if ((x_min1 < x_max2) && (x_min2 < x_max1) && (y_min1 < y_max2) && (y_min2 < y_max1)) {\n var x_min = (x_min1 < x_min2) ? x_min2 : x_min1;\n var x_max = (x_max1 < x_max2) ? x_max1 : x_max2;\n var xY_min = (-A1 * x_min - C1) / B1\n var xY_max = (-A1 * x_max - C1) / B1\n intersection.push([x_min.toFixed(6), xY_min.toFixed(6)]);\n intersection.push([x_max.toFixed(6), xY_max.toFixed(6)]);\n }\n }\n }\n }\n else {\n // get intersection coordinates\n var x1 = bbox1[bbox1_key[i]].x;\n var x2 = bbox1[bbox1_key[i + 1]].x;\n var x3 = bbox2[bbox2_key[j]].x;\n var x4 = bbox2[bbox2_key[j + 1]].x;\n var y1 = bbox1[bbox1_key[i]].y;\n var y2 = bbox1[bbox1_key[i + 1]].y;\n var y3 = bbox2[bbox2_key[j]].y;\n var y4 = bbox2[bbox2_key[j + 1]].y;\n\n var den = (x2 - x1) * (y3 - y4) - (y2 - y1) * (x3 - x4);\n var t = ((x3 - x1) * (y3 - y4) - (y3 - y1) * (x3 - x4)) / den;\n var u = ((x3 - x1) * (y1 - y2) - (y3 - y1) * (x1 - x2)) / den;\n if ((t >= 0) && (t <= 1) && (u >= 0) && (u <= 1)) { // intersect happens if this condition is fulfilled\n var ix = x1 + t * (x2 - x1);\n var iy = y1 + t * (y2 - y1);\n intersection.push([ix.toFixed(6), iy.toFixed(6)]);\n }\n }\n }\n }\n // get clipped coordinates\n var specialcase1 = true;\n var specialcase2 = false;\n var temp1 = bbox1[bbox1_key[0]];\n var temp2 = bbox2[bbox2_key[0]];\n for (let i = 1; i < bbox1_key.length - 1; i++) { // special case\n if (temp1 !== bbox1[bbox1_key[i]]) {\n specialcase1 = false;\n break;\n }\n }\n for (let i = 1; i < bbox2_key.length - 1; i++) {\n if (temp2 !== bbox2[bbox2_key[i]]) {\n specialcase2 = false;\n break;\n }\n }\n if (!(specialcase2)) {\n for (let i = 0; i < bbox1_key.length - 1; i++) {\n var sign_check = [];\n for (let j = 0; j < bbox2_key.length - 1; j++) {\n var x1 = bbox2[bbox2_key[j]].x\n var x2 = bbox2[bbox2_key[j + 1]].x\n var y1 = bbox2[bbox2_key[j]].y\n var y2 = bbox2[bbox2_key[j + 1]].y\n var cross_prod = (bbox1[bbox1_key[i]].x - x1) * (y2 - y1) - (bbox1[bbox1_key[i]].y - y1) * (x2 - x1);\n var sign = (cross_prod >= 0) ? true : false;\n sign_check.push(sign);\n }\n if (sign_check.length === 4 && (sign_check.every((val, index) => val === [true, true, true, true][index]) ||\n sign_check.every((val, index) => val === [false, false, false, false][index]))) { // similar signs indicate the coordinate is within another bbox\n intersection.push([bbox1[bbox1_key[i]].x.toFixed(6), bbox1[bbox1_key[i]].y.toFixed(6)]);\n //intersection.push([bbox1[bbox1_key[i]].x, bbox1[bbox1_key[i]].y]);\n }\n }\n }\n if (!(specialcase1)) {\n for (let i = 0; i < bbox2_key.length - 1; i++) { // similar purpose but for second bbox\n var sign_check = [];\n for (let j = 0; j < bbox1_key.length - 1; j++) {\n var x1 = bbox1[bbox1_key[j]].x\n var x2 = bbox1[bbox1_key[j + 1]].x\n var y1 = bbox1[bbox1_key[j]].y\n var y2 = bbox1[bbox1_key[j + 1]].y\n var cross_prod = (bbox2[bbox2_key[i]].x - x1) * (y2 - y1) - (bbox2[bbox2_key[i]].y - y1) * (x2 - x1);\n var sign = (cross_prod >= 0) ? true : false;\n sign_check.push(sign);\n }\n if (sign_check.length === 4 && (sign_check.every((val, index) => val === [true, true, true, true][index]) ||\n sign_check.every((val, index) => val === [false, false, false, false][index]))) {\n intersection.push([bbox2[bbox2_key[i]].x.toFixed(6), bbox2[bbox2_key[i]].y.toFixed(6)]);\n //intersection.push([bbox2[bbox2_key[i]].x, bbox2[bbox2_key[i]].y]);\n }\n }\n }\n intersection = intersection.filter((item, index, self) =>\n index === self.findIndex((t) => JSON.stringify(t) === JSON.stringify(item))\n );\n // sort intersection points\n if (intersection.length > 2) {\n var intersection_angle = [];\n var intersection_distance = [];\n\n let minIndex = intersection.reduce((minIdx, currentValue, currentIndex, array) => {\n if (currentValue[0] < array[minIdx][0]) {\n return currentIndex;\n } else if (currentValue[0] === array[minIdx][0]) {\n return currentValue[1] < array[minIdx][1] ? currentIndex : minIdx;\n } else {\n return minIdx;\n }\n }, 0);\n var temp_intersection = intersection.filter((_, index) => index !== minIndex);\n for (let i = 0; i < temp_intersection.length; i++) {\n intersection_angle.push(Math.atan2(temp_intersection[i][1] - intersection[minIndex][1], temp_intersection[i][0] - intersection[minIndex][0]));\n intersection_distance.push((temp_intersection[i][1] - intersection[minIndex][1]) ** 2 + (temp_intersection[i][0] - intersection[minIndex][0]) ** 2);\n }\n let combined = temp_intersection.map((point, index) => ({ // sort intersect coordinates based on angle and then distance\n point: point,\n angle: intersection_angle[index],\n distance: intersection_distance[index]\n }));\n combined.sort((a, b) => a.angle - b.angle || a.distance - b.distance);\n let sorted_intersection = combined.map(item => item.point);\n sorted_intersection.unshift(intersection[minIndex]);\n sorted_intersection.push(intersection[minIndex]);\n // shoelace algorithm to calculate the intersect area\n var area = 0;\n for (let i = 0; i < sorted_intersection.length - 1; i++) {\n area += ((sorted_intersection[i][0] * sorted_intersection[i + 1][1])\n - (sorted_intersection[i + 1][0] * sorted_intersection[i][1]))\n }\n return area;\n }\n return 0;\n }\n function calculateIOU(bbox1, bbox2, intersectArea) {\n if (intersectArea !== 0) {\n var lot_area1 = (bbox1[\"vertex1\"].x * bbox1[\"vertex2\"].y) + (bbox1[\"vertex2\"].x * bbox1[\"vertex3\"].y)\n + (bbox1[\"vertex3\"].x * bbox1[\"vertex4\"].y) + (bbox1[\"vertex4\"].x * bbox1[\"vertex_0\"].y)\n - ((bbox1[\"vertex2\"].x * bbox1[\"vertex1\"].y) + (bbox1[\"vertex3\"].x * bbox1[\"vertex2\"].y)\n + (bbox1[\"vertex4\"].x * bbox1[\"vertex3\"].y) + (bbox1[\"vertex_0\"].x * bbox1[\"vertex4\"].y));\n\n var lot_area2 = (bbox2[\"vertex1\"].x * bbox2[\"vertex2\"].y) + (bbox2[\"vertex2\"].x * bbox2[\"vertex3\"].y)\n + (bbox2[\"vertex3\"].x * bbox2[\"vertex4\"].y) + (bbox2[\"vertex4\"].x * bbox2[\"vertex_0\"].y)\n - ((bbox2[\"vertex2\"].x * bbox2[\"vertex1\"].y) + (bbox2[\"vertex3\"].x * bbox2[\"vertex2\"].y)\n + (bbox2[\"vertex4\"].x * bbox2[\"vertex3\"].y) + (bbox2[\"vertex_0\"].x * bbox2[\"vertex4\"].y));\n return intersectArea / (lot_area1 + lot_area2 - intersectArea)\n }\n return 0;\n }\n function calculateIOR(bbox1, intersectArea) {\n if (intersectArea !== 0) {\n var lot_area = (bbox1[\"vertex1\"].x * bbox1[\"vertex2\"].y) + (bbox1[\"vertex2\"].x * bbox1[\"vertex3\"].y)\n + (bbox1[\"vertex3\"].x * bbox1[\"vertex4\"].y) + (bbox1[\"vertex4\"].x * bbox1[\"vertex_0\"].y)\n - ((bbox1[\"vertex2\"].x * bbox1[\"vertex1\"].y) + (bbox1[\"vertex3\"].x * bbox1[\"vertex2\"].y)\n + (bbox1[\"vertex4\"].x * bbox1[\"vertex3\"].y) + (bbox1[\"vertex_0\"].x * bbox1[\"vertex4\"].y));\n return intersectArea / lot_area;\n }\n return 0;\n }\n\n var occupied = [];\n var current_index = -1;\n // initialize lots to no object\n for (let key1 in payload[\"lots\"]) {\n if (payload[\"lots\"].hasOwnProperty(key1)) {\n occupied.push({ \"occupied\": 0 });\n }\n }\n for (let key1 in payload[\"objects\"]) {\n current_index++;\n if (payload[\"objects\"].hasOwnProperty(key1)) { // find all lots within the object centroid distance threshold\n var objectCentroid = getBboxCentroid(payload[\"objects\"][key1]);\n var potential_region = [];\n for (let key2 in payload[\"lots\"]) {\n if (payload[\"lots\"].hasOwnProperty(key2)) {\n var regionCentroid = getBboxCentroid(payload[\"lots\"][key2]);\n var euclideanDistance = calculateEuclideanDistance(objectCentroid, regionCentroid);\n potential_region.push(euclideanDistance);\n }\n }\n // calculate IOU or IOR\n if (object_counter) {\n for (let i = 0; i < potential_region.length; i++) {\n if (potential_region[i] < distance_threshold) {\n var intersectRegion = calculateIntersectArea(payload[\"objects\"][key1], payload[\"lots\"][i]);\n var IOR = 0;\n if (intersection_type == \"region\") {\n IOR = calculateIOR(payload[\"lots\"][i], intersectRegion)\n }\n else if (intersection_type == \"object\") {\n IOR = calculateIOR(payload[\"objects\"][key1], intersectRegion)\n }\n if (IOR > intersection_threshold) {\n occupied[i][\"occupied\"] += 1;\n var keyData = Object.keys(payload[\"objects_extra\"][current_index]);\n var numberData = keyData.length;\n for (let j = 0; j < numberData; j++) {\n if (!occupied[i][\"extra\"]) {\n occupied[i][\"extra\"] = {};\n }\n if (!occupied[i][\"extra\"][keyData[j]]) {\n occupied[i][\"extra\"][keyData[j]] = [];\n }\n occupied[i][\"extra\"][keyData[j]].push(payload[\"objects_extra\"][current_index][keyData[j]]);\n }\n }\n }\n }\n }\n else { // consider the best lot only\n var best_region_index = potential_region.reduce((minIndex, currentValue, currentIndex, array) =>\n currentValue < array[minIndex] ? currentIndex : minIndex, 0);\n if (potential_region[best_region_index] < distance_threshold) {\n var intersectRegion = calculateIntersectArea(payload[\"objects\"][key1], payload[\"lots\"][best_region_index]);\n var IOR = 0;\n if (intersection_type == \"region\") {\n IOR = calculateIOR(payload[\"lots\"][best_region_index], intersectRegion)\n }\n else if (intersection_type == \"object\") {\n IOR = calculateIOR(payload[\"objects\"][key1], intersectRegion)\n }\n if (IOR > intersection_threshold) {\n occupied[best_region_index][\"occupied\"] += 1;\n var keyData = Object.keys(payload[\"objects_extra\"][current_index]);\n var numberData = keyData.length;\n for (let i = 0; i < numberData; i++) {\n if (!occupied[best_region_index][\"extra\"]) {\n occupied[best_region_index][\"extra\"] = {};\n }\n if (!occupied[best_region_index][\"extra\"][keyData[i]]) {\n occupied[best_region_index][\"extra\"][keyData[i]] = [];\n }\n occupied[best_region_index][\"extra\"][keyData[i]].push(payload[\"objects_extra\"][current_index][keyData[i]]);\n }\n }\n }\n }\n }\n }\n return occupied;\n}\n\n// turn into correct format\nfunction postprocess_output(occupied) {\n var results = {};\n\n for (let i = 0; i < occupied.length; i++) {\n results = initializeResults(results, occupied, i);\n previous_data = initializePreviousData(results, i);\n if (results[\"Region \" + (i + 1)].hasOwnProperty(\"id\") || previous_data[\"Region \" + (i + 1)].hasOwnProperty(\"id\")) {\n results = handleObjectWithID(results, i);\n }\n else if (results[\"Region \" + (i + 1)][\"occupied\"] > 0) {\n results = handleOccupiedRegion(results, i);\n }\n else if (previous_data[\"Region \" + (i + 1)][\"entry_time\"]) {\n results = handleNotOccupiedRegion(results, i);\n }\n }\n return results;\n}\n\nfunction initializeResults(results, occupied, i) {\n const regionKey = \"Region \" + (i + 1);\n\n if (!results[regionKey]) {\n results[regionKey] = {};\n }\n results[regionKey][\"video_id\"] = video_id;\n results[regionKey][\"region_id\"] = (region_id != -1) ? region_id : i + 1;\n\n if (occupied[i].hasOwnProperty(\"occupied\")) {\n results[regionKey][\"occupied\"] = occupied[i].occupied;\n }\n\n if (occupied[i].hasOwnProperty(\"extra\")) {\n for (let key in occupied[i].extra) {\n results[regionKey][key] = occupied[i].extra[key];\n }\n }\n if (results[regionKey].hasOwnProperty(\"id\") || (previous_data && previous_data.hasOwnProperty(regionKey) && previous_data[regionKey].hasOwnProperty(\"id\"))) {\n results[regionKey][\"actual_entry_time\"] = [];\n results[regionKey][\"actual_dwell_time\"] = [];\n results[regionKey][\"entry_time\"] = [];\n results[regionKey][\"dwell_time\"] = [];\n results[regionKey][\"exit_time\"] = [];\n }\n else {\n results[\"Region \" + (i + 1)][\"actual_entry_time\"] = \"\";\n results[\"Region \" + (i + 1)][\"actual_dwell_time\"] = \"\";\n results[\"Region \" + (i + 1)][\"entry_time\"] = \"\";\n results[\"Region \" + (i + 1)][\"dwell_time\"] = \"\";\n results[\"Region \" + (i + 1)][\"exit_time\"] = \"\";\n results[\"Region \" + (i + 1)][\"occupancy_check\"] = 0;\n }\n return results;\n}\n\nfunction initializePreviousData(results, i) {\n const regionKey = \"Region \" + (i + 1);\n\n // Ensure previous_data is set up for this region.\n if (!previous_data) {\n previous_data = {};\n }\n if (results[regionKey].hasOwnProperty(\"id\")) {\n if (!previous_data[regionKey]) {\n previous_data[regionKey] = {};\n previous_data[regionKey][\"id\"] = [];\n previous_data[regionKey][\"actual_entry_time\"] = [];\n previous_data[regionKey][\"actual_dwell_time\"] = [];\n previous_data[regionKey][\"entry_time\"] = [];\n previous_data[regionKey][\"dwell_time\"] = [];\n previous_data[regionKey][\"exit_time\"] = [];\n }\n }\n else {\n if (!previous_data.hasOwnProperty(regionKey)) {\n previous_data[regionKey] = results[regionKey];\n previous_data[regionKey][\"actual_entry_time\"] = \"\";\n previous_data[regionKey][\"actual_dwell_time\"] = \"\";\n previous_data[regionKey][\"entry_time\"] = \"\";\n previous_data[regionKey][\"dwell_time\"] = \"\";\n previous_data[regionKey][\"exit_time\"] = \"\";\n previous_data[regionKey][\"occupancy_check\"] = 0;\n }\n }\n\n return previous_data;\n}\n\nfunction handleObjectWithID(results, i) {\n const regionKey = \"Region \" + (i + 1);\n const resultIds = results[regionKey][\"id\"];\n const previousDataIds = previous_data[regionKey][\"id\"];\n\n if (!resultIds && !previousDataIds) {\n return results;\n }\n\n if (resultIds) {\n for (let j = 0; j < resultIds.length; j++) {\n // Check existing id data\n let prevIndex = -1;\n\n if (previous_data[regionKey][\"id\"] && previous_data[regionKey][\"id\"].length > 0) {\n prevIndex = previous_data[regionKey][\"id\"].indexOf(resultIds[j]);\n }\n\n if (prevIndex === -1) {\n results[regionKey][\"actual_entry_time\"].push(msg.nodered_timestamp);\n results[regionKey][\"entry_time\"].push(msg.object_timestamp);\n results[regionKey][\"actual_dwell_time\"].push(\"00:00:00\");\n results[regionKey][\"dwell_time\"].push(0);\n results[regionKey][\"exit_time\"].push(\"\");\n } else {\n results[regionKey][\"actual_entry_time\"].push(previous_data[regionKey][\"actual_entry_time\"][prevIndex]);\n results[regionKey][\"entry_time\"].push(previous_data[regionKey][\"entry_time\"][prevIndex]);\n\n // Calculate dwell time.\n const dwell_timestamp = (msg.object_timestamp - results[regionKey][\"entry_time\"][j]);\n const dwell_hours = Math.floor(dwell_timestamp / 3600).toString().padStart(2, \"0\");\n const dwell_mins = Math.floor((dwell_timestamp / 60) % 60).toString().padStart(2, \"0\");\n const dwell_seconds = Math.floor(dwell_timestamp % 60).toString().padStart(2, \"0\");\n results[regionKey][\"dwell_time\"].push(dwell_timestamp);\n results[regionKey][\"actual_dwell_time\"].push(dwell_hours + \":\" + dwell_mins + \":\" + dwell_seconds);\n results[regionKey][\"exit_time\"].push(\"\");\n }\n }\n }\n\n if (previousDataIds) {\n for (let j = 0; j < previousDataIds.length; j++) {\n let prevIndex = -1;\n\n if (!results[regionKey][\"id\"]) {\n results[regionKey] = previous_data[regionKey];\n results[regionKey][\"occupied\"] = 0;\n break;\n }\n else {\n if (!results[regionKey][\"dwell_time\"].some(val => val >= stop_duration)) {\n results[regionKey][\"occupied\"] = 0;\n }\n\n prevIndex = results[regionKey][\"id\"].indexOf(previousDataIds[j]);\n if (prevIndex === -1) {\n let keyList = Object.keys(previous_data[regionKey]);\n for (let k = 0; k < keyList.length; k++) {\n if (keyList[k] == \"video_id\" || keyList[k] == \"region_id\" || keyList[k] == \"occupied\") {\n continue;\n }\n results[regionKey][keyList[k]].push(previous_data[regionKey][keyList[k]][j]);\n }\n }\n }\n }\n }\n return results;\n}\n\nfunction handleOccupiedRegion(results, i) {\n const regionKey = \"Region \" + (i + 1);\n var prev_consecutive = (previous_data[regionKey][\"consecutive_occupied\"]) || 0;\n results[regionKey][\"consecutive_occupied\"] = prev_consecutive + 1;\n if (results[regionKey][\"consecutive_occupied\"] < min_entry_frames) {\n results[regionKey][\"occupied\"] = 0;\n results[regionKey][\"occupancy_check\"] = 0;\n return results;\n }\n if (previous_data[regionKey][\"entry_time\"] == \"\") {\n results[regionKey][\"actual_entry_time\"] = msg.nodered_timestamp;\n results[regionKey][\"entry_time\"] = msg.object_timestamp;\n }\n else {\n if (previous_data[regionKey][\"occupancy_check\"] >= occupancy_check_duration) {\n results[regionKey][\"actual_dwell_time\"] = \"00:00:00\";\n results[regionKey][\"dwell_time\"] = 0;\n results[regionKey][\"actual_entry_time\"] = msg.nodered_timestamp;\n results[regionKey][\"entry_time\"] = msg.object_timestamp;\n }\n else {\n results[regionKey][\"actual_entry_time\"] = previous_data[regionKey][\"actual_entry_time\"];\n results[regionKey][\"entry_time\"] = previous_data[regionKey][\"entry_time\"];\n }\n\n var dwell_timestamp = msg.object_timestamp - results[regionKey][\"entry_time\"];\n var dwell_hours = Math.floor(dwell_timestamp / 3600).toString();\n dwell_hours = dwell_hours.toString().length == 1 ? dwell_hours.toString().padStart(2, \"0\") : dwell_hours.toString();\n var dwell_mins = Math.floor((dwell_timestamp / 60) % 60).toString().padStart(2, \"0\");\n var dwell_seconds = Math.floor(dwell_timestamp % 60).toString().padStart(2, \"0\");\n\n results[regionKey][\"dwell_time\"] = dwell_timestamp;\n results[regionKey][\"actual_dwell_time\"] = dwell_hours + \":\" + dwell_mins + \":\" + dwell_seconds;\n results[regionKey][\"occupancy_check\"] = 0;\n }\n\n return results;\n}\n\nfunction handleNotOccupiedRegion(results, i) {\n const regionKey = \"Region \" + (i + 1);\n\n results[regionKey][\"actual_entry_time\"] = previous_data[regionKey][\"actual_entry_time\"];\n results[regionKey][\"entry_time\"] = previous_data[regionKey][\"entry_time\"];\n results[regionKey][\"actual_dwell_time\"] = previous_data[regionKey][\"actual_dwell_time\"];\n results[regionKey][\"dwell_time\"] = previous_data[regionKey][\"dwell_time\"];\n results[regionKey][\"occupancy_check\"] = previous_data[regionKey][\"occupancy_check\"] + 1;\n\n if (previous_data[regionKey][\"exit_time\"]) {\n results[regionKey][\"exit_time\"] = previous_data[regionKey][\"exit_time\"];\n }\n else {\n results[regionKey][\"exit_time\"] = msg.nodered_timestamp;\n }\n return results;\n}\n\nfunction main(payload, distance_threshold = 100, intersection_type = \"region\", intersection_threshold = 0.5, object_counter = false) {\n var results = preprocess_bboxes(payload);\n var occupied = euclidean_iou_function(results, distance_threshold, intersection_type, intersection_threshold, object_counter);\n var out = postprocess_output(occupied);\n flow.set(previous_data_name, out);\n return out;\n}\n\n// user-defined parameters\nvar distance_threshold = flow.get(\"distance_threshold\");\nvar intersection_type = flow.get(\"intersection_type\");\nvar intersection_threshold = flow.get(\"intersection_threshold\");\nvar object_counter = flow.get(\"object_counter\");\n\nmsg.payload = main(payload, distance_threshold, intersection_type, intersection_threshold, object_counter);\nflow.set(detection_name, msg);\nreturn msg;", "outputs": 1, "timeout": 0, "noerr": 0, diff --git a/metro-ai-suite/metro-vision-ai-app-recipe/smart-parking/src/node-red/nodes/core/function/zc1-ri-data-extraction.html b/metro-ai-suite/metro-vision-ai-app-recipe/smart-parking/src/node-red/nodes/core/function/zc1-ri-data-extraction.html index 5816287017..0a9e3282a4 100644 --- a/metro-ai-suite/metro-vision-ai-app-recipe/smart-parking/src/node-red/nodes/core/function/zc1-ri-data-extraction.html +++ b/metro-ai-suite/metro-vision-ai-app-recipe/smart-parking/src/node-red/nodes/core/function/zc1-ri-data-extraction.html @@ -364,7 +364,7 @@ category: 'rapid ri', defaults: { name: {value:"_DEFAULT_"}, - func: {value:"let payload = msg.payload[\"objects\"];\nlet result = {};\nlet counter = 1;\n\nvar object_confidence = flow.get(\"object_confidence\");\nvar target_object = flow.get(\"target_object\");\nvar stop_duration = flow.get(\"stop_duration\");\n\nvar date = new Date();\nvar localDate = new Date(date.toLocaleString(\"en-US\", { timeZone: \"Asia/Kuala_Lumpur\" }));\n\nvar years = localDate.getFullYear();\nvar months = (localDate.getMonth() + 1).toString().padStart(2, \"0\");\nvar dates = (localDate.getDate()).toString().padStart(2, \"0\");\nvar hours = localDate.getHours();\nvar minutes = localDate.getMinutes();\nvar seconds = localDate.getSeconds();\n\nhours = (\"0\" + hours).slice(-2);\nminutes = (\"0\" + minutes).slice(-2);\nseconds = (\"0\" + seconds).slice(-2);\n\nmsg.nodered_timestamp = years + \"/\" + months + \"/\" + dates + \" \" + hours + \":\" + minutes + \":\" + seconds;\nmsg.object_timestamp = new Date().getTime() / 1000;\n\nfor (let key in payload) {\n if (payload.hasOwnProperty(key) && payload[key].hasOwnProperty(\"detection\")) {\n if (!(target_object.length == 1 && target_object[0] == \"\")) {\n var skip = true;\n for (let i = 0; i < target_object.length; i++) {\n if (payload[key][\"roi_type\"] == target_object[i]) {\n skip = false;\n break;\n }\n }\n if (skip == true) { continue; }\n }\n if (payload[key][\"detection\"][\"confidence\"] > object_confidence) {\n var current_object_data = {\n \"bbox\": {\n \"vertex1\": { \"x\": payload[key][\"x\"], \"y\": payload[key][\"y\"] },\n \"vertex2\": { \"x\": payload[key][\"x\"] + payload[key][\"w\"], \"y\": payload[key][\"y\"] },\n \"vertex3\": { \"x\": payload[key][\"x\"] + payload[key][\"w\"], \"y\": payload[key][\"y\"] + payload[key][\"h\"] },\n \"vertex4\": { \"x\": payload[key][\"x\"], \"y\": payload[key][\"y\"] + payload[key][\"h\"] },\n \"vertex_0\": { \"x\": payload[key][\"x\"], \"y\": payload[key][\"y\"] }\n }\n }\n if (payload[key].hasOwnProperty(\"type\") && payload[key][\"type\"].hasOwnProperty(\"label\")) {\n current_object_data[\"type\"] = payload[key][\"type\"][\"label\"];\n }\n if (payload[key].hasOwnProperty(\"color\") && payload[key][\"color\"].hasOwnProperty(\"label\")) {\n current_object_data[\"color\"] = payload[key][\"color\"][\"label\"];\n }\n if (payload[key].hasOwnProperty(\"license_plate\") && payload[key][\"license_plate\"].hasOwnProperty(\"label\")) {\n current_object_data[\"license_plate\"] = payload[key][\"license_plate\"][\"label\"];\n }\n if (payload[key].hasOwnProperty(\"id\")) {\n current_object_data[\"id\"] = payload[key][\"id\"];\n }\n if (payload[key].hasOwnProperty(\"roi_type\")) {\n current_object_data[\"roi_type\"] = payload[key][\"roi_type\"];\n }\n result[`object_${counter++}`] = current_object_data;\n }\n }\n}\n\nif (Object.keys(result).length > 0) {\n msg.payload = result;\n return msg;\n}"}, + func: {value:"let payload = msg.payload[\"objects\"];\nlet result = {};\nlet counter = 1;\n\nvar object_confidence = flow.get(\"object_confidence\");\nvar target_object = flow.get(\"target_object\");\nvar stop_duration = flow.get(\"stop_duration\");\n\nvar date = new Date();\nvar localDate = new Date();\n\nvar years = localDate.getFullYear();\nvar months = (localDate.getMonth() + 1).toString().padStart(2, \"0\");\nvar dates = (localDate.getDate()).toString().padStart(2, \"0\");\nvar hours = localDate.getHours();\nvar minutes = localDate.getMinutes();\nvar seconds = localDate.getSeconds();\n\nhours = (\"0\" + hours).slice(-2);\nminutes = (\"0\" + minutes).slice(-2);\nseconds = (\"0\" + seconds).slice(-2);\n\nmsg.nodered_timestamp = years + \"/\" + months + \"/\" + dates + \" \" + hours + \":\" + minutes + \":\" + seconds;\nmsg.object_timestamp = new Date().getTime() / 1000;\n\nfor (let key in payload) {\n if (payload.hasOwnProperty(key) && payload[key].hasOwnProperty(\"detection\")) {\n if (!(target_object.length == 1 && target_object[0] == \"\")) {\n var skip = true;\n for (let i = 0; i < target_object.length; i++) {\n if (payload[key][\"roi_type\"] == target_object[i]) {\n skip = false;\n break;\n }\n }\n if (skip == true) { continue; }\n }\n if (payload[key][\"detection\"][\"confidence\"] > object_confidence) {\n var current_object_data = {\n \"bbox\": {\n \"vertex1\": { \"x\": payload[key][\"x\"], \"y\": payload[key][\"y\"] },\n \"vertex2\": { \"x\": payload[key][\"x\"] + payload[key][\"w\"], \"y\": payload[key][\"y\"] },\n \"vertex3\": { \"x\": payload[key][\"x\"] + payload[key][\"w\"], \"y\": payload[key][\"y\"] + payload[key][\"h\"] },\n \"vertex4\": { \"x\": payload[key][\"x\"], \"y\": payload[key][\"y\"] + payload[key][\"h\"] },\n \"vertex_0\": { \"x\": payload[key][\"x\"], \"y\": payload[key][\"y\"] }\n }\n }\n if (payload[key].hasOwnProperty(\"type\") && payload[key][\"type\"].hasOwnProperty(\"label\")) {\n current_object_data[\"type\"] = payload[key][\"type\"][\"label\"];\n }\n if (payload[key].hasOwnProperty(\"color\") && payload[key][\"color\"].hasOwnProperty(\"label\")) {\n current_object_data[\"color\"] = payload[key][\"color\"][\"label\"];\n }\n if (payload[key].hasOwnProperty(\"license_plate\") && payload[key][\"license_plate\"].hasOwnProperty(\"label\")) {\n current_object_data[\"license_plate\"] = payload[key][\"license_plate\"][\"label\"];\n }\n if (payload[key].hasOwnProperty(\"id\")) {\n current_object_data[\"id\"] = payload[key][\"id\"];\n }\n if (payload[key].hasOwnProperty(\"roi_type\")) {\n current_object_data[\"roi_type\"] = payload[key][\"roi_type\"];\n }\n result[`object_${counter++}`] = current_object_data;\n }\n }\n}\n\nif (Object.keys(result).length > 0) {\n msg.payload = result;\n return msg;\n}"}, outputs: {value:1}, timeout:{value:RED.settings.functionTimeout || 0}, noerr: {value:0,required:true,