From 1bb0c145bca1a0be9c482925a4f0af7e51a2ce41 Mon Sep 17 00:00:00 2001 From: Thibaut Pollina Date: Mon, 10 Nov 2025 16:14:35 +0000 Subject: [PATCH 01/36] update --- node-red/projects/dashboard/flows.json | 1688 ++++-------------------- 1 file changed, 228 insertions(+), 1460 deletions(-) diff --git a/node-red/projects/dashboard/flows.json b/node-red/projects/dashboard/flows.json index e15e8b231..6cbf85a69 100644 --- a/node-red/projects/dashboard/flows.json +++ b/node-red/projects/dashboard/flows.json @@ -127,14 +127,6 @@ "info": "", "env": [] }, - { - "id": "83b2474231efac73", - "type": "tab", - "label": "Cron", - "disabled": true, - "info": "", - "env": [] - }, { "id": "4ed26b8b.253504", "type": "subflow", @@ -222,76 +214,6 @@ "env": [], "color": "#DDAA99" }, - { - "id": "910e195536aea223", - "type": "group", - "z": "83b2474231efac73", - "style": { - "stroke": "#999999", - "stroke-opacity": "1", - "fill": "none", - "fill-opacity": "1", - "label": true, - "label-position": "nw", - "color": "#a4a4a4" - }, - "nodes": [ - "a197bfa71c1761b4", - "495e1733d60d5651", - "0fdba9b9b11427db", - "019662c5bf05e430", - "8dc5b8c9bfd7cc87", - "1c782af30d1ff770", - "5f5dec5ac53119ae", - "61dde05d9a3f805e", - "b17728471bdfc61e", - "e4719fcedaa61149", - "9301eaa353ece507", - "67bcd7bd5a4c38cc", - "2a55ae214f1e7273", - "8447c9830c3aed1e" - ], - "x": -6, - "y": 19, - "w": 1412, - "h": 577 - }, - { - "id": "751429d7682830be", - "type": "group", - "z": "83b2474231efac73", - "style": { - "stroke": "#999999", - "stroke-opacity": "1", - "fill": "none", - "fill-opacity": "1", - "label": true, - "label-position": "nw", - "color": "#a4a4a4" - }, - "nodes": [ - "caf377d323526d14", - "316dae6f231575b7", - "9e169af039fc8031", - "1bc303a19fc9e38b", - "ff83a2795f988098", - "fd3ae27267703c27", - "527165cd7171fe26", - "50629a570b41196e", - "0aef9a09d59e574c", - "a9701575d8df4ce1", - "60d1ad2a7e54e60e", - "f8df009e737eb044", - "e6eb3b16a1d6bf22", - "1997fd46811f0331", - "5f091b11b5c9fa40", - "0650e037776b3d8a" - ], - "x": 14, - "y": 739, - "w": 1812, - "h": 522 - }, { "id": "f591255fbfc6c466", "type": "group", @@ -455,7 +377,7 @@ ], "x": 14, "y": 659, - "w": 552, + "w": 372, "h": 142 }, { @@ -478,7 +400,7 @@ ], "x": 14, "y": 819, - "w": 352, + "w": 372, "h": 142 }, { @@ -499,9 +421,9 @@ "0d10e66bb2b9d1cc", "53516cda8e4f5a3c" ], - "x": 434, + "x": 414, "y": 819, - "w": 372, + "w": 352, "h": 142 }, { @@ -550,8 +472,8 @@ "61ebceb724041fb2" ], "x": 994, - "y": 339, - "w": 392, + "y": 499, + "w": 412, "h": 142 }, { @@ -573,7 +495,7 @@ "7d1f9414be49d7c3" ], "x": 994, - "y": 519, + "y": 659, "w": 412, "h": 142 }, @@ -593,12 +515,13 @@ "nodes": [ "f7c18ab2aa687120", "ede7a86fd97b5e1b", - "af8ce4702c170449" + "af8ce4702c170449", + "9249e16ed7ae4929" ], "x": 14, "y": 979, - "w": 412, - "h": 142 + "w": 562, + "h": 162 }, { "id": "979647e2e34284fb", @@ -653,9 +576,9 @@ "59ec9623add53a47", "750a1061c84bdae2" ], - "x": 874, - "y": 759, - "w": 332, + "x": 994, + "y": 819, + "w": 412, "h": 142 }, { @@ -1002,115 +925,6 @@ "disabled": "false", "groupType": "default" }, - { - "id": "c8bb5168bda76add", - "type": "ui-page", - "d": true, - "name": "Cron Experiment", - "ui": "e6ae26617c24c3ea", - "path": "/plots", - "icon": "sync", - "layout": "grid", - "theme": "f7770f0b818c3a67", - "breakpoints": [ - { - "name": "Default", - "px": "0", - "cols": "3" - }, - { - "name": "Tablet", - "px": "576", - "cols": "6" - }, - { - "name": "Small Desktop", - "px": "768", - "cols": "9" - }, - { - "name": "Desktop", - "px": "1024", - "cols": "12" - } - ], - "order": 8, - "className": "", - "visible": "true", - "disabled": "false" - }, - { - "id": "4a8738206c62b02e", - "type": "ui-group", - "name": "Plots", - "page": "c8bb5168bda76add", - "width": 6, - "height": 1, - "order": 1, - "showTitle": false, - "className": "", - "visible": "true", - "disabled": "false", - "groupType": "default" - }, - { - "id": "7edf5f14f6ca0bdd", - "type": "ui-group", - "name": "Feed", - "page": "c8bb5168bda76add", - "width": 6, - "height": 1, - "order": 2, - "showTitle": false, - "className": "", - "visible": "true", - "disabled": "false", - "groupType": "default" - }, - { - "id": "e41267c4a14ba4af", - "type": "ui-spacer", - "group": "4a8738206c62b02e", - "name": "spacer", - "tooltip": "", - "order": 4, - "width": "6", - "height": 1, - "className": "" - }, - { - "id": "9d9ac37fd36c358e", - "type": "ui-spacer", - "group": "4a8738206c62b02e", - "name": "spacer", - "tooltip": "", - "order": 7, - "width": "6", - "height": 1, - "className": "" - }, - { - "id": "db02fe254c34125f", - "type": "ui-spacer", - "group": "4a8738206c62b02e", - "name": "spacer", - "tooltip": "", - "order": 10, - "width": "6", - "height": 1, - "className": "" - }, - { - "id": "ff024f7a460585b4", - "type": "ui-spacer", - "group": "4a8738206c62b02e", - "name": "spacer", - "tooltip": "", - "order": 1, - "width": "6", - "height": 1, - "className": "" - }, { "id": "f22f627015431032", "type": "ui-page", @@ -1375,20 +1189,6 @@ "disabled": "false", "groupType": "default" }, - { - "id": "518a4242f548c1f2", - "type": "ui-group", - "name": "links", - "page": "f8dd620721c6d70b", - "width": "12", - "height": 1, - "order": 3, - "showTitle": false, - "className": "", - "visible": "true", - "disabled": "false", - "groupType": "default" - }, { "id": "bcc78241b99ba27f", "type": "ui-page", @@ -2231,9 +2031,9 @@ "name": "body", "order": 1, "width": "12", - "height": "8", + "height": "10", "head": "", - "format": "\n", + "format": "\n", "storeOutMessages": true, "passthru": true, "resendOnRefresh": true, @@ -2314,7 +2114,8 @@ "y": 40, "wires": [ [ - "abe86c4e9dedb557" + "abe86c4e9dedb557", + "605bf275a4d80952" ] ] }, @@ -2390,10 +2191,59 @@ "y": 140, "wires": [ [ + "39c370d8a50f3acc", + "3580c4605b01c2b0" + ] + ] + }, + { + "id": "3580c4605b01c2b0", + "type": "debug", + "z": "1b667c6443413ced", + "name": "debug 7", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "true", + "targetType": "full", + "statusVal": "", + "statusType": "auto", + "x": 380, + "y": 260, + "wires": [] + }, + { + "id": "605bf275a4d80952", + "type": "storage info", + "z": "1b667c6443413ced", + "name": "", + "x": 90, + "y": 340, + "wires": [ + [ + "ecb17cf4ccad8d7b", "39c370d8a50f3acc" ] ] }, + { + "id": "ecb17cf4ccad8d7b", + "type": "debug", + "z": "1b667c6443413ced", + "name": "debug 8", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "true", + "targetType": "full", + "statusVal": "", + "statusType": "auto", + "x": 360, + "y": 400, + "wires": [] + }, { "id": "a57a165cd0ce511b", "type": "ui-template", @@ -2406,7 +2256,7 @@ "width": "7", "height": "18", "head": "", - "format": " \n ", + "format": " \n ", "storeOutMessages": true, "passthru": true, "resendOnRefresh": true, @@ -2762,28 +2612,10 @@ "y": 320, "wires": [ [ - "3fa4274844a4e1f8", "f514a138d38d3c61" ] ] }, - { - "id": "3fa4274844a4e1f8", - "type": "debug", - "z": "ab58b3fd0e6bcd77", - "name": "debug 10", - "active": true, - "tosidebar": true, - "console": false, - "tostatus": false, - "complete": "true", - "targetType": "full", - "statusVal": "", - "statusType": "auto", - "x": 220, - "y": 320, - "wires": [] - }, { "id": "349d0f7644f26a62", "type": "ui-template", @@ -3357,7 +3189,7 @@ "width": 0, "height": 0, "head": "", - "format": "\n\n\n\n\n", + "format": "\n\n\n\n\n", "storeOutMessages": true, "passthru": true, "resendOnRefresh": true, @@ -3546,7 +3378,8 @@ "y": 200, "wires": [ [ - "aa6924c7e0aff26c" + "aa6924c7e0aff26c", + "b373abca8b67ca64" ] ] }, @@ -3711,6 +3544,23 @@ ] ] }, + { + "id": "b373abca8b67ca64", + "type": "debug", + "z": "35d7387466dd0bc0", + "name": "debug 11", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "true", + "targetType": "full", + "statusVal": "", + "statusType": "auto", + "x": 280, + "y": 280, + "wires": [] + }, { "id": "250979b4672d81b6", "type": "ui-event", @@ -3772,7 +3622,6 @@ "wires": [ [ "d4129bf3f5622aa6", - "33c0d1b8251b5d00", "921be0438de344ac" ] ] @@ -3782,7 +3631,7 @@ "type": "ui-table", "z": "0fd76ac156d78937", "group": "bfd4acb7b243514f", - "name": "", + "name": "List of acq", "label": "", "order": 2, "width": 0, @@ -3893,7 +3742,7 @@ "resendOnRefresh": true, "templateScope": "local", "className": "", - "x": 1290, + "x": 1510, "y": 160, "wires": [ [] @@ -3917,7 +3766,7 @@ "resendOnRefresh": true, "templateScope": "local", "className": "", - "x": 1300, + "x": 1520, "y": 40, "wires": [ [] @@ -3930,18 +3779,18 @@ "group": "bfd4acb7b243514f", "page": "", "ui": "", - "name": "List of Acquisitions", + "name": "Dialog", "order": 1, "width": 0, "height": 0, "head": "", - "format": "\n\n\n", + "format": "\n\n\n", "storeOutMessages": true, "passthru": true, "resendOnRefresh": true, "templateScope": "local", "className": "", - "x": 690, + "x": 850, "y": 140, "wires": [ [ @@ -3963,7 +3812,7 @@ "correl": "", "expiry": "", "broker": "8dc3722c.06efa8", - "x": 990, + "x": 1090, "y": 140, "wires": [] }, @@ -3984,13 +3833,13 @@ "checkall": "true", "repair": false, "outputs": 1, - "x": 850, + "x": 970, "y": 140, "wires": [ [ "4bdd8a4afcdb704b", - "33c0d1b8251b5d00", - "f8f4b34842d35713" + "f8f4b34842d35713", + "621e28a4a222b99a" ] ] }, @@ -4007,10 +3856,11 @@ "rap": false, "inputs": 0, "x": 100, - "y": 200, + "y": 260, "wires": [ [ - "33c0d1b8251b5d00" + "33c0d1b8251b5d00", + "c601b4ae4a8fc401" ] ] }, @@ -4032,8 +3882,8 @@ "resendOnRefresh": true, "templateScope": "local", "className": "", - "x": 1050, - "y": 200, + "x": 530, + "y": 240, "wires": [ [] ] @@ -4061,7 +3911,7 @@ "wires": [ [], [ - "26074b8d8ad8abf9" + "89d1bfb7839c2077" ] ] }, @@ -4070,15 +3920,15 @@ "type": "function", "z": "0fd76ac156d78937", "name": "set seg_params", - "func": "if (msg.topic) {\n global.set(\"seg_project_name\", msg.payload.dataset.project_name);\n global.set(\"seg_sample_id\", msg.payload.dataset.sample_id);\n global.set(\"seg_acquisition_id\", msg.payload.dataset.acquisition_id);\n global.set(\"seg_path\", msg.payload.dataset.path);\n}\nreturn msg;\n", + "func": "if (msg.topic) {\n global.set(\"seg_project_name\", msg.payload.dataset.project_name);\n global.set(\"seg_sample_id\", msg.payload.dataset.sample_id);\n global.set(\"seg_acquisition_id\", msg.payload.dataset.acquisition_id);\n global.set(\"seg_path\", msg.payload.dataset.path);\n global.set(\"process_min_ESD\", msg.payload.settings.process_min_ESD);\n}\nreturn msg;\n", "outputs": 1, "timeout": "", "noerr": 0, "initialize": "", "finalize": "", "libs": [], - "x": 1020, - "y": 80, + "x": 1120, + "y": 100, "wires": [ [] ] @@ -4096,7 +3946,7 @@ "finalize": "", "libs": [], "x": 120, - "y": 260, + "y": 200, "wires": [ [ "33c0d1b8251b5d00" @@ -4104,21 +3954,74 @@ ] }, { - "id": "77bb783e65c3ffd6", - "type": "ui-event", - "z": "14f8c9b5ce1235cc", - "ui": "e6ae26617c24c3ea", - "name": "UI Event", - "x": 80, - "y": 40, + "id": "89d1bfb7839c2077", + "type": "function", + "z": "0fd76ac156d78937", + "name": "add process_min_ESD", + "func": "// Ensure msg.payload and msg.payload.settings exist\nmsg.payload = msg.payload || {};\n\n// Read the variable from global context\nconst process_min_ESD = global.get(\"process_min_ESD\");\n\n// Add it to the payload settings\nmsg.payload.process_min_ESD = process_min_ESD;\n\n// Return the modified message\nreturn msg;\n", + "outputs": 1, + "timeout": 0, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 680, + "y": 140, "wires": [ [ - "55814595d9207e95" + "26074b8d8ad8abf9" ] ] }, { - "id": "55814595d9207e95", + "id": "c601b4ae4a8fc401", + "type": "debug", + "z": "0fd76ac156d78937", + "name": "debug 5", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "false", + "statusVal": "", + "statusType": "auto", + "x": 500, + "y": 300, + "wires": [] + }, + { + "id": "621e28a4a222b99a", + "type": "debug", + "z": "0fd76ac156d78937", + "name": "debug 6", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "true", + "targetType": "full", + "statusVal": "", + "statusType": "auto", + "x": 1100, + "y": 180, + "wires": [] + }, + { + "id": "77bb783e65c3ffd6", + "type": "ui-event", + "z": "14f8c9b5ce1235cc", + "ui": "e6ae26617c24c3ea", + "name": "UI Event", + "x": 80, + "y": 40, + "wires": [ + [ + "55814595d9207e95" + ] + ] + }, + { + "id": "55814595d9207e95", "type": "switch", "z": "14f8c9b5ce1235cc", "name": "msg.topic === \"$pageview\"", @@ -4217,6 +4120,7 @@ "id": "f530763503fdc7d1", "type": "mqtt in", "z": "14f8c9b5ce1235cc", + "d": true, "g": "979647e2e34284fb", "name": "", "topic": "status/#", @@ -4238,6 +4142,7 @@ "id": "e5a0432fc7b35b29", "type": "function", "z": "14f8c9b5ce1235cc", + "d": true, "g": "979647e2e34284fb", "name": "Count objects segmented", "func": "var objectCount = global.get('objectCount') || 0;\n\nif (msg.payload.status === 'Done') {\n msg.payload.objectCount = objectCount;\n objectCount=0;\n }\nelse if (msg.topic === 'status/segmenter/metric') {\n objectCount++;\n}\n\nglobal.set('objectCount', objectCount);\n\nreturn msg;", @@ -4259,6 +4164,7 @@ "id": "ba8045b3448d5bbf", "type": "switch", "z": "14f8c9b5ce1235cc", + "d": true, "g": "979647e2e34284fb", "name": "msg.payload.metadata.equivalent_diameter is not null", "property": "payload.metadata.equivalent_diameter", @@ -4283,6 +4189,7 @@ "id": "f0313532a2b77337", "type": "switch", "z": "14f8c9b5ce1235cc", + "d": true, "g": "979647e2e34284fb", "name": "", "property": "topic", @@ -4302,8 +4209,7 @@ "wires": [ [ "e5a0432fc7b35b29", - "c6c66ebc50dd5d90", - "f6c913298cba35ee" + "c6c66ebc50dd5d90" ] ] }, @@ -4311,6 +4217,7 @@ "id": "e045f6939008f23f", "type": "ui-chart", "z": "14f8c9b5ce1235cc", + "d": true, "g": "979647e2e34284fb", "group": "bdcb68c6cff2da73", "name": "", @@ -4374,6 +4281,7 @@ "id": "68fe28f50beb2add", "type": "function", "z": "14f8c9b5ce1235cc", + "d": true, "g": "979647e2e34284fb", "name": "convert px to um", "func": "// Conversion factor from pixels to micrometers (µm)\nconst px_to_um = 0.75;\n\n// Convert relevant measurements\nmsg.payload.metadata.equivalent_diameter = Math.round(msg.payload.metadata.equivalent_diameter * px_to_um);\nmsg.payload.metadata.width = Math.round(msg.payload.metadata.width * px_to_um);\nmsg.payload.metadata.height = Math.round(msg.payload.metadata.height * px_to_um);\nmsg.payload.metadata.area = Math.round(msg.payload.metadata.area * (px_to_um ** 2)); // Area is in square units\nmsg.payload.metadata.area_exc = Math.round(msg.payload.metadata.area_exc * (px_to_um ** 2));\nmsg.payload.metadata.convex_area = Math.round(msg.payload.metadata.convex_area * (px_to_um ** 2));\nmsg.payload.metadata.major = Math.round(msg.payload.metadata.major * px_to_um);\nmsg.payload.metadata.minor = Math.round(msg.payload.metadata.minor * px_to_um);\nmsg.payload.metadata.perim = Math.round(msg.payload.metadata[\"perim.\"] * px_to_um); // Ensure proper key reference\nmsg.payload.metadata.bounding_box_area = Math.round(msg.payload.metadata.bounding_box_area * (px_to_um ** 2));\n\nreturn msg;\n", @@ -4399,6 +4307,7 @@ "id": "a20376f0ac08af8e", "type": "ui-markdown", "z": "14f8c9b5ce1235cc", + "d": true, "g": "979647e2e34284fb", "group": "bdcb68c6cff2da73", "name": "Histogram of equivalent_diameter", @@ -4417,6 +4326,7 @@ "id": "694187e7d85faec3", "type": "ui-markdown", "z": "14f8c9b5ce1235cc", + "d": true, "g": "979647e2e34284fb", "group": "bdcb68c6cff2da73", "name": "elongation vs. area", @@ -4435,6 +4345,7 @@ "id": "693e1ad09770bdf5", "type": "ui-chart", "z": "14f8c9b5ce1235cc", + "d": true, "g": "979647e2e34284fb", "group": "bdcb68c6cff2da73", "name": "elongation vs. area", @@ -4498,6 +4409,7 @@ "id": "82a3257cd0a3815d", "type": "ui-markdown", "z": "14f8c9b5ce1235cc", + "d": true, "g": "979647e2e34284fb", "group": "fa6393a7d7e3b7d7", "name": "equivalent_diameter vs. MeanValue", @@ -4516,6 +4428,7 @@ "id": "44ef242ece9b6b4b", "type": "ui-markdown", "z": "14f8c9b5ce1235cc", + "d": true, "g": "979647e2e34284fb", "group": "fa6393a7d7e3b7d7", "name": "MeanSaturation vs. MeanValue", @@ -4534,6 +4447,7 @@ "id": "7433aae2400b7f78", "type": "ui-chart", "z": "14f8c9b5ce1235cc", + "d": true, "g": "979647e2e34284fb", "group": "fa6393a7d7e3b7d7", "name": "equivalent_diameter vs. MeanValue", @@ -4597,6 +4511,7 @@ "id": "a90099cfe7a37a37", "type": "ui-chart", "z": "14f8c9b5ce1235cc", + "d": true, "g": "979647e2e34284fb", "group": "fa6393a7d7e3b7d7", "name": "MeanSaturation vs. MeanValue", @@ -4660,6 +4575,7 @@ "id": "df0dcb1175b8bd5f", "type": "ui-chart", "z": "14f8c9b5ce1235cc", + "d": true, "g": "979647e2e34284fb", "group": "ed357f41cdf951bc", "name": "x vs. y", @@ -4719,26 +4635,11 @@ [] ] }, - { - "id": "bfcd2b12c9c89c15", - "type": "debug", - "z": "14f8c9b5ce1235cc", - "name": "debug 5", - "active": true, - "tosidebar": true, - "console": false, - "tostatus": false, - "complete": "false", - "statusVal": "", - "statusType": "auto", - "x": 600, - "y": 200, - "wires": [] - }, { "id": "c6c66ebc50dd5d90", "type": "switch", "z": "14f8c9b5ce1235cc", + "d": true, "g": "979647e2e34284fb", "name": "", "property": "payload.status", @@ -4756,28 +4657,9 @@ "x": 430, "y": 380, "wires": [ - [ - "bfcd2b12c9c89c15" - ] + [] ] }, - { - "id": "f6c913298cba35ee", - "type": "debug", - "z": "14f8c9b5ce1235cc", - "name": "debug 8", - "active": true, - "tosidebar": true, - "console": false, - "tostatus": false, - "complete": "payload", - "targetType": "msg", - "statusVal": "", - "statusType": "auto", - "x": 380, - "y": 180, - "wires": [] - }, { "id": "64c27d4448d8f79f", "type": "ui-event", @@ -5049,30 +4931,6 @@ [] ] }, - { - "id": "590ff706a46acf3a", - "type": "ui-template", - "z": "8018bd5586fd4054", - "group": "518a4242f548c1f2", - "page": "", - "ui": "", - "name": "links", - "order": 1, - "width": 0, - "height": 0, - "head": "", - "format": "\n", - "storeOutMessages": true, - "passthru": true, - "resendOnRefresh": true, - "templateScope": "local", - "className": "", - "x": 490, - "y": 260, - "wires": [ - [] - ] - }, { "id": "062a3af5e12e8445", "type": "ui-template", @@ -5897,8 +5755,7 @@ "y": 140, "wires": [ [ - "b4d023d76ef0504c", - "c35c707fc4606403" + "b4d023d76ef0504c" ] ] }, @@ -5964,45 +5821,10 @@ "y": 340, "wires": [ [ - "1bd39bfcfab012fe", - "568563f0ea454a6e" + "1bd39bfcfab012fe" ] ] }, - { - "id": "568563f0ea454a6e", - "type": "debug", - "z": "d5b2c64b84f8ed4f", - "name": "debug 6", - "active": true, - "tosidebar": true, - "console": false, - "tostatus": false, - "complete": "true", - "targetType": "full", - "statusVal": "", - "statusType": "auto", - "x": 340, - "y": 340, - "wires": [] - }, - { - "id": "c35c707fc4606403", - "type": "debug", - "z": "d5b2c64b84f8ed4f", - "name": "debug 7", - "active": true, - "tosidebar": true, - "console": false, - "tostatus": false, - "complete": "true", - "targetType": "full", - "statusVal": "", - "statusType": "auto", - "x": 880, - "y": 200, - "wires": [] - }, { "id": "486bc5c0891a3a15", "type": "function", @@ -6223,7 +6045,7 @@ "z": "a02961610bc3982a", "g": "27e22d982f0bcb2f", "name": "", - "x": 550, + "x": 530, "y": 920, "wires": [ [] @@ -6241,7 +6063,7 @@ "once": false, "onceDelay": 0.1, "topic": "", - "x": 530, + "x": 510, "y": 860, "wires": [ [ @@ -6355,7 +6177,7 @@ "g": "3fe3efd04fb1a41a", "name": "hostname", "info": "", - "x": 480, + "x": 300, "y": 700, "wires": [] }, @@ -6366,7 +6188,7 @@ "g": "9c49d230dea06fdf", "name": "name", "info": "", - "x": 290, + "x": 310, "y": 860, "wires": [] }, @@ -6377,7 +6199,7 @@ "g": "27e22d982f0bcb2f", "name": "storage", "info": "", - "x": 730, + "x": 690, "y": 860, "wires": [] }, @@ -6496,7 +6318,7 @@ "g": "dc792afbab434446", "name": "", "x": 1120, - "y": 440, + "y": 600, "wires": [ [] ] @@ -6514,7 +6336,7 @@ "onceDelay": 0.1, "topic": "", "x": 1090, - "y": 380, + "y": 540, "wires": [ [ "61ebceb724041fb2" @@ -6528,8 +6350,8 @@ "g": "dc792afbab434446", "name": "acquisitions", "info": "", - "x": 1290, - "y": 380, + "x": 1310, + "y": 540, "wires": [] }, { @@ -6545,7 +6367,7 @@ "onceDelay": 0.1, "topic": "", "x": 1090, - "y": 560, + "y": 700, "wires": [ [ "7d1f9414be49d7c3" @@ -6560,7 +6382,7 @@ "name": "segmentations", "info": "", "x": 1300, - "y": 560, + "y": 700, "wires": [] }, { @@ -6570,7 +6392,7 @@ "g": "dc307bb5d26c60f4", "name": "", "x": 1130, - "y": 620, + "y": 760, "wires": [ [] ] @@ -6584,7 +6406,9 @@ "x": 150, "y": 1080, "wires": [ - [] + [ + "9249e16ed7ae4929" + ] ] }, { @@ -6614,7 +6438,7 @@ "g": "855d9ef468b5db0f", "name": "machine info", "info": "", - "x": 330, + "x": 290, "y": 1020, "wires": [] }, @@ -6630,8 +6454,8 @@ "once": false, "onceDelay": 0.1, "topic": "", - "x": 970, - "y": 800, + "x": 1090, + "y": 860, "wires": [ [ "59ec9623add53a47" @@ -6644,8 +6468,8 @@ "z": "a02961610bc3982a", "g": "91f8b76bf61fc59c", "name": "", - "x": 980, - "y": 860, + "x": 1100, + "y": 920, "wires": [ [] ] @@ -6657,8 +6481,25 @@ "g": "91f8b76bf61fc59c", "name": "camera", "info": "", - "x": 1130, - "y": 800, + "x": 1330, + "y": 860, + "wires": [] + }, + { + "id": "9249e16ed7ae4929", + "type": "debug", + "z": "a02961610bc3982a", + "g": "855d9ef468b5db0f", + "name": "debug 10", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "false", + "statusVal": "", + "statusType": "auto", + "x": 470, + "y": 1100, "wires": [] }, { @@ -7384,1078 +7225,5 @@ "wires": [ [] ] - }, - { - "id": "495e1733d60d5651", - "type": "switch", - "z": "83b2474231efac73", - "g": "910e195536aea223", - "name": "", - "property": "topic", - "propertyType": "msg", - "rules": [ - { - "t": "eq", - "v": "light", - "vt": "str" - }, - { - "t": "eq", - "v": "actuator/pump", - "vt": "str" - }, - { - "t": "eq", - "v": "imager/image/update_config", - "vt": "str" - }, - { - "t": "eq", - "v": "imager/image/image", - "vt": "str" - }, - { - "t": "eq", - "v": "segmenter/segment", - "vt": "str" - }, - { - "t": "eq", - "v": "restart/hardware_controller", - "vt": "str" - }, - { - "t": "eq", - "v": "restart/segmenter", - "vt": "str" - }, - { - "t": "eq", - "v": "restart/planktoscope", - "vt": "str" - }, - { - "t": "eq", - "v": "purge", - "vt": "str" - } - ], - "checkall": "true", - "repair": false, - "outputs": 9, - "x": 350, - "y": 160, - "wires": [ - [ - "1c782af30d1ff770", - "67bcd7bd5a4c38cc" - ], - [ - "1c782af30d1ff770", - "67bcd7bd5a4c38cc" - ], - [ - "0fdba9b9b11427db" - ], - [ - "8dc5b8c9bfd7cc87" - ], - [ - "019662c5bf05e430" - ], - [ - "5f5dec5ac53119ae" - ], - [ - "61dde05d9a3f805e" - ], - [ - "2a55ae214f1e7273" - ], - [ - "8447c9830c3aed1e" - ] - ] - }, - { - "id": "0fdba9b9b11427db", - "type": "function", - "z": "83b2474231efac73", - "g": "910e195536aea223", - "name": "update_config command", - "func": "const now = new Date();\n\nmsg.topic = \"imager/image\";\n\nconst pad = (n) => n.toString().padStart(2, '0');\n\nlet formattedDate = `${now.getUTCFullYear()}_${pad(now.getUTCMonth() + 1)}_${pad(now.getUTCDate())}_${pad(now.getUTCHours())}_${pad(now.getUTCMinutes())}_${pad(now.getUTCSeconds())}`;\n\nglobal.set(\"path\", `${now.toISOString().split('T')[0]}/S1/${formattedDate}`);\n\nmsg.payload = {\n \"action\": \"update_config\",\n \"config\": {\n \"sample_project\": \"FairScope_TenEarth\",\n \"sample_id\": \"S1\",\n \"sample_operator\": \"Fairscope\",\n \"sample_instrument\": \"PlanktoScope v2.6\",\n \"sample_date\": now.toISOString().split('T')[0],\n \"acq_id\": formattedDate,\n \"acq_instrument\": \"PlanktoScope v2.6\",\n \"acq_magnification\": \"1.2\",\n \"acq_software\": \"PlanktoScope v2024.0.0-alpha.1\",\n \"acq_resolution\": \"1080p\",\n \"acq_time_between_frames\": 0.1,\n \"acq_minimum_mesh\": 20,\n \"acq_maximum_mesh\": 300,\n \"acq_min_esd\": 20,\n \"acq_max_esd\": 300,\n \"acq_camera_name\": \"HQ Camera\",\n \"process_pixel\": 0.75,\n \"acq_local_datetime\": now.toISOString().split('.')[0] + 'Z',\n \"object_date\": now.toISOString().split('T')[0],\n \"object_time\": now.toISOString().split('T')[1].split('.')[0] + 'Z',\n \"object_lat\": 48.587424697483556,\n \"object_lon\": -3.8382606493497824\n }\n};\n\nreturn msg;", - "outputs": 1, - "timeout": 0, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 630, - "y": 180, - "wires": [ - [ - "1c782af30d1ff770", - "67bcd7bd5a4c38cc" - ] - ] - }, - { - "id": "019662c5bf05e430", - "type": "function", - "z": "83b2474231efac73", - "g": "910e195536aea223", - "name": "segment command", - "func": "msg.payload = {\n \"action\": \"segment\",\n \"path\": global.get(\"path\"),\n \"settings\": {\n \"force\": false,\n \"recursive\": true,\n \"ecotaxa\": true,\n \"keep\": true\n }\n}\n\nreturn msg; ", - "outputs": 1, - "timeout": 0, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 610, - "y": 260, - "wires": [ - [ - "1c782af30d1ff770", - "67bcd7bd5a4c38cc" - ] - ] - }, - { - "id": "8dc5b8c9bfd7cc87", - "type": "function", - "z": "83b2474231efac73", - "g": "910e195536aea223", - "name": "image command", - "func": "msg.topic = \"imager/image\"\n\nreturn msg;", - "outputs": 1, - "timeout": 0, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 630, - "y": 220, - "wires": [ - [ - "1c782af30d1ff770", - "67bcd7bd5a4c38cc" - ] - ] - }, - { - "id": "1c782af30d1ff770", - "type": "mqtt out", - "z": "83b2474231efac73", - "g": "910e195536aea223", - "name": "", - "topic": "", - "qos": "", - "retain": "", - "respTopic": "", - "contentType": "", - "userProps": "", - "correl": "", - "expiry": "", - "broker": "8dc3722c.06efa8", - "x": 1270, - "y": 100, - "wires": [] - }, - { - "id": "caf377d323526d14", - "type": "mqtt in", - "z": "83b2474231efac73", - "g": "751429d7682830be", - "name": "", - "topic": "status/#", - "qos": "0", - "datatype": "json", - "broker": "8dc3722c.06efa8", - "nl": false, - "rap": false, - "inputs": 0, - "x": 100, - "y": 800, - "wires": [ - [ - "ff83a2795f988098" - ] - ] - }, - { - "id": "316dae6f231575b7", - "type": "function", - "z": "83b2474231efac73", - "g": "751429d7682830be", - "name": "Count objects segmented", - "func": "var objectCount = global.get('objectCount') || 0;\n\nif (msg.payload.status === 'Done') {\n msg.payload.objectCount = objectCount;\n objectCount=0;\n }\nelse if (msg.topic === 'status/segmenter/metric') {\n objectCount++;\n}\n\nglobal.set('objectCount', objectCount);\n\nreturn msg;", - "outputs": 1, - "timeout": 0, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 550, - "y": 860, - "wires": [ - [ - "9e169af039fc8031" - ] - ] - }, - { - "id": "9e169af039fc8031", - "type": "switch", - "z": "83b2474231efac73", - "g": "751429d7682830be", - "name": "msg.payload.metadata.equivalent_diameter is not null", - "property": "payload.metadata.equivalent_diameter", - "propertyType": "msg", - "rules": [ - { - "t": "nnull" - } - ], - "checkall": "true", - "repair": false, - "outputs": 1, - "x": 880, - "y": 860, - "wires": [ - [ - "50629a570b41196e" - ] - ] - }, - { - "id": "1bc303a19fc9e38b", - "type": "ui-template", - "z": "83b2474231efac73", - "g": "751429d7682830be", - "group": "7edf5f14f6ca0bdd", - "page": "", - "ui": "", - "name": "Stream Pi Camera", - "order": 3, - "width": 0, - "height": 0, - "head": "", - "format": "
\n \"If\n
", - "storeOutMessages": true, - "passthru": true, - "resendOnRefresh": true, - "templateScope": "local", - "className": "", - "x": 390, - "y": 780, - "wires": [ - [] - ] - }, - { - "id": "ff83a2795f988098", - "type": "switch", - "z": "83b2474231efac73", - "g": "751429d7682830be", - "name": "", - "property": "topic", - "propertyType": "msg", - "rules": [ - { - "t": "eq", - "v": "status/segmenter/metric", - "vt": "str" - } - ], - "checkall": "true", - "repair": false, - "outputs": 1, - "x": 350, - "y": 860, - "wires": [ - [ - "316dae6f231575b7" - ] - ] - }, - { - "id": "fd3ae27267703c27", - "type": "ui-chart", - "z": "83b2474231efac73", - "g": "751429d7682830be", - "group": "4a8738206c62b02e", - "name": "", - "label": "Histogram - Equivalent Diameter", - "order": 2, - "chartType": "histogram", - "category": "Equivalent Diameter (μm)", - "categoryType": "str", - "xAxisLabel": "Equivalent Diameter (μm)", - "xAxisProperty": "payload.metadata.equivalent_diameter", - "xAxisPropertyType": "msg", - "xAxisType": "bins", - "xAxisFormat": "", - "xAxisFormatType": "auto", - "xmin": "20", - "xmax": "100", - "yAxisLabel": "Abundance", - "yAxisProperty": "payload.metadata.equivalent_diameter", - "yAxisPropertyType": "msg", - "ymin": "", - "ymax": "", - "bins": "80", - "action": "append", - "stackSeries": false, - "pointShape": "circle", - "pointRadius": "1", - "showLegend": true, - "removeOlder": 1, - "removeOlderUnit": "86400", - "removeOlderPoints": "", - "colors": [ - "#00ff04", - "#ff0000", - "#ff7f0e", - "#2ca02c", - "#a347e1", - "#d62728", - "#ff9896", - "#9467bd", - "#c5b0d5" - ], - "textColor": [ - "#666666" - ], - "textColorDefault": true, - "gridColor": [ - "#e5e5e5" - ], - "gridColorDefault": true, - "width": "3", - "height": "6", - "className": "", - "interpolation": "linear", - "x": 1660, - "y": 820, - "wires": [ - [] - ] - }, - { - "id": "5f5dec5ac53119ae", - "type": "exec", - "z": "83b2474231efac73", - "g": "910e195536aea223", - "command": "sudo systemctl restart planktoscope-org.controller.service", - "addpay": false, - "append": "", - "useSpawn": "false", - "timer": "", - "winHide": false, - "oldrc": false, - "name": "Restart hardware controller", - "x": 640, - "y": 320, - "wires": [ - [], - [], - [] - ] - }, - { - "id": "61dde05d9a3f805e", - "type": "exec", - "z": "83b2474231efac73", - "g": "910e195536aea223", - "command": "sudo systemctl restart planktoscope-org.segmenter.service", - "addpay": false, - "append": "", - "useSpawn": "false", - "timer": "", - "winHide": false, - "oldrc": false, - "name": "Restart segmenter", - "x": 610, - "y": 380, - "wires": [ - [], - [], - [] - ] - }, - { - "id": "527165cd7171fe26", - "type": "ui-template", - "z": "83b2474231efac73", - "g": "751429d7682830be", - "group": "7edf5f14f6ca0bdd", - "page": "", - "ui": "", - "name": "Actions Clock", - "order": 1, - "width": 0, - "height": 0, - "head": "", - "format": "\n\n\n\n\n", - "storeOutMessages": true, - "passthru": true, - "resendOnRefresh": true, - "templateScope": "local", - "className": "", - "x": 380, - "y": 820, - "wires": [ - [] - ] - }, - { - "id": "50629a570b41196e", - "type": "function", - "z": "83b2474231efac73", - "g": "751429d7682830be", - "name": "convert px to um", - "func": "// Conversion factor from pixels to micrometers (µm)\nconst px_to_um = 0.75;\n\n// Convert relevant measurements\nmsg.payload.metadata.equivalent_diameter = Math.round(msg.payload.metadata.equivalent_diameter * px_to_um);\nmsg.payload.metadata.width = Math.round(msg.payload.metadata.width * px_to_um);\nmsg.payload.metadata.height = Math.round(msg.payload.metadata.height * px_to_um);\nmsg.payload.metadata.area = Math.round(msg.payload.metadata.area * (px_to_um ** 2)); // Area is in square units\nmsg.payload.metadata.area_exc = Math.round(msg.payload.metadata.area_exc * (px_to_um ** 2));\nmsg.payload.metadata.convex_area = Math.round(msg.payload.metadata.convex_area * (px_to_um ** 2));\nmsg.payload.metadata.major = Math.round(msg.payload.metadata.major * px_to_um);\nmsg.payload.metadata.minor = Math.round(msg.payload.metadata.minor * px_to_um);\nmsg.payload.metadata.perim = Math.round(msg.payload.metadata[\"perim.\"] * px_to_um); // Ensure proper key reference\nmsg.payload.metadata.bounding_box_area = Math.round(msg.payload.metadata.bounding_box_area * (px_to_um ** 2));\n\nreturn msg;\n", - "outputs": 1, - "timeout": 0, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 1210, - "y": 860, - "wires": [ - [ - "fd3ae27267703c27", - "60d1ad2a7e54e60e", - "1997fd46811f0331", - "5f091b11b5c9fa40", - "0650e037776b3d8a" - ] - ] - }, - { - "id": "0aef9a09d59e574c", - "type": "ui-markdown", - "z": "83b2474231efac73", - "g": "751429d7682830be", - "group": "4a8738206c62b02e", - "name": "Histogram of equivalent_diameter", - "order": 3, - "width": "3", - "height": "6", - "content": "### Distribution of Object Sizes \nUnderstanding the size distribution of planktonic objects helps identify dominant size classes and detect anomalies. A skewed distribution might indicate specific biological trends, such as a bloom of a particular species or a technical issue in the acquisition process. \n\n*In the graph:* \n- A **peak in smaller sizes** suggests dominance of tiny plankton. \n- A **broad distribution** indicates a mix of various size classes. \n- A **skewed or bimodal pattern** may reflect biological or technical factors (e.g., species-specific blooms or imaging biases).\n", - "className": "", - "x": 1660, - "y": 860, - "wires": [ - [] - ] - }, - { - "id": "a9701575d8df4ce1", - "type": "ui-markdown", - "z": "83b2474231efac73", - "g": "751429d7682830be", - "group": "4a8738206c62b02e", - "name": "elongation vs. area", - "order": 6, - "width": "3", - "height": "6", - "content": "### Relationship Between Elongation and Object Area \nThis scatter plot helps assess whether larger objects tend to be more elongated or compact. Certain plankton species have characteristic shapes, and identifying correlations between elongation and area can aid in distinguishing different taxa or understanding morphological variability. \n\n*In the graph:* \n- **Top-right**: Large and highly elongated objects (e.g., filamentous plankton). \n- **Bottom-right**: Small but highly elongated objects (e.g., thin needle-like forms). \n- **Top-left**: Large but compact objects (e.g., rounded or clustered plankton). \n- **Bottom-left**: Small and compact objects (e.g., spherical or short structures).", - "className": "", - "x": 1610, - "y": 960, - "wires": [ - [] - ] - }, - { - "id": "60d1ad2a7e54e60e", - "type": "ui-chart", - "z": "83b2474231efac73", - "g": "751429d7682830be", - "group": "4a8738206c62b02e", - "name": "elongation vs. area", - "label": "elongation vs. area", - "order": 5, - "chartType": "scatter", - "category": "elongation vs. area", - "categoryType": "str", - "xAxisLabel": "elongation (ratio of major to minor axis)", - "xAxisProperty": "payload.metadata.elongation", - "xAxisPropertyType": "msg", - "xAxisType": "linear", - "xAxisFormat": "", - "xAxisFormatType": "auto", - "xmin": "0", - "xmax": "50", - "yAxisLabel": "area (μm²)", - "yAxisProperty": "payload.metadata.area", - "yAxisPropertyType": "msg", - "ymin": "300", - "ymax": "1500", - "bins": 10, - "action": "append", - "stackSeries": true, - "pointShape": "circle", - "pointRadius": "1", - "showLegend": true, - "removeOlder": "1", - "removeOlderUnit": "3600", - "removeOlderPoints": "300", - "colors": [ - "#ff0088", - "#ff0000", - "#ff7f0e", - "#2ca02c", - "#a347e1", - "#d62728", - "#ff9896", - "#9467bd", - "#c5b0d5" - ], - "textColor": [ - "#666666" - ], - "textColorDefault": true, - "gridColor": [ - "#e5e5e5" - ], - "gridColorDefault": true, - "width": "3", - "height": "6", - "className": "", - "interpolation": "linear", - "x": 1610, - "y": 920, - "wires": [ - [] - ] - }, - { - "id": "f8df009e737eb044", - "type": "ui-markdown", - "z": "83b2474231efac73", - "g": "751429d7682830be", - "group": "4a8738206c62b02e", - "name": "equivalent_diameter vs. MeanValue", - "order": 9, - "width": "3", - "height": "6", - "content": "### Variation of Mean Brightness with Equivalent Diameter \nThis graph explores whether larger objects tend to be brighter or darker. This can reveal differences in pigmentation, transparency, or even imaging inconsistencies. \n\n*In the graph:* \n- **Top-right**: Large and bright objects.\n- **Top-left**: Small and bright objects.\n- **Bottom-right**: Large and dark objects.\n- **Bottom-left**: Small and dark objects. ", - "className": "", - "x": 1660, - "y": 1060, - "wires": [ - [] - ] - }, - { - "id": "e6eb3b16a1d6bf22", - "type": "ui-markdown", - "z": "83b2474231efac73", - "g": "751429d7682830be", - "group": "4a8738206c62b02e", - "name": "MeanSaturation vs. MeanValue", - "order": 13, - "width": "3", - "height": "6", - "content": "### Relationship Between Mean Saturation and Mean Brightness\nThis visualization helps understand how color properties vary with brightness. Some plankton groups exhibit distinct coloration, and saturation could be a useful feature for classification. A strong trend might suggest systematic differences in pigmentation or optical properties across plankton groups.\n\n*In the graph:* \n- **Top-right**: Bright and highly saturated objects (vivid colors). \n- **Top-left**: Bright but low-saturation objects (pale or near-white). \n- **Bottom-right**: Dark but highly saturated objects (deep, rich colors). \n- **Bottom-left**: Dark and low-saturation objects (grayish or faintly colored).", - "className": "", - "x": 1650, - "y": 1160, - "wires": [ - [] - ] - }, - { - "id": "1997fd46811f0331", - "type": "ui-chart", - "z": "83b2474231efac73", - "g": "751429d7682830be", - "group": "4a8738206c62b02e", - "name": "equivalent_diameter vs. MeanValue", - "label": "equivalent_diameter vs. MeanValue", - "order": 8, - "chartType": "scatter", - "category": "equivalent_diameter vs. MeanValue", - "categoryType": "str", - "xAxisLabel": "Equivalent Diameter (μm)", - "xAxisProperty": "payload.metadata.equivalent_diameter", - "xAxisPropertyType": "msg", - "xAxisType": "linear", - "xAxisFormat": "", - "xAxisFormatType": "auto", - "xmin": "0", - "xmax": "50", - "yAxisLabel": "MeanValue (0–255, grayscale value)", - "yAxisProperty": "payload.metadata.MeanValue", - "yAxisPropertyType": "msg", - "ymin": "150", - "ymax": "255", - "bins": 10, - "action": "append", - "stackSeries": true, - "pointShape": "circle", - "pointRadius": "1", - "showLegend": true, - "removeOlder": "1", - "removeOlderUnit": "3600", - "removeOlderPoints": "300", - "colors": [ - "#8c00ff", - "#ff0000", - "#ff7f0e", - "#2ca02c", - "#a347e1", - "#d62728", - "#ff9896", - "#9467bd", - "#c5b0d5" - ], - "textColor": [ - "#666666" - ], - "textColorDefault": true, - "gridColor": [ - "#e5e5e5" - ], - "gridColorDefault": true, - "width": "3", - "height": "6", - "className": "", - "interpolation": "linear", - "x": 1660, - "y": 1020, - "wires": [ - [] - ] - }, - { - "id": "5f091b11b5c9fa40", - "type": "ui-chart", - "z": "83b2474231efac73", - "g": "751429d7682830be", - "group": "4a8738206c62b02e", - "name": "MeanSaturation vs. MeanValue", - "label": "MeanSaturation vs. MeanValue", - "order": 11, - "chartType": "scatter", - "category": "MeanSaturation vs. MeanValue", - "categoryType": "str", - "xAxisLabel": "MeanSaturation (%)", - "xAxisProperty": "payload.metadata.MeanSaturation", - "xAxisPropertyType": "msg", - "xAxisType": "linear", - "xAxisFormat": "", - "xAxisFormatType": "auto", - "xmin": "0", - "xmax": "50", - "yAxisLabel": "MeanValue (0–255, grayscale value)", - "yAxisProperty": "payload.metadata.MeanValue", - "yAxisPropertyType": "msg", - "ymin": "150", - "ymax": "255", - "bins": 10, - "action": "append", - "stackSeries": true, - "pointShape": "circle", - "pointRadius": "1", - "showLegend": true, - "removeOlder": "1", - "removeOlderUnit": "3600", - "removeOlderPoints": "300", - "colors": [ - "#ff9500", - "#ff0000", - "#ff7f0e", - "#2ca02c", - "#a347e1", - "#d62728", - "#ff9896", - "#9467bd", - "#c5b0d5" - ], - "textColor": [ - "#666666" - ], - "textColorDefault": true, - "gridColor": [ - "#e5e5e5" - ], - "gridColorDefault": true, - "width": "3", - "height": "6", - "className": "", - "interpolation": "linear", - "x": 1650, - "y": 1120, - "wires": [ - [] - ] - }, - { - "id": "b17728471bdfc61e", - "type": "ui-template", - "z": "83b2474231efac73", - "g": "910e195536aea223", - "group": "7edf5f14f6ca0bdd", - "page": "", - "ui": "", - "name": "Table", - "order": 2, - "width": 0, - "height": 0, - "head": "", - "format": "\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
TopicActionStatus
light{{msg.payload.light.action}}{{msg.payload.light.status}}
actuator/pump{{msg.payload[\"actuator/pump\"].action}}{{msg.payload[\"actuator/pump\"].status}}
imager/image{{msg.payload[\"imager/image\"].action}}{{msg.payload[\"imager/image\"].status}}
segmenter/segment{{msg.payload[\"segmenter/segment\"].action}}{{msg.payload[\"segmenter/segment\"].status}}
\n", - "storeOutMessages": true, - "passthru": true, - "resendOnRefresh": true, - "templateScope": "local", - "className": "", - "x": 1330, - "y": 320, - "wires": [ - [] - ] - }, - { - "id": "e4719fcedaa61149", - "type": "switch", - "z": "83b2474231efac73", - "g": "910e195536aea223", - "name": "", - "property": "topic", - "propertyType": "msg", - "rules": [ - { - "t": "eq", - "v": "status/light", - "vt": "str" - }, - { - "t": "eq", - "v": "status/pump", - "vt": "str" - }, - { - "t": "eq", - "v": "status/imager", - "vt": "str" - }, - { - "t": "eq", - "v": "status/segmenter", - "vt": "str" - } - ], - "checkall": "true", - "repair": false, - "outputs": 4, - "x": 770, - "y": 540, - "wires": [ - [ - "67bcd7bd5a4c38cc" - ], - [ - "67bcd7bd5a4c38cc" - ], - [ - "67bcd7bd5a4c38cc" - ], - [ - "67bcd7bd5a4c38cc" - ] - ] - }, - { - "id": "9301eaa353ece507", - "type": "mqtt in", - "z": "83b2474231efac73", - "g": "910e195536aea223", - "name": "", - "topic": "status/#", - "qos": "0", - "datatype": "json", - "broker": "8dc3722c.06efa8", - "nl": false, - "rap": false, - "inputs": 0, - "x": 300, - "y": 540, - "wires": [ - [ - "e4719fcedaa61149" - ] - ] - }, - { - "id": "67bcd7bd5a4c38cc", - "type": "function", - "z": "83b2474231efac73", - "g": "910e195536aea223", - "name": "save current actions & status", - "func": "// Récupérer les données précédentes stockées dans le flow context\nvar storedData = flow.get(\"deviceData\") || {\n light: { action: \"\", status: \"\" },\n \"actuator/pump\": { action: \"\", status: \"\" },\n \"imager/image\": { action: \"\", status: \"\" },\n \"segmenter/segment\": { action: \"\", status: \"\" }\n};\n\n// Mettre à jour les valeurs en fonction du topic reçu\nif (msg.topic === \"light\") {\n storedData.light.action = msg.payload.action;\n} else if (msg.topic === \"status/light\") {\n storedData.light.status = msg.payload.status;\n} else if (msg.topic === \"actuator/pump\") {\n storedData[\"actuator/pump\"].action = msg.payload;\n} else if (msg.topic === \"status/pump\") {\n storedData[\"actuator/pump\"].status = msg.payload.status;\n} else if (msg.topic === \"imager/image\") {\n storedData[\"imager/image\"].action = msg.payload.action;\n} else if (msg.topic === \"status/imager\") {\n storedData[\"imager/image\"].status = msg.payload.status;\n} else if (msg.topic === \"segmenter/segment\") {\n storedData[\"segmenter/segment\"].action = msg.payload.action;\n} else if (msg.topic === \"status/segmenter\") {\n storedData[\"segmenter/segment\"].status = msg.payload.status;\n}\n\n// Sauvegarder les nouvelles valeurs\nflow.set(\"deviceData\", storedData);\n\n// Attacher les données mises à jour au message\nmsg.payload = storedData;\n\nreturn msg;\n", - "outputs": 1, - "timeout": 0, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 1120, - "y": 320, - "wires": [ - [ - "b17728471bdfc61e" - ] - ] - }, - { - "id": "2a55ae214f1e7273", - "type": "exec", - "z": "83b2474231efac73", - "g": "910e195536aea223", - "command": "sudo reboot now", - "addpay": false, - "append": "", - "useSpawn": "false", - "timer": "", - "winHide": false, - "oldrc": false, - "name": "Restart PlanktoScope", - "x": 620, - "y": 440, - "wires": [ - [], - [], - [] - ] - }, - { - "id": "0650e037776b3d8a", - "type": "ui-chart", - "z": "83b2474231efac73", - "g": "751429d7682830be", - "group": "4a8738206c62b02e", - "name": "x vs. y", - "label": "x vs. y", - "order": 12, - "chartType": "scatter", - "category": "x vs. y", - "categoryType": "str", - "xAxisLabel": "x", - "xAxisProperty": "payload.metadata.x", - "xAxisPropertyType": "msg", - "xAxisType": "linear", - "xAxisFormat": "", - "xAxisFormatType": "auto", - "xmin": "0", - "xmax": "50", - "yAxisLabel": "y", - "yAxisProperty": "payload.metadata.y", - "yAxisPropertyType": "msg", - "ymin": "", - "ymax": "", - "bins": 10, - "action": "append", - "stackSeries": true, - "pointShape": "circle", - "pointRadius": "1", - "showLegend": true, - "removeOlder": "1", - "removeOlderUnit": "3600", - "removeOlderPoints": "300", - "colors": [ - "#2b00ff", - "#ff0000", - "#ff7f0e", - "#2ca02c", - "#a347e1", - "#d62728", - "#ff9896", - "#9467bd", - "#c5b0d5" - ], - "textColor": [ - "#666666" - ], - "textColorDefault": true, - "gridColor": [ - "#e5e5e5" - ], - "gridColorDefault": true, - "width": "3", - "height": "6", - "className": "", - "interpolation": "linear", - "x": 1570, - "y": 1220, - "wires": [ - [] - ] - }, - { - "id": "8447c9830c3aed1e", - "type": "exec", - "z": "83b2474231efac73", - "g": "910e195536aea223", - "command": "rm -rf data/img/* data/clean/* data/objects/*", - "addpay": false, - "append": "", - "useSpawn": "false", - "timer": "", - "winHide": false, - "oldrc": false, - "name": "Purge data", - "x": 590, - "y": 500, - "wires": [ - [], - [], - [] - ] - }, - { - "id": "a197bfa71c1761b4", - "type": "cronplus", - "z": "83b2474231efac73", - "g": "910e195536aea223", - "name": "", - "outputField": "payload", - "timeZone": "", - "storeName": "", - "commandResponseMsgOutput": "output1", - "defaultLocation": "", - "defaultLocationType": "default", - "outputs": 1, - "options": [ - { - "name": "purge", - "topic": "purge", - "payloadType": "default", - "payload": "", - "expressionType": "cron", - "expression": "0 18 * * * *", - "location": "", - "offset": "0", - "solarType": "all", - "solarEvents": "sunrise,sunset" - }, - { - "name": "schedule9", - "topic": "restart/planktoscope", - "payloadType": "default", - "payload": "", - "expressionType": "cron", - "expression": "0 20 * * * *", - "location": "", - "offset": "0", - "solarType": "all", - "solarEvents": "sunrise,sunset" - }, - { - "name": "restart_segmenter", - "topic": "restart/segmenter", - "payloadType": "default", - "payload": "", - "expressionType": "cron", - "expression": "0 25 * * * *", - "location": "", - "offset": "0", - "solarType": "all", - "solarEvents": "sunrise,sunset" - }, - { - "name": "restart_hardware_controller", - "topic": "restart/hardware_controller", - "payloadType": "default", - "payload": "", - "expressionType": "cron", - "expression": "0 30 * * * *", - "location": "", - "offset": "0", - "solarType": "all", - "solarEvents": "sunrise,sunset" - }, - { - "name": "light_on", - "topic": "light", - "payloadType": "json", - "payload": "{\"action\": \"on\"}", - "expressionType": "cron", - "expression": "0 35 * * * *", - "location": "", - "offset": "0", - "solarType": "all", - "solarEvents": "sunrise,sunset" - }, - { - "name": "update_config", - "topic": "imager/image/update_config", - "payloadType": "default", - "payload": "", - "expressionType": "cron", - "expression": "1 35 * * * *", - "location": "", - "offset": "0", - "solarType": "all", - "solarEvents": "sunrise,sunset" - }, - { - "name": "pump", - "topic": "actuator/pump", - "payloadType": "json", - "payload": "{\"action\": \"move\",\"direction\": \"FORWARD\",\"volume\": 3,\"flowrate\": 30}", - "expressionType": "cron", - "expression": "2 35 * * * *", - "location": "", - "offset": "0", - "solarType": "all", - "solarEvents": "sunrise,sunset" - }, - { - "name": "image", - "topic": "imager/image/image", - "payloadType": "json", - "payload": "{\"action\": \"image\",\"pump_direction\": \"FORWARD\",\"volume\": 0.008,\"nb_frame\": 200,\"sleep\": 0.1}", - "expressionType": "cron", - "expression": "0 36 * * * *", - "location": "", - "offset": "0", - "solarType": "all", - "solarEvents": "sunrise,sunset" - }, - { - "name": "light_off", - "topic": "light", - "payloadType": "json", - "payload": "{\"action\": \"off\"}", - "expressionType": "cron", - "expression": "0 45 * * * *", - "location": "", - "offset": "0", - "solarType": "all", - "solarEvents": "sunrise,sunset" - }, - { - "name": "segment", - "topic": "segmenter/segment", - "payloadType": "default", - "payload": "", - "expressionType": "cron", - "expression": "1 45 * * * *", - "location": "", - "offset": "0", - "solarType": "all", - "solarEvents": "sunrise,sunset" - } - ], - "x": 100, - "y": 60, - "wires": [ - [] - ] } ] \ No newline at end of file From 1e3df1d7a252ba1daa461b81752911106b5d4239 Mon Sep 17 00:00:00 2001 From: Thibaut Pollina Date: Thu, 13 Nov 2025 14:17:32 +0000 Subject: [PATCH 02/36] update --- node-red/projects/dashboard/flows.json | 220 ++++++++++++++++++++----- 1 file changed, 177 insertions(+), 43 deletions(-) diff --git a/node-red/projects/dashboard/flows.json b/node-red/projects/dashboard/flows.json index 940fdb7df..99512df48 100644 --- a/node-red/projects/dashboard/flows.json +++ b/node-red/projects/dashboard/flows.json @@ -2033,13 +2033,13 @@ "width": "12", "height": "10", "head": "", - "format": "\n", + "format": "\n", "storeOutMessages": true, "passthru": true, "resendOnRefresh": true, "templateScope": "local", "className": "", - "x": 370, + "x": 510, "y": 180, "wires": [ [] @@ -2063,7 +2063,7 @@ "resendOnRefresh": true, "templateScope": "widget:ui", "className": "", - "x": 370, + "x": 510, "y": 220, "wires": [ [] @@ -2087,7 +2087,7 @@ "resendOnRefresh": true, "templateScope": "local", "className": "", - "x": 370, + "x": 510, "y": 140, "wires": [ [] @@ -2115,7 +2115,8 @@ "wires": [ [ "abe86c4e9dedb557", - "605bf275a4d80952" + "605bf275a4d80952", + "90ba8dbcce201a83" ] ] }, @@ -2187,12 +2188,11 @@ "type": "get machine info", "z": "1b667c6443413ced", "name": "", - "x": 110, - "y": 140, + "x": 850, + "y": 40, "wires": [ [ - "39c370d8a50f3acc", - "3580c4605b01c2b0" + "c06a59555d958a6f" ] ] }, @@ -2209,8 +2209,8 @@ "targetType": "full", "statusVal": "", "statusType": "auto", - "x": 380, - "y": 260, + "x": 1160, + "y": 160, "wires": [] }, { @@ -2218,17 +2218,98 @@ "type": "storage info", "z": "1b667c6443413ced", "name": "", - "x": 90, - "y": 340, + "x": 830, + "y": 80, + "wires": [ + [ + "ee16d4c7be3a6e3a" + ] + ] + }, + { + "id": "ee16d4c7be3a6e3a", + "type": "function", + "z": "1b667c6443413ced", + "name": "set storage info", + "func": "if (msg.topic) {\n global.set(\"image_acquired\", msg.payload.image_acquired);\n global.set(\"object_segmented\", msg.payload.object_segmented);\n global.set(\"storage_percent_free\", msg.payload.storage_percent_free);\n global.set(\"storage_percent_used\", msg.payload.storage_percent_used);\n}\nreturn msg;\n", + "outputs": 1, + "timeout": "", + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 1000, + "y": 80, + "wires": [ + [] + ] + }, + { + "id": "c06a59555d958a6f", + "type": "function", + "z": "1b667c6443413ced", + "name": "set machine info", + "func": "if (msg.topic) {\n global.set(\"hardware_version\", msg.payload.machine_info.hardware_version);\n global.set(\"machine_name\", msg.payload.machine_info.machine_name);\n global.set(\"hostname\", msg.payload.machine_info.hostname);\n global.set(\"software_version\", msg.payload.machine_info.software_version);\n}\nreturn msg;\n", + "outputs": 1, + "timeout": "", + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 1040, + "y": 40, + "wires": [ + [] + ] + }, + { + "id": "90ba8dbcce201a83", + "type": "delay", + "z": "1b667c6443413ced", + "name": "", + "pauseType": "delay", + "timeout": "1", + "timeoutUnits": "seconds", + "rate": "1", + "nbRateUnits": "1", + "rateUnits": "second", + "randomFirst": "1", + "randomLast": "5", + "randomUnits": "seconds", + "drop": false, + "allowrate": false, + "outputs": 1, + "x": 180, + "y": 140, + "wires": [ + [ + "74f8dad75cdd3c16" + ] + ] + }, + { + "id": "74f8dad75cdd3c16", + "type": "function", + "z": "1b667c6443413ced", + "name": "Get Global Variables", + "func": "const keys = global.keys(); // Get all global variable keys\nmsg.payload = {}; // Initialize the payload object\n\nkeys.forEach(key => {\n // Ignore keys that start with \"$\"\n if (!key.startsWith('$')) {\n msg.payload[key] = global.get(key);\n }\n});\n\nreturn msg;\n", + "outputs": 1, + "timeout": 0, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 200, + "y": 280, "wires": [ [ - "ecb17cf4ccad8d7b", - "39c370d8a50f3acc" + "39c370d8a50f3acc", + "14e528a100c60603" ] ] }, { - "id": "ecb17cf4ccad8d7b", + "id": "14e528a100c60603", "type": "debug", "z": "1b667c6443413ced", "name": "debug 8", @@ -2236,12 +2317,11 @@ "tosidebar": true, "console": false, "tostatus": false, - "complete": "true", - "targetType": "full", + "complete": "false", "statusVal": "", "statusType": "auto", - "x": 360, - "y": 400, + "x": 400, + "y": 360, "wires": [] }, { @@ -2284,7 +2364,8 @@ "y": 140, "wires": [ [ - "f514a138d38d3c61" + "f514a138d38d3c61", + "4f5f150680be40da" ] ] }, @@ -2500,7 +2581,7 @@ "width": "0", "height": "0", "head": "", - "format": "\n\n\n\n", + "format": "\n\n\n\n", "storeOutMessages": true, "passthru": true, "resendOnRefresh": true, @@ -2582,7 +2663,7 @@ "id": "1d591029bae11418", "type": "function", "z": "ab58b3fd0e6bcd77", - "name": "set focus settings", + "name": "set light settings", "func": "if (msg.topic) {\n global.set(\"led_status\", msg.payload.action);\n\n}\nreturn msg;", "outputs": 1, "timeout": "", @@ -2590,7 +2671,7 @@ "initialize": "", "finalize": "", "libs": [], - "x": 810, + "x": 800, "y": 220, "wires": [ [] @@ -2616,6 +2697,22 @@ ] ] }, + { + "id": "4f5f150680be40da", + "type": "debug", + "z": "ab58b3fd0e6bcd77", + "name": "debug 12", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "false", + "statusVal": "", + "statusType": "auto", + "x": 380, + "y": 340, + "wires": [] + }, { "id": "349d0f7644f26a62", "type": "ui-template", @@ -3189,13 +3286,13 @@ "width": 0, "height": 0, "head": "", - "format": "\n\n\n\n\n", + "format": "\n\n\n\n\n", "storeOutMessages": true, "passthru": true, "resendOnRefresh": true, "templateScope": "local", "className": "", - "x": 410, + "x": 370, "y": 140, "wires": [ [ @@ -3253,7 +3350,8 @@ "y": 140, "wires": [ [ - "f841ebf999dd8cb3" + "f841ebf999dd8cb3", + "e9760c98e9340f3e" ], [ "09ecc922fe2214a8" @@ -3515,7 +3613,8 @@ "y": 280, "wires": [ [ - "ce5cca3b8fe34379" + "ce5cca3b8fe34379", + "5090913821c57c81" ] ] }, @@ -3553,12 +3652,46 @@ "tosidebar": true, "console": false, "tostatus": false, - "complete": "true", - "targetType": "full", + "complete": "payload", + "targetType": "msg", "statusVal": "", "statusType": "auto", - "x": 280, - "y": 280, + "x": 520, + "y": 340, + "wires": [] + }, + { + "id": "5090913821c57c81", + "type": "debug", + "z": "35d7387466dd0bc0", + "name": "debug 13", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "payload", + "targetType": "msg", + "statusVal": "", + "statusType": "auto", + "x": 940, + "y": 340, + "wires": [] + }, + { + "id": "e9760c98e9340f3e", + "type": "debug", + "z": "35d7387466dd0bc0", + "name": "debug 14", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "payload", + "targetType": "msg", + "statusVal": "", + "statusType": "auto", + "x": 920, + "y": 80, "wires": [] }, { @@ -3876,7 +4009,7 @@ "width": "0", "height": "0", "head": "", - "format": "\n\n\n\n\n", + "format": "\n\n\n\n", "storeOutMessages": true, "passthru": true, "resendOnRefresh": true, @@ -3982,7 +4115,8 @@ "tosidebar": true, "console": false, "tostatus": false, - "complete": "false", + "complete": "true", + "targetType": "full", "statusVal": "", "statusType": "auto", "x": 500, @@ -6809,15 +6943,15 @@ "id": "1a7bb2d868fe8707", "type": "function", "z": "807baf16b84dfb29", - "name": "Get min_esd", - "func": "msg.payload = global.get(\"min_esd\");\n\nreturn msg;\n", + "name": "Get process_min_ESD", + "func": "msg.payload = global.get(\"process_min_ESD\");\n\nreturn msg;\n", "outputs": 1, "timeout": 0, "noerr": 0, "initialize": "", "finalize": "", "libs": [], - "x": 230, + "x": 260, "y": 140, "wires": [ [ @@ -6831,11 +6965,11 @@ "z": "807baf16b84dfb29", "group": "070185f2b69cf66d", "name": "", - "label": "min_esd", + "label": "process_min_ESD", "order": 6, "width": 0, "height": 0, - "topic": "min_esd", + "topic": "process_min_ESD", "topicType": "str", "mode": "number", "tooltip": "", @@ -6850,7 +6984,7 @@ "icon": "", "iconPosition": "left", "iconInnerPosition": "inside", - "x": 500, + "x": 530, "y": 140, "wires": [ [ @@ -6862,15 +6996,15 @@ "id": "aa0070c82f8fe4b6", "type": "function", "z": "807baf16b84dfb29", - "name": "set min_esd", - "func": "global.set(\"min_esd\", Number(msg.payload));\n", + "name": "set process_min_ESD", + "func": "global.set(\"process_min_ESD\", Number(msg.payload));\n", "outputs": 1, "timeout": "", "noerr": 0, "initialize": "", "finalize": "", "libs": [], - "x": 750, + "x": 780, "y": 140, "wires": [ [] From 73e82d80208ccfa04c82cf138099c75077755d48 Mon Sep 17 00:00:00 2001 From: Thibaut Pollina Date: Wed, 19 Nov 2025 15:49:06 +0000 Subject: [PATCH 03/36] update --- infra/setup.sh | 2 +- node-red/projects/dashboard/flows.json | 99 +++++++++++--------------- 2 files changed, 42 insertions(+), 59 deletions(-) diff --git a/infra/setup.sh b/infra/setup.sh index d65ba4e1e..75ad7964a 100755 --- a/infra/setup.sh +++ b/infra/setup.sh @@ -1,6 +1,6 @@ #!/bin/bash -eux -../os/developer-mode/install-tailsale.sh +../os/developer-mode/install-tailscale.sh sudo apt install -y git ssh tmux just sudo systemctl enable --now ssh diff --git a/node-red/projects/dashboard/flows.json b/node-red/projects/dashboard/flows.json index 99512df48..f0e6524a9 100644 --- a/node-red/projects/dashboard/flows.json +++ b/node-red/projects/dashboard/flows.json @@ -2303,27 +2303,10 @@ "y": 280, "wires": [ [ - "39c370d8a50f3acc", - "14e528a100c60603" + "39c370d8a50f3acc" ] ] }, - { - "id": "14e528a100c60603", - "type": "debug", - "z": "1b667c6443413ced", - "name": "debug 8", - "active": true, - "tosidebar": true, - "console": false, - "tostatus": false, - "complete": "false", - "statusVal": "", - "statusType": "auto", - "x": 400, - "y": 360, - "wires": [] - }, { "id": "a57a165cd0ce511b", "type": "ui-template", @@ -3836,7 +3819,7 @@ "mobileBreakpoint": "sm", "mobileBreakpointType": "defaults", "action": "replace", - "x": 370, + "x": 330, "y": 140, "wires": [ [ @@ -3905,32 +3888,6 @@ [] ] }, - { - "id": "26074b8d8ad8abf9", - "type": "ui-template", - "z": "0fd76ac156d78937", - "group": "bfd4acb7b243514f", - "page": "", - "ui": "", - "name": "Dialog", - "order": 1, - "width": 0, - "height": 0, - "head": "", - "format": "\n\n\n", - "storeOutMessages": true, - "passthru": true, - "resendOnRefresh": true, - "templateScope": "local", - "className": "", - "x": 850, - "y": 140, - "wires": [ - [ - "24c71b69e60e41bd" - ] - ] - }, { "id": "4bdd8a4afcdb704b", "type": "mqtt out", @@ -3992,8 +3949,7 @@ "y": 260, "wires": [ [ - "33c0d1b8251b5d00", - "c601b4ae4a8fc401" + "33c0d1b8251b5d00" ] ] }, @@ -4091,26 +4047,27 @@ "type": "function", "z": "0fd76ac156d78937", "name": "add process_min_ESD", - "func": "// Ensure msg.payload and msg.payload.settings exist\nmsg.payload = msg.payload || {};\n\n// Read the variable from global context\nconst process_min_ESD = global.get(\"process_min_ESD\");\n\n// Add it to the payload settings\nmsg.payload.process_min_ESD = process_min_ESD;\n\n// Return the modified message\nreturn msg;\n", + "func": "// Ensure msg.payload and msg.payload.settings exist\nmsg.payload = msg.payload || {};\n\n// Read the variable from global context\nconst process_min_ESD = global.get(\"process_min_ESD\");\n\n// Add it to the payload settings\nmsg.payload.process_min_ESD = process_min_ESD;\nmsg.payload.open_dialog = true;\n\n// Return the modified message\nreturn msg;\n", "outputs": 1, "timeout": 0, "noerr": 0, "initialize": "", "finalize": "", "libs": [], - "x": 680, - "y": 140, + "x": 540, + "y": 460, "wires": [ [ + "7b77659569034650", "26074b8d8ad8abf9" ] ] }, { - "id": "c601b4ae4a8fc401", + "id": "621e28a4a222b99a", "type": "debug", "z": "0fd76ac156d78937", - "name": "debug 5", + "name": "debug 6", "active": true, "tosidebar": true, "console": false, @@ -4119,15 +4076,15 @@ "targetType": "full", "statusVal": "", "statusType": "auto", - "x": 500, - "y": 300, + "x": 1100, + "y": 180, "wires": [] }, { - "id": "621e28a4a222b99a", + "id": "7b77659569034650", "type": "debug", "z": "0fd76ac156d78937", - "name": "debug 6", + "name": "debug 5", "active": true, "tosidebar": true, "console": false, @@ -4136,10 +4093,36 @@ "targetType": "full", "statusVal": "", "statusType": "auto", - "x": 1100, - "y": 180, + "x": 860, + "y": 200, "wires": [] }, + { + "id": "26074b8d8ad8abf9", + "type": "ui-template", + "z": "0fd76ac156d78937", + "group": "bfd4acb7b243514f", + "page": "", + "ui": "", + "name": "Dialog", + "order": 1, + "width": 0, + "height": 0, + "head": "", + "format": "\n\n\n", + "storeOutMessages": true, + "passthru": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 750, + "y": 140, + "wires": [ + [ + "24c71b69e60e41bd" + ] + ] + }, { "id": "77bb783e65c3ffd6", "type": "ui-event", From fa0ca2a3e76fcd9d09e885c8a9c386efc2060e9d Mon Sep 17 00:00:00 2001 From: Thibaut Pollina Date: Thu, 20 Nov 2025 15:01:36 +0000 Subject: [PATCH 04/36] update --- node-red/projects/dashboard/flows.json | 991 +++++++------------------ 1 file changed, 262 insertions(+), 729 deletions(-) diff --git a/node-red/projects/dashboard/flows.json b/node-red/projects/dashboard/flows.json index f0e6524a9..b49566b7e 100644 --- a/node-red/projects/dashboard/flows.json +++ b/node-red/projects/dashboard/flows.json @@ -523,41 +523,6 @@ "w": 562, "h": 162 }, - { - "id": "979647e2e34284fb", - "type": "group", - "z": "14f8c9b5ce1235cc", - "style": { - "stroke": "#999999", - "stroke-opacity": "1", - "fill": "none", - "fill-opacity": "1", - "label": true, - "label-position": "nw", - "color": "#a4a4a4" - }, - "nodes": [ - "f530763503fdc7d1", - "e5a0432fc7b35b29", - "ba8045b3448d5bbf", - "f0313532a2b77337", - "e045f6939008f23f", - "68fe28f50beb2add", - "a20376f0ac08af8e", - "694187e7d85faec3", - "693e1ad09770bdf5", - "82a3257cd0a3815d", - "44ef242ece9b6b4b", - "7433aae2400b7f78", - "a90099cfe7a37a37", - "df0dcb1175b8bd5f", - "c6c66ebc50dd5d90" - ], - "x": 14, - "y": 279, - "w": 1172, - "h": 542 - }, { "id": "91f8b76bf61fc59c", "type": "group", @@ -584,7 +549,7 @@ { "id": "e6ae26617c24c3ea", "type": "ui-base", - "name": "PlanktoScope GUI v.2", + "name": "PlanktoScope", "path": "/dashboard", "appIcon": "", "includeClientData": true, @@ -593,8 +558,8 @@ "ui-control" ], "showPathInSidebar": true, - "headerContent": "dashpage", - "navigationStyle": "icon", + "headerContent": "dashboard", + "navigationStyle": "none", "titleBarStyle": "fixed", "showReconnectNotification": true, "notificationDisplayTime": "1", @@ -735,10 +700,10 @@ }, "sizes": { "density": "default", - "pagePadding": "12px", - "groupGap": "11px", + "pagePadding": "10px", + "groupGap": "10px", "groupBorderRadius": "4px", - "widgetGap": "13px" + "widgetGap": "10px" } }, { @@ -916,9 +881,9 @@ "type": "ui-group", "name": "Table", "page": "7a4e042a60b734a6", - "width": "8", + "width": "12", "height": 1, - "order": 2, + "order": 3, "showTitle": false, "className": "", "visible": "true", @@ -956,7 +921,7 @@ "cols": "12" } ], - "order": 11, + "order": 10, "className": "", "visible": "false", "disabled": "false" @@ -1006,7 +971,7 @@ "cols": "12" } ], - "order": 12, + "order": 11, "className": "", "visible": "false", "disabled": "false" @@ -1056,7 +1021,7 @@ "cols": "12" } ], - "order": 13, + "order": 12, "className": "", "visible": "false", "disabled": "false" @@ -1106,7 +1071,7 @@ "cols": "12" } ], - "order": 10, + "order": 9, "className": "", "visible": "false", "disabled": "false" @@ -1156,7 +1121,7 @@ "cols": "12" } ], - "order": 9, + "order": 8, "className": "", "visible": "true", "disabled": "false" @@ -1212,7 +1177,7 @@ { "name": "Small Desktop", "px": "768", - "cols": "9" + "cols": "6" }, { "name": "Desktop", @@ -1222,15 +1187,15 @@ ], "order": 2, "className": "", - "visible": true, - "disabled": false + "visible": "true", + "disabled": "false" }, { "id": "58ab4d5e3dd68192", "type": "ui-group", "name": "Streaming", "page": "bcc78241b99ba27f", - "width": "7", + "width": "6", "height": 1, "order": 2, "showTitle": false, @@ -1244,7 +1209,7 @@ "type": "ui-group", "name": "Settings", "page": "bcc78241b99ba27f", - "width": "5", + "width": "6", "height": 1, "order": 3, "showTitle": false, @@ -1276,7 +1241,7 @@ { "name": "Small Desktop", "px": "768", - "cols": "9" + "cols": "6" }, { "name": "Desktop", @@ -1294,7 +1259,7 @@ "type": "ui-group", "name": "Streaming", "page": "5c3e73c675caac42", - "width": "7", + "width": "6", "height": 1, "order": 2, "showTitle": false, @@ -1308,10 +1273,10 @@ "type": "ui-group", "name": "Acquisition settings", "page": "5c3e73c675caac42", - "width": "5", + "width": "6", "height": 1, "order": 3, - "showTitle": true, + "showTitle": false, "className": "", "visible": "true", "disabled": "false", @@ -1353,24 +1318,10 @@ "visible": "true", "disabled": "false" }, - { - "id": "713fd4c3b9030f45", - "type": "ui-group", - "name": "body", - "page": "632260133d581caa", - "width": "12", - "height": "1", - "order": 2, - "showTitle": false, - "className": "", - "visible": "true", - "disabled": "false", - "groupType": "default" - }, { "id": "5d39a98563150f22", "type": "ui-group", - "name": "header", + "name": "body", "page": "632260133d581caa", "width": "12", "height": 1, @@ -1522,21 +1473,7 @@ "page": "d129fac8e7742d5b", "width": "12", "height": 1, - "order": 5, - "showTitle": false, - "className": "", - "visible": "true", - "disabled": "false", - "groupType": "default" - }, - { - "id": "fa6393a7d7e3b7d7", - "type": "ui-group", - "name": "Plots Left", - "page": "d129fac8e7742d5b", - "width": 6, - "height": 1, - "order": 3, + "order": 4, "showTitle": false, "className": "", "visible": "true", @@ -1621,42 +1558,14 @@ "disabled": "false", "groupType": "default" }, - { - "id": "ed357f41cdf951bc", - "type": "ui-group", - "name": "x vs y", - "page": "d129fac8e7742d5b", - "width": "12", - "height": 1, - "order": 2, - "showTitle": false, - "className": "", - "visible": "true", - "disabled": "false", - "groupType": "default" - }, - { - "id": "bdcb68c6cff2da73", - "type": "ui-group", - "name": "Plot Right", - "page": "d129fac8e7742d5b", - "width": 6, - "height": 1, - "order": 4, - "showTitle": false, - "className": "", - "visible": "true", - "disabled": "false", - "groupType": "default" - }, { "id": "402b3d24c87ab0d2", "type": "ui-group", - "name": "Group 53", + "name": "Informations", "page": "7a4e042a60b734a6", - "width": "4", + "width": "12", "height": 1, - "order": 3, + "order": 2, "showTitle": false, "className": "", "visible": "true", @@ -1694,7 +1603,7 @@ "cols": "12" } ], - "order": 14, + "order": 13, "className": "", "visible": "true", "disabled": "false" @@ -1744,7 +1653,7 @@ "cols": "12" } ], - "order": 15, + "order": 14, "className": "", "visible": "true", "disabled": "false" @@ -1763,6 +1672,34 @@ "disabled": "false", "groupType": "default" }, + { + "id": "fa6393a7d7e3b7d7", + "type": "ui-group", + "name": "Plots Left", + "page": "d129fac8e7742d5b", + "width": 6, + "height": 1, + "order": 2, + "showTitle": false, + "className": "", + "visible": "true", + "disabled": "false", + "groupType": "default" + }, + { + "id": "bdcb68c6cff2da73", + "type": "ui-group", + "name": "Plot Right", + "page": "d129fac8e7742d5b", + "width": 6, + "height": 1, + "order": 3, + "showTitle": false, + "className": "", + "visible": "true", + "disabled": "false", + "groupType": "default" + }, { "id": "82099021.9ceb08", "type": "file", @@ -2021,30 +1958,6 @@ ] ] }, - { - "id": "39c370d8a50f3acc", - "type": "ui-template", - "z": "1b667c6443413ced", - "group": "713fd4c3b9030f45", - "page": "", - "ui": "", - "name": "body", - "order": 1, - "width": "12", - "height": "10", - "head": "", - "format": "\n", - "storeOutMessages": true, - "passthru": true, - "resendOnRefresh": true, - "templateScope": "local", - "className": "", - "x": 510, - "y": 180, - "wires": [ - [] - ] - }, { "id": "1cf1b20e425ec126", "type": "ui-template", @@ -2057,38 +1970,14 @@ "width": 0, "height": 0, "head": "", - "format": "\n\n\n", + "format": "\n\n\n", "storeOutMessages": true, "passthru": true, "resendOnRefresh": true, "templateScope": "widget:ui", "className": "", "x": 510, - "y": 220, - "wires": [ - [] - ] - }, - { - "id": "cd5cd3a0277b8dba", - "type": "ui-template", - "z": "1b667c6443413ced", - "group": "5d39a98563150f22", - "page": "", - "ui": "", - "name": "header", - "order": 1, - "width": "12", - "height": "6", - "head": "", - "format": "\n", - "storeOutMessages": true, - "passthru": true, - "resendOnRefresh": true, - "templateScope": "local", - "className": "", - "x": 510, - "y": 140, + "y": 440, "wires": [ [] ] @@ -2196,23 +2085,6 @@ ] ] }, - { - "id": "3580c4605b01c2b0", - "type": "debug", - "z": "1b667c6443413ced", - "name": "debug 7", - "active": true, - "tosidebar": true, - "console": false, - "tostatus": false, - "complete": "true", - "targetType": "full", - "statusVal": "", - "statusType": "auto", - "x": 1160, - "y": 160, - "wires": [] - }, { "id": "605bf275a4d80952", "type": "storage info", @@ -2303,63 +2175,187 @@ "y": 280, "wires": [ [ - "39c370d8a50f3acc" + "52902327e2363f8b", + "cd5cd3a0277b8dba", + "0c0eb2a907745142" ] ] }, { - "id": "a57a165cd0ce511b", + "id": "52902327e2363f8b", "type": "ui-template", - "z": "ab58b3fd0e6bcd77", - "group": "58ab4d5e3dd68192", + "z": "1b667c6443413ced", + "group": "", "page": "", - "ui": "", - "name": "Streaming", - "order": 1, - "width": "7", - "height": "18", + "ui": "e6ae26617c24c3ea", + "name": "toolbar", + "order": 2, + "width": "12", + "height": "6", "head": "", - "format": " \n ", + "format": "\n\n\n\n\n\n", "storeOutMessages": true, "passthru": true, "resendOnRefresh": true, - "templateScope": "local", + "templateScope": "widget:ui", "className": "", - "x": 1310, - "y": 100, + "x": 820, + "y": 200, + "wires": [ + [ + "1db1c2e3e19e85ff" + ] + ] + }, + { + "id": "8efc52e6ee9206f6", + "type": "poweroff", + "z": "1b667c6443413ced", + "name": "", + "x": 1160, + "y": 240, "wires": [ [] ] }, { - "id": "c8de0186a0a927e8", - "type": "function", - "z": "ab58b3fd0e6bcd77", - "name": "Get Global Variables", - "func": "const keys = global.keys(); // Get all global variable keys\nmsg.payload = {}; // Initialize the payload object\n\nkeys.forEach(key => {\n // Ignore keys that start with \"$\"\n if (!key.startsWith('$')) {\n msg.payload[key] = global.get(key);\n }\n});\n\nreturn msg;\n", - "outputs": 1, - "timeout": 0, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 120, - "y": 140, + "id": "39c7e4b018ce16cb", + "type": "reboot", + "z": "1b667c6443413ced", + "name": "", + "x": 1160, + "y": 200, "wires": [ - [ - "f514a138d38d3c61", - "4f5f150680be40da" - ] + [] ] }, { - "id": "927b92eee9edfcbe", - "type": "ui-template", - "z": "ab58b3fd0e6bcd77", - "group": "7572915171e440cd", - "page": "", - "ui": "", - "name": "Navigation Top", + "id": "1db1c2e3e19e85ff", + "type": "switch", + "z": "1b667c6443413ced", + "name": "", + "property": "payload", + "propertyType": "msg", + "rules": [ + { + "t": "eq", + "v": "reboot", + "vt": "str" + }, + { + "t": "eq", + "v": "shutdown", + "vt": "str" + } + ], + "checkall": "true", + "repair": false, + "outputs": 2, + "x": 970, + "y": 200, + "wires": [ + [ + "39c7e4b018ce16cb" + ], + [ + "8efc52e6ee9206f6" + ] + ] + }, + { + "id": "cd5cd3a0277b8dba", + "type": "ui-template", + "z": "1b667c6443413ced", + "group": "5d39a98563150f22", + "page": "", + "ui": "", + "name": "body", + "order": 1, + "width": "0", + "height": "0", + "head": "", + "format": "\n", + "storeOutMessages": true, + "passthru": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 510, + "y": 180, + "wires": [ + [] + ] + }, + { + "id": "0c0eb2a907745142", + "type": "debug", + "z": "1b667c6443413ced", + "name": "debug 15", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "true", + "targetType": "full", + "statusVal": "", + "statusType": "auto", + "x": 420, + "y": 380, + "wires": [] + }, + { + "id": "a57a165cd0ce511b", + "type": "ui-template", + "z": "ab58b3fd0e6bcd77", + "group": "58ab4d5e3dd68192", + "page": "", + "ui": "", + "name": "Streaming", + "order": 1, + "width": "6", + "height": "18", + "head": "", + "format": "
\n \n
\n\n\n", + "storeOutMessages": true, + "passthru": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 1310, + "y": 100, + "wires": [ + [] + ] + }, + { + "id": "c8de0186a0a927e8", + "type": "function", + "z": "ab58b3fd0e6bcd77", + "name": "Get Global Variables", + "func": "const keys = global.keys(); // Get all global variable keys\nmsg.payload = {}; // Initialize the payload object\n\nkeys.forEach(key => {\n // Ignore keys that start with \"$\"\n if (!key.startsWith('$')) {\n msg.payload[key] = global.get(key);\n }\n});\n\nreturn msg;\n", + "outputs": 1, + "timeout": 0, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 120, + "y": 140, + "wires": [ + [ + "f514a138d38d3c61", + "4f5f150680be40da" + ] + ] + }, + { + "id": "927b92eee9edfcbe", + "type": "ui-template", + "z": "ab58b3fd0e6bcd77", + "group": "7572915171e440cd", + "page": "", + "ui": "", + "name": "Navigation Top", "order": 1, "width": 0, "height": 0, @@ -2564,7 +2560,7 @@ "width": "0", "height": "0", "head": "", - "format": "\n\n\n\n", + "format": "\n\n\n\n", "storeOutMessages": true, "passthru": true, "resendOnRefresh": true, @@ -3269,7 +3265,7 @@ "width": 0, "height": 0, "head": "", - "format": "\n\n\n\n\n", + "format": "\n\n\n\n", "storeOutMessages": true, "passthru": true, "resendOnRefresh": true, @@ -3333,8 +3329,7 @@ "y": 140, "wires": [ [ - "f841ebf999dd8cb3", - "e9760c98e9340f3e" + "f841ebf999dd8cb3" ], [ "09ecc922fe2214a8" @@ -3346,7 +3341,7 @@ "type": "function", "z": "35d7387466dd0bc0", "name": "set acq_params", - "func": "if (msg.topic) {\n global.set(\"acq_id\", msg.payload.acq_id);\n global.set(\"acq_nb_frame\", msg.payload.acq_nb_frame);\n global.set(\"acq_interframe_volume\", msg.payload.acq_interframe_volume);\n global.set(\"acq_imaged_volume\", msg.payload.acq_imaged_volume);\n global.set(\"acq_pumped_volume\", msg.payload.acq_pumped_volume);\n global.set(\"acq_comment\", msg.payload.acq_comment);\n global.set(\"acq_progression\", msg.payload.acq_progression);\n global.set(\"acq_duration_left\", msg.payload.acq_duration_left);\n global.set(\"acq_status\", msg.payload.acq_status);\n}\nreturn msg;\n", + "func": "if (msg.topic) {\n global.set(\"acq_id\", msg.payload.acq_id);\n global.set(\"acq_nb_frame\", msg.payload.acq_nb_frame);\n global.set(\"acq_interframe_volume\", msg.payload.acq_interframe_volume);\n global.set(\"acq_imaged_volume\", msg.payload.acq_imaged_volume);\n global.set(\"acq_pumped_volume\", msg.payload.acq_pumped_volume);\n global.set(\"acq_comment\", msg.payload.acq_comment);\n global.set(\"acq_progression\", msg.payload.acq_progression);\n global.set(\"acq_duration_left\", msg.payload.acq_duration_left);\n global.set(\"acq_start_timestamp\", msg.payload.acq_start_timestamp);\n \n}\nreturn msg;\n", "outputs": 1, "timeout": "", "noerr": 0, @@ -3459,8 +3454,7 @@ "y": 200, "wires": [ [ - "aa6924c7e0aff26c", - "b373abca8b67ca64" + "aa6924c7e0aff26c" ] ] }, @@ -3585,7 +3579,7 @@ "type": "function", "z": "35d7387466dd0bc0", "name": "start acquisition", - "func": "const acq_interframe_volume = global.get(\"acq_interframe_volume\") || 0;\nconst acq_nb_frame = global.get(\"acq_nb_frame\") || 0;\n\n\n// Crée le payload final\nmsg.payload = {\n action: \"image\",\n pump_direction: \"FORWARD\",\n volume: acq_interframe_volume/1000,\n nb_frame: acq_nb_frame,\n sleep: 0.1\n};\n\nreturn msg;\n", + "func": "const acq_interframe_volume = global.get(\"acq_interframe_volume\") || 0;\nconst acq_nb_frame = global.get(\"acq_nb_frame\") || 0;\n\n\n// Crée le payload final\nmsg.payload = {\n action: \"image\",\n pump_direction: \"FORWARD\",\n volume: acq_interframe_volume,\n nb_frame: acq_nb_frame,\n sleep: 0.1\n};\n\nreturn msg;\n", "outputs": 1, "timeout": 0, "noerr": 0, @@ -3596,8 +3590,7 @@ "y": 280, "wires": [ [ - "ce5cca3b8fe34379", - "5090913821c57c81" + "ce5cca3b8fe34379" ] ] }, @@ -3626,57 +3619,6 @@ ] ] }, - { - "id": "b373abca8b67ca64", - "type": "debug", - "z": "35d7387466dd0bc0", - "name": "debug 11", - "active": true, - "tosidebar": true, - "console": false, - "tostatus": false, - "complete": "payload", - "targetType": "msg", - "statusVal": "", - "statusType": "auto", - "x": 520, - "y": 340, - "wires": [] - }, - { - "id": "5090913821c57c81", - "type": "debug", - "z": "35d7387466dd0bc0", - "name": "debug 13", - "active": true, - "tosidebar": true, - "console": false, - "tostatus": false, - "complete": "payload", - "targetType": "msg", - "statusVal": "", - "statusType": "auto", - "x": 940, - "y": 340, - "wires": [] - }, - { - "id": "e9760c98e9340f3e", - "type": "debug", - "z": "35d7387466dd0bc0", - "name": "debug 14", - "active": true, - "tosidebar": true, - "console": false, - "tostatus": false, - "complete": "payload", - "targetType": "msg", - "statusVal": "", - "statusType": "auto", - "x": 920, - "y": 80, - "wires": [] - }, { "id": "250979b4672d81b6", "type": "ui-event", @@ -3749,10 +3691,10 @@ "group": "bfd4acb7b243514f", "name": "List of acq", "label": "", - "order": 2, + "order": 1, "width": 0, "height": 0, - "maxrows": "10", + "maxrows": "100", "passthru": false, "autocols": false, "showSearch": false, @@ -3928,8 +3870,7 @@ "wires": [ [ "4bdd8a4afcdb704b", - "f8f4b34842d35713", - "621e28a4a222b99a" + "f8f4b34842d35713" ] ] }, @@ -4054,58 +3995,23 @@ "initialize": "", "finalize": "", "libs": [], - "x": 540, - "y": 460, + "x": 680, + "y": 140, "wires": [ [ - "7b77659569034650", "26074b8d8ad8abf9" ] ] }, - { - "id": "621e28a4a222b99a", - "type": "debug", - "z": "0fd76ac156d78937", - "name": "debug 6", - "active": true, - "tosidebar": true, - "console": false, - "tostatus": false, - "complete": "true", - "targetType": "full", - "statusVal": "", - "statusType": "auto", - "x": 1100, - "y": 180, - "wires": [] - }, - { - "id": "7b77659569034650", - "type": "debug", - "z": "0fd76ac156d78937", - "name": "debug 5", - "active": true, - "tosidebar": true, - "console": false, - "tostatus": false, - "complete": "true", - "targetType": "full", - "statusVal": "", - "statusType": "auto", - "x": 860, - "y": 200, - "wires": [] - }, { "id": "26074b8d8ad8abf9", "type": "ui-template", "z": "0fd76ac156d78937", - "group": "bfd4acb7b243514f", - "page": "", + "group": "", + "page": "7a4e042a60b734a6", "ui": "", "name": "Dialog", - "order": 1, + "order": 2, "width": 0, "height": 0, "head": "", @@ -4113,9 +4019,9 @@ "storeOutMessages": true, "passthru": true, "resendOnRefresh": true, - "templateScope": "local", + "templateScope": "widget:page", "className": "", - "x": 750, + "x": 850, "y": 140, "wires": [ [ @@ -4234,11 +4140,9 @@ ] }, { - "id": "f530763503fdc7d1", + "id": "62f69ab05b710c7c", "type": "mqtt in", "z": "14f8c9b5ce1235cc", - "d": true, - "g": "979647e2e34284fb", "name": "", "topic": "status/#", "qos": "0", @@ -4247,67 +4151,18 @@ "nl": false, "rap": false, "inputs": 0, - "x": 100, - "y": 320, - "wires": [ - [ - "f0313532a2b77337" - ] - ] - }, - { - "id": "e5a0432fc7b35b29", - "type": "function", - "z": "14f8c9b5ce1235cc", - "d": true, - "g": "979647e2e34284fb", - "name": "Count objects segmented", - "func": "var objectCount = global.get('objectCount') || 0;\n\nif (msg.payload.status === 'Done') {\n msg.payload.objectCount = objectCount;\n objectCount=0;\n }\nelse if (msg.topic === 'status/segmenter/metric') {\n objectCount++;\n}\n\nglobal.set('objectCount', objectCount);\n\nreturn msg;", - "outputs": 1, - "timeout": 0, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 410, - "y": 320, - "wires": [ - [ - "ba8045b3448d5bbf" - ] - ] - }, - { - "id": "ba8045b3448d5bbf", - "type": "switch", - "z": "14f8c9b5ce1235cc", - "d": true, - "g": "979647e2e34284fb", - "name": "msg.payload.metadata.equivalent_diameter is not null", - "property": "payload.metadata.equivalent_diameter", - "propertyType": "msg", - "rules": [ - { - "t": "nnull" - } - ], - "checkall": "true", - "repair": false, - "outputs": 1, - "x": 740, - "y": 320, + "x": 200, + "y": 960, "wires": [ [ - "68fe28f50beb2add" + "e2269afad765ca6e" ] ] }, { - "id": "f0313532a2b77337", + "id": "e2269afad765ca6e", "type": "switch", "z": "14f8c9b5ce1235cc", - "d": true, - "g": "979647e2e34284fb", "name": "", "property": "topic", "propertyType": "msg", @@ -4321,21 +4176,19 @@ "checkall": "true", "repair": false, "outputs": 1, - "x": 230, - "y": 320, + "x": 350, + "y": 960, "wires": [ [ - "e5a0432fc7b35b29", - "c6c66ebc50dd5d90" + "6c0e567b12e22791", + "fae799ce36e737e1" ] ] }, { - "id": "e045f6939008f23f", + "id": "6c0e567b12e22791", "type": "ui-chart", "z": "14f8c9b5ce1235cc", - "d": true, - "g": "979647e2e34284fb", "group": "bdcb68c6cff2da73", "name": "", "label": "Histogram - Equivalent Diameter", @@ -4349,14 +4202,14 @@ "xAxisType": "bins", "xAxisFormat": "", "xAxisFormatType": "auto", - "xmin": "20", - "xmax": "100", + "xmin": "0", + "xmax": "300", "yAxisLabel": "Abundance", "yAxisProperty": "payload.metadata.equivalent_diameter", "yAxisPropertyType": "msg", "ymin": "", "ymax": "", - "bins": "80", + "bins": "10", "action": "append", "stackSeries": false, "pointShape": "circle", @@ -4388,184 +4241,17 @@ "height": "6", "className": "", "interpolation": "linear", - "x": 1020, - "y": 500, - "wires": [ - [] - ] - }, - { - "id": "68fe28f50beb2add", - "type": "function", - "z": "14f8c9b5ce1235cc", - "d": true, - "g": "979647e2e34284fb", - "name": "convert px to um", - "func": "// Conversion factor from pixels to micrometers (µm)\nconst px_to_um = 0.75;\n\n// Convert relevant measurements\nmsg.payload.metadata.equivalent_diameter = Math.round(msg.payload.metadata.equivalent_diameter * px_to_um);\nmsg.payload.metadata.width = Math.round(msg.payload.metadata.width * px_to_um);\nmsg.payload.metadata.height = Math.round(msg.payload.metadata.height * px_to_um);\nmsg.payload.metadata.area = Math.round(msg.payload.metadata.area * (px_to_um ** 2)); // Area is in square units\nmsg.payload.metadata.area_exc = Math.round(msg.payload.metadata.area_exc * (px_to_um ** 2));\nmsg.payload.metadata.convex_area = Math.round(msg.payload.metadata.convex_area * (px_to_um ** 2));\nmsg.payload.metadata.major = Math.round(msg.payload.metadata.major * px_to_um);\nmsg.payload.metadata.minor = Math.round(msg.payload.metadata.minor * px_to_um);\nmsg.payload.metadata.perim = Math.round(msg.payload.metadata[\"perim.\"] * px_to_um); // Ensure proper key reference\nmsg.payload.metadata.bounding_box_area = Math.round(msg.payload.metadata.bounding_box_area * (px_to_um ** 2));\n\nreturn msg;\n", - "outputs": 1, - "timeout": 0, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 150, - "y": 520, - "wires": [ - [ - "e045f6939008f23f", - "693e1ad09770bdf5", - "7433aae2400b7f78", - "a90099cfe7a37a37", - "df0dcb1175b8bd5f" - ] - ] - }, - { - "id": "a20376f0ac08af8e", - "type": "ui-markdown", - "z": "14f8c9b5ce1235cc", - "d": true, - "g": "979647e2e34284fb", - "group": "bdcb68c6cff2da73", - "name": "Histogram of equivalent_diameter", - "order": 3, - "width": "3", - "height": "6", - "content": "### Distribution of Object Sizes \nUnderstanding the size distribution of planktonic objects helps identify dominant size classes and detect anomalies. A skewed distribution might indicate specific biological trends, such as a bloom of a particular species or a technical issue in the acquisition process. \n\n*In the graph:* \n- A **peak in smaller sizes** suggests dominance of tiny plankton. \n- A **broad distribution** indicates a mix of various size classes. \n- A **skewed or bimodal pattern** may reflect biological or technical factors (e.g., species-specific blooms or imaging biases).\n", - "className": "", - "x": 1020, - "y": 540, - "wires": [ - [] - ] - }, - { - "id": "694187e7d85faec3", - "type": "ui-markdown", - "z": "14f8c9b5ce1235cc", - "d": true, - "g": "979647e2e34284fb", - "group": "bdcb68c6cff2da73", - "name": "elongation vs. area", - "order": 4, - "width": "3", - "height": "6", - "content": "### Relationship Between Elongation and Object Area \nThis scatter plot helps assess whether larger objects tend to be more elongated or compact. Certain plankton species have characteristic shapes, and identifying correlations between elongation and area can aid in distinguishing different taxa or understanding morphological variability. \n\n*In the graph:* \n- **Top-right**: Large and highly elongated objects (e.g., filamentous plankton). \n- **Bottom-right**: Small but highly elongated objects (e.g., thin needle-like forms). \n- **Top-left**: Large but compact objects (e.g., rounded or clustered plankton). \n- **Bottom-left**: Small and compact objects (e.g., spherical or short structures).", - "className": "", - "x": 970, - "y": 620, - "wires": [ - [] - ] - }, - { - "id": "693e1ad09770bdf5", - "type": "ui-chart", - "z": "14f8c9b5ce1235cc", - "d": true, - "g": "979647e2e34284fb", - "group": "bdcb68c6cff2da73", - "name": "elongation vs. area", - "label": "elongation vs. area", - "order": 2, - "chartType": "scatter", - "category": "elongation vs. area", - "categoryType": "str", - "xAxisLabel": "elongation (ratio of major to minor axis)", - "xAxisProperty": "payload.metadata.elongation", - "xAxisPropertyType": "msg", - "xAxisType": "linear", - "xAxisFormat": "", - "xAxisFormatType": "auto", - "xmin": "0", - "xmax": "50", - "yAxisLabel": "area (μm²)", - "yAxisProperty": "payload.metadata.area", - "yAxisPropertyType": "msg", - "ymin": "300", - "ymax": "1500", - "bins": 10, - "action": "append", - "stackSeries": true, - "pointShape": "circle", - "pointRadius": "1", - "showLegend": true, - "removeOlder": "1", - "removeOlderUnit": "3600", - "removeOlderPoints": "300", - "colors": [ - "#ff0088", - "#ff0000", - "#ff7f0e", - "#2ca02c", - "#a347e1", - "#d62728", - "#ff9896", - "#9467bd", - "#c5b0d5" - ], - "textColor": [ - "#666666" - ], - "textColorDefault": true, - "gridColor": [ - "#e5e5e5" - ], - "gridColorDefault": true, - "width": "3", - "height": "6", - "className": "", - "interpolation": "linear", - "x": 970, - "y": 580, - "wires": [ - [] - ] - }, - { - "id": "82a3257cd0a3815d", - "type": "ui-markdown", - "z": "14f8c9b5ce1235cc", - "d": true, - "g": "979647e2e34284fb", - "group": "fa6393a7d7e3b7d7", - "name": "equivalent_diameter vs. MeanValue", - "order": 3, - "width": "3", - "height": "6", - "content": "### Variation of Mean Brightness with Equivalent Diameter \nThis graph explores whether larger objects tend to be brighter or darker. This can reveal differences in pigmentation, transparency, or even imaging inconsistencies. \n\n*In the graph:* \n- **Top-right**: Large and bright objects.\n- **Top-left**: Small and bright objects.\n- **Bottom-right**: Large and dark objects.\n- **Bottom-left**: Small and dark objects. ", - "className": "", - "x": 560, - "y": 700, - "wires": [ - [] - ] - }, - { - "id": "44ef242ece9b6b4b", - "type": "ui-markdown", - "z": "14f8c9b5ce1235cc", - "d": true, - "g": "979647e2e34284fb", - "group": "fa6393a7d7e3b7d7", - "name": "MeanSaturation vs. MeanValue", - "order": 4, - "width": "3", - "height": "6", - "content": "### Relationship Between Mean Saturation and Mean Brightness\nThis visualization helps understand how color properties vary with brightness. Some plankton groups exhibit distinct coloration, and saturation could be a useful feature for classification. A strong trend might suggest systematic differences in pigmentation or optical properties across plankton groups.\n\n*In the graph:* \n- **Top-right**: Bright and highly saturated objects (vivid colors). \n- **Top-left**: Bright but low-saturation objects (pale or near-white). \n- **Bottom-right**: Dark but highly saturated objects (deep, rich colors). \n- **Bottom-left**: Dark and low-saturation objects (grayish or faintly colored).", - "className": "", - "x": 550, - "y": 780, + "x": 580, + "y": 960, "wires": [ [] ] }, { - "id": "7433aae2400b7f78", + "id": "fae799ce36e737e1", "type": "ui-chart", "z": "14f8c9b5ce1235cc", "d": true, - "g": "979647e2e34284fb", "group": "fa6393a7d7e3b7d7", "name": "equivalent_diameter vs. MeanValue", "label": "equivalent_diameter vs. MeanValue", @@ -4574,7 +4260,7 @@ "category": "equivalent_diameter vs. MeanValue", "categoryType": "str", "xAxisLabel": "Equivalent Diameter (μm)", - "xAxisProperty": "payload.metadata.equivalent_diameter", + "xAxisProperty": "payload.metadata.x", "xAxisPropertyType": "msg", "xAxisType": "linear", "xAxisFormat": "", @@ -4582,7 +4268,7 @@ "xmin": "0", "xmax": "50", "yAxisLabel": "MeanValue (0–255, grayscale value)", - "yAxisProperty": "payload.metadata.MeanValue", + "yAxisProperty": "payload.metadata.y", "yAxisPropertyType": "msg", "ymin": "150", "ymax": "255", @@ -4618,161 +4304,8 @@ "height": "6", "className": "", "interpolation": "linear", - "x": 560, - "y": 660, - "wires": [ - [] - ] - }, - { - "id": "a90099cfe7a37a37", - "type": "ui-chart", - "z": "14f8c9b5ce1235cc", - "d": true, - "g": "979647e2e34284fb", - "group": "fa6393a7d7e3b7d7", - "name": "MeanSaturation vs. MeanValue", - "label": "MeanSaturation vs. MeanValue", - "order": 2, - "chartType": "scatter", - "category": "MeanSaturation vs. MeanValue", - "categoryType": "str", - "xAxisLabel": "MeanSaturation (%)", - "xAxisProperty": "payload.metadata.MeanSaturation", - "xAxisPropertyType": "msg", - "xAxisType": "linear", - "xAxisFormat": "", - "xAxisFormatType": "auto", - "xmin": "0", - "xmax": "50", - "yAxisLabel": "MeanValue (0–255, grayscale value)", - "yAxisProperty": "payload.metadata.MeanValue", - "yAxisPropertyType": "msg", - "ymin": "150", - "ymax": "255", - "bins": 10, - "action": "append", - "stackSeries": true, - "pointShape": "circle", - "pointRadius": "1", - "showLegend": true, - "removeOlder": "1", - "removeOlderUnit": "3600", - "removeOlderPoints": "300", - "colors": [ - "#ff9500", - "#ff0000", - "#ff7f0e", - "#2ca02c", - "#a347e1", - "#d62728", - "#ff9896", - "#9467bd", - "#c5b0d5" - ], - "textColor": [ - "#666666" - ], - "textColorDefault": true, - "gridColor": [ - "#e5e5e5" - ], - "gridColorDefault": true, - "width": "3", - "height": "6", - "className": "", - "interpolation": "linear", - "x": 550, - "y": 740, - "wires": [ - [] - ] - }, - { - "id": "df0dcb1175b8bd5f", - "type": "ui-chart", - "z": "14f8c9b5ce1235cc", - "d": true, - "g": "979647e2e34284fb", - "group": "ed357f41cdf951bc", - "name": "x vs. y", - "label": "x vs. y", - "order": 1, - "chartType": "scatter", - "category": "x vs. y", - "categoryType": "str", - "xAxisLabel": "x", - "xAxisProperty": "payload.metadata.x", - "xAxisPropertyType": "msg", - "xAxisType": "linear", - "xAxisFormat": "", - "xAxisFormatType": "auto", - "xmin": "0", - "xmax": "50", - "yAxisLabel": "y", - "yAxisProperty": "payload.metadata.y", - "yAxisPropertyType": "msg", - "ymin": "", - "ymax": "", - "bins": 10, - "action": "append", - "stackSeries": true, - "pointShape": "circle", - "pointRadius": "1", - "showLegend": true, - "removeOlder": "1", - "removeOlderUnit": "3600", - "removeOlderPoints": "300", - "colors": [ - "#2b00ff", - "#ff0000", - "#ff7f0e", - "#2ca02c", - "#a347e1", - "#d62728", - "#ff9896", - "#9467bd", - "#c5b0d5" - ], - "textColor": [ - "#666666" - ], - "textColorDefault": true, - "gridColor": [ - "#e5e5e5" - ], - "gridColorDefault": true, - "width": "6", - "height": "6", - "className": "", - "interpolation": "linear", - "x": 930, - "y": 440, - "wires": [ - [] - ] - }, - { - "id": "c6c66ebc50dd5d90", - "type": "switch", - "z": "14f8c9b5ce1235cc", - "d": true, - "g": "979647e2e34284fb", - "name": "", - "property": "payload.status", - "propertyType": "msg", - "rules": [ - { - "t": "eq", - "v": "Done", - "vt": "str" - } - ], - "checkall": "true", - "repair": false, - "outputs": 1, - "x": 430, - "y": 380, + "x": 580, + "y": 1160, "wires": [ [] ] @@ -4851,7 +4384,7 @@ "width": 0, "height": 0, "head": "", - "format": "\n\n", + "format": "\n\n", "storeOutMessages": true, "passthru": true, "resendOnRefresh": true, @@ -4875,7 +4408,7 @@ "width": 0, "height": 0, "head": "", - "format": "\n\n", + "format": "\n\n", "storeOutMessages": true, "passthru": true, "resendOnRefresh": true, @@ -5036,7 +4569,7 @@ "width": "12", "height": "6", "head": "", - "format": "\n", + "format": "\n", "storeOutMessages": true, "passthru": true, "resendOnRefresh": true, @@ -5186,7 +4719,7 @@ "width": "0", "height": "0", "head": "", - "format": "\n\n\n\n\n", + "format": "\n\n\n\n\n", "storeOutMessages": true, "passthru": true, "resendOnRefresh": true, @@ -5586,7 +5119,7 @@ "width": "0", "height": "0", "head": "", - "format": "\n\n\n\n\n", + "format": "\n\n\n\n\n\n", "storeOutMessages": true, "passthru": true, "resendOnRefresh": true, From 7efebd5915dbf660f846a21fa2c4d8bcc77db43a Mon Sep 17 00:00:00 2001 From: Thibaut Pollina Date: Thu, 20 Nov 2025 16:02:24 +0000 Subject: [PATCH 05/36] update --- node-red/projects/dashboard/flows.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/node-red/projects/dashboard/flows.json b/node-red/projects/dashboard/flows.json index b49566b7e..f17ecce34 100644 --- a/node-red/projects/dashboard/flows.json +++ b/node-red/projects/dashboard/flows.json @@ -1970,7 +1970,7 @@ "width": 0, "height": 0, "head": "", - "format": "\n\n\n", + "format": "\n\n\n", "storeOutMessages": true, "passthru": true, "resendOnRefresh": true, @@ -2193,7 +2193,7 @@ "width": "12", "height": "6", "head": "", - "format": "\n\n\n\n\n\n", + "format": "\n\n\n\n\n\n", "storeOutMessages": true, "passthru": true, "resendOnRefresh": true, @@ -2274,7 +2274,7 @@ "width": "0", "height": "0", "head": "", - "format": "\n", + "format": "\n", "storeOutMessages": true, "passthru": true, "resendOnRefresh": true, From cab29be47f942218132e076f5bebd0add718dda3 Mon Sep 17 00:00:00 2001 From: Thibaut Pollina Date: Thu, 20 Nov 2025 21:52:55 +0000 Subject: [PATCH 06/36] update --- node-red/projects/dashboard/flows.json | 1369 +++--------------------- 1 file changed, 126 insertions(+), 1243 deletions(-) diff --git a/node-red/projects/dashboard/flows.json b/node-red/projects/dashboard/flows.json index f17ecce34..4d36a872a 100644 --- a/node-red/projects/dashboard/flows.json +++ b/node-red/projects/dashboard/flows.json @@ -47,14 +47,6 @@ "info": "", "env": [] }, - { - "id": "2d14eef8a9f51595", - "type": "tab", - "label": "Export", - "disabled": false, - "info": "", - "env": [] - }, { "id": "8555b76c53e789e0", "type": "tab", @@ -111,22 +103,6 @@ "info": "", "env": [] }, - { - "id": "807baf16b84dfb29", - "type": "tab", - "label": "Settings", - "disabled": false, - "info": "", - "env": [] - }, - { - "id": "1b99100829ae7f41", - "type": "tab", - "label": "Hardware", - "disabled": false, - "info": "", - "env": [] - }, { "id": "4ed26b8b.253504", "type": "subflow", @@ -921,7 +897,7 @@ "cols": "12" } ], - "order": 10, + "order": 9, "className": "", "visible": "false", "disabled": "false" @@ -971,7 +947,7 @@ "cols": "12" } ], - "order": 11, + "order": 10, "className": "", "visible": "false", "disabled": "false" @@ -1021,7 +997,7 @@ "cols": "12" } ], - "order": 12, + "order": 11, "className": "", "visible": "false", "disabled": "false" @@ -1071,7 +1047,7 @@ "cols": "12" } ], - "order": 9, + "order": 8, "className": "", "visible": "false", "disabled": "false" @@ -1121,7 +1097,7 @@ "cols": "12" } ], - "order": 8, + "order": 7, "className": "", "visible": "true", "disabled": "false" @@ -1473,70 +1449,6 @@ "page": "d129fac8e7742d5b", "width": "12", "height": 1, - "order": 4, - "showTitle": false, - "className": "", - "visible": "true", - "disabled": "false", - "groupType": "default" - }, - { - "id": "d9e5a1ba9059ca02", - "type": "ui-page", - "name": "Export", - "ui": "e6ae26617c24c3ea", - "path": "/export", - "icon": "export", - "layout": "grid", - "theme": "f7770f0b818c3a67", - "breakpoints": [ - { - "name": "Default", - "px": "0", - "cols": "3" - }, - { - "name": "Tablet", - "px": "576", - "cols": "6" - }, - { - "name": "Small Desktop", - "px": "768", - "cols": "9" - }, - { - "name": "Desktop", - "px": "1024", - "cols": "12" - } - ], - "order": 7, - "className": "", - "visible": "true", - "disabled": "false" - }, - { - "id": "1c2cd2bf4d4ab815", - "type": "ui-group", - "name": "Navigation Top", - "page": "d9e5a1ba9059ca02", - "width": "12", - "height": 1, - "order": 1, - "showTitle": false, - "className": "", - "visible": "true", - "disabled": "false", - "groupType": "default" - }, - { - "id": "5d3c24612267b256", - "type": "ui-group", - "name": "Navigation bottom", - "page": "d9e5a1ba9059ca02", - "width": "12", - "height": 1, "order": 3, "showTitle": false, "className": "", @@ -1544,20 +1456,6 @@ "disabled": "false", "groupType": "default" }, - { - "id": "d1c2dcd580716d02", - "type": "ui-group", - "name": "Viewer", - "page": "d9e5a1ba9059ca02", - "width": "12", - "height": 1, - "order": 2, - "showTitle": false, - "className": "", - "visible": "true", - "disabled": "false", - "groupType": "default" - }, { "id": "402b3d24c87ab0d2", "type": "ui-group", @@ -1572,112 +1470,12 @@ "disabled": "false", "groupType": "default" }, - { - "id": "5447fb7a420dfdc6", - "type": "ui-page", - "name": "Settings", - "ui": "e6ae26617c24c3ea", - "path": "/settings", - "icon": "cog", - "layout": "grid", - "theme": "f7770f0b818c3a67", - "breakpoints": [ - { - "name": "Default", - "px": "0", - "cols": "3" - }, - { - "name": "Tablet", - "px": "576", - "cols": "6" - }, - { - "name": "Small Desktop", - "px": "768", - "cols": "9" - }, - { - "name": "Desktop", - "px": "1024", - "cols": "12" - } - ], - "order": 13, - "className": "", - "visible": "true", - "disabled": "false" - }, - { - "id": "070185f2b69cf66d", - "type": "ui-group", - "name": "admin", - "page": "5447fb7a420dfdc6", - "width": "12", - "height": 1, - "order": 1, - "showTitle": false, - "className": "", - "visible": "true", - "disabled": "false", - "groupType": "default" - }, - { - "id": "e11ae78aee22fab5", - "type": "ui-page", - "name": "Hardware", - "ui": "e6ae26617c24c3ea", - "path": "/hardware", - "icon": "robot", - "layout": "grid", - "theme": "f7770f0b818c3a67", - "breakpoints": [ - { - "name": "Default", - "px": "0", - "cols": "3" - }, - { - "name": "Tablet", - "px": "576", - "cols": "6" - }, - { - "name": "Small Desktop", - "px": "768", - "cols": "9" - }, - { - "name": "Desktop", - "px": "1024", - "cols": "12" - } - ], - "order": 14, - "className": "", - "visible": "true", - "disabled": "false" - }, - { - "id": "9d212165f2367f01", - "type": "ui-group", - "name": "Group 51", - "page": "e11ae78aee22fab5", - "width": 6, - "height": 1, - "order": 1, - "showTitle": false, - "className": "", - "visible": "true", - "disabled": "false", - "groupType": "default" - }, { "id": "fa6393a7d7e3b7d7", "type": "ui-group", - "name": "Plots Left", + "name": "Table", "page": "d129fac8e7742d5b", - "width": 6, + "width": "12", "height": 1, "order": 2, "showTitle": false, @@ -1686,20 +1484,6 @@ "disabled": "false", "groupType": "default" }, - { - "id": "bdcb68c6cff2da73", - "type": "ui-group", - "name": "Plot Right", - "page": "d129fac8e7742d5b", - "width": 6, - "height": 1, - "order": 3, - "showTitle": false, - "className": "", - "visible": "true", - "disabled": "false", - "groupType": "default" - }, { "id": "82099021.9ceb08", "type": "file", @@ -2193,7 +1977,7 @@ "width": "12", "height": "6", "head": "", - "format": "\n\n\n\n\n\n", + "format": "\n\n\n\n\n\n", "storeOutMessages": true, "passthru": true, "resendOnRefresh": true, @@ -2274,7 +2058,7 @@ "width": "0", "height": "0", "head": "", - "format": "\n", + "format": "\n", "storeOutMessages": true, "passthru": true, "resendOnRefresh": true, @@ -2299,8 +2083,8 @@ "targetType": "full", "statusVal": "", "statusType": "auto", - "x": 420, - "y": 380, + "x": 520, + "y": 280, "wires": [] }, { @@ -3844,7 +3628,7 @@ "correl": "", "expiry": "", "broker": "8dc3722c.06efa8", - "x": 1090, + "x": 1150, "y": 140, "wires": [] }, @@ -3865,7 +3649,7 @@ "checkall": "true", "repair": false, "outputs": 1, - "x": 970, + "x": 1030, "y": 140, "wires": [ [ @@ -3890,7 +3674,8 @@ "y": 260, "wires": [ [ - "33c0d1b8251b5d00" + "33c0d1b8251b5d00", + "eed6aba5c7ab3b6c" ] ] }, @@ -3912,7 +3697,7 @@ "resendOnRefresh": true, "templateScope": "local", "className": "", - "x": 530, + "x": 610, "y": 240, "wires": [ [] @@ -3957,7 +3742,7 @@ "initialize": "", "finalize": "", "libs": [], - "x": 1120, + "x": 1180, "y": 100, "wires": [ [] @@ -3999,12 +3784,29 @@ "y": 140, "wires": [ [ - "26074b8d8ad8abf9" + "7fde0897e436c334" ] ] }, { - "id": "26074b8d8ad8abf9", + "id": "eed6aba5c7ab3b6c", + "type": "debug", + "z": "0fd76ac156d78937", + "name": "debug 5", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "true", + "targetType": "full", + "statusVal": "", + "statusType": "auto", + "x": 320, + "y": 460, + "wires": [] + }, + { + "id": "7fde0897e436c334", "type": "ui-template", "z": "0fd76ac156d78937", "group": "", @@ -4015,7 +3817,7 @@ "width": 0, "height": 0, "head": "", - "format": "\n\n\n", + "format": "\n\n\n", "storeOutMessages": true, "passthru": true, "resendOnRefresh": true, @@ -4088,7 +3890,9 @@ "x": 590, "y": 40, "wires": [ - [] + [ + "164baa371893de7a" + ] ] }, { @@ -4103,7 +3907,7 @@ "width": 0, "height": 0, "head": "", - "format": "\n\n", + "format": "\n\n", "storeOutMessages": true, "passthru": true, "resendOnRefresh": true, @@ -4127,7 +3931,7 @@ "width": 0, "height": 0, "head": "", - "format": "\n\n", + "format": "\n\n", "storeOutMessages": true, "passthru": true, "resendOnRefresh": true, @@ -4140,310 +3944,113 @@ ] }, { - "id": "62f69ab05b710c7c", - "type": "mqtt in", + "id": "164baa371893de7a", + "type": "list segmentations", "z": "14f8c9b5ce1235cc", "name": "", - "topic": "status/#", - "qos": "0", - "datatype": "json", - "broker": "8dc3722c.06efa8", - "nl": false, - "rap": false, - "inputs": 0, - "x": 200, - "y": 960, + "x": 170, + "y": 180, "wires": [ [ - "e2269afad765ca6e" + "18a10804663dbaa1" ] ] }, { - "id": "e2269afad765ca6e", - "type": "switch", + "id": "6532974092861ef7", + "type": "ui-table", "z": "14f8c9b5ce1235cc", - "name": "", - "property": "topic", - "propertyType": "msg", - "rules": [ + "group": "fa6393a7d7e3b7d7", + "name": "List of acq", + "label": "", + "order": 1, + "width": 0, + "height": 0, + "maxrows": "10", + "passthru": false, + "autocols": false, + "showSearch": true, + "deselect": true, + "selectionType": "click", + "columns": [ { - "t": "eq", - "v": "status/segmenter/metric", - "vt": "str" - } - ], - "checkall": "true", - "repair": false, - "outputs": 1, - "x": 350, - "y": 960, - "wires": [ - [ - "6c0e567b12e22791", - "fae799ce36e737e1" - ] - ] - }, - { - "id": "6c0e567b12e22791", - "type": "ui-chart", - "z": "14f8c9b5ce1235cc", - "group": "bdcb68c6cff2da73", - "name": "", - "label": "Histogram - Equivalent Diameter", - "order": 1, - "chartType": "histogram", - "category": "Equivalent Diameter (μm)", - "categoryType": "str", - "xAxisLabel": "Equivalent Diameter (μm)", - "xAxisProperty": "payload.metadata.equivalent_diameter", - "xAxisPropertyType": "msg", - "xAxisType": "bins", - "xAxisFormat": "", - "xAxisFormatType": "auto", - "xmin": "0", - "xmax": "300", - "yAxisLabel": "Abundance", - "yAxisProperty": "payload.metadata.equivalent_diameter", - "yAxisPropertyType": "msg", - "ymin": "", - "ymax": "", - "bins": "10", - "action": "append", - "stackSeries": false, - "pointShape": "circle", - "pointRadius": "1", - "showLegend": true, - "removeOlder": 1, - "removeOlderUnit": "86400", - "removeOlderPoints": "", - "colors": [ - "#00ff04", - "#ff0000", - "#ff7f0e", - "#2ca02c", - "#a347e1", - "#d62728", - "#ff9896", - "#9467bd", - "#c5b0d5" - ], - "textColor": [ - "#666666" - ], - "textColorDefault": true, - "gridColor": [ - "#e5e5e5" + "title": "Project name", + "key": "project_name", + "keyType": "key", + "type": "text", + "width": "", + "align": "start" + }, + { + "title": "Sample ID", + "key": "sample_id", + "keyType": "key", + "type": "text", + "width": "", + "align": "start" + }, + { + "title": "Acquisition ID", + "key": "acquisition_id", + "keyType": "key", + "type": "text", + "width": "", + "align": "start" + }, + { + "title": "Number of objects", + "key": "image_acquired_count", + "keyType": "key", + "type": "text", + "width": "", + "align": "start" + }, + { + "title": "Gallery", + "key": "gallery", + "keyType": "key", + "type": "link", + "width": "", + "align": "start" + }, + { + "title": "Download", + "key": "export", + "keyType": "key", + "type": "link", + "width": "", + "align": "start" + } ], - "gridColorDefault": true, - "width": "3", - "height": "6", - "className": "", - "interpolation": "linear", - "x": 580, - "y": 960, + "mobileBreakpoint": "sm", + "mobileBreakpointType": "defaults", + "action": "replace", + "x": 650, + "y": 180, "wires": [ [] ] }, { - "id": "fae799ce36e737e1", - "type": "ui-chart", + "id": "18a10804663dbaa1", + "type": "function", "z": "14f8c9b5ce1235cc", - "d": true, - "group": "fa6393a7d7e3b7d7", - "name": "equivalent_diameter vs. MeanValue", - "label": "equivalent_diameter vs. MeanValue", - "order": 1, - "chartType": "scatter", - "category": "equivalent_diameter vs. MeanValue", - "categoryType": "str", - "xAxisLabel": "Equivalent Diameter (μm)", - "xAxisProperty": "payload.metadata.x", - "xAxisPropertyType": "msg", - "xAxisType": "linear", - "xAxisFormat": "", - "xAxisFormatType": "auto", - "xmin": "0", - "xmax": "50", - "yAxisLabel": "MeanValue (0–255, grayscale value)", - "yAxisProperty": "payload.metadata.y", - "yAxisPropertyType": "msg", - "ymin": "150", - "ymax": "255", - "bins": 10, - "action": "append", - "stackSeries": true, - "pointShape": "circle", - "pointRadius": "1", - "showLegend": true, - "removeOlder": "1", - "removeOlderUnit": "3600", - "removeOlderPoints": "300", - "colors": [ - "#8c00ff", - "#ff0000", - "#ff7f0e", - "#2ca02c", - "#a347e1", - "#d62728", - "#ff9896", - "#9467bd", - "#c5b0d5" - ], - "textColor": [ - "#666666" - ], - "textColorDefault": true, - "gridColor": [ - "#e5e5e5" - ], - "gridColorDefault": true, - "width": "3", - "height": "6", - "className": "", - "interpolation": "linear", - "x": 580, - "y": 1160, - "wires": [ - [] - ] - }, - { - "id": "64c27d4448d8f79f", - "type": "ui-event", - "z": "2d14eef8a9f51595", - "ui": "e6ae26617c24c3ea", - "name": "UI Event", - "x": 80, - "y": 40, - "wires": [ - [ - "b47026c6196ae54f" - ] - ] - }, - { - "id": "b47026c6196ae54f", - "type": "switch", - "z": "2d14eef8a9f51595", - "name": "msg.topic === \"$pageview\"", - "property": "topic", - "propertyType": "msg", - "rules": [ - { - "t": "eq", - "v": "$pageview", - "vt": "str" - } - ], - "checkall": "true", - "repair": false, + "name": "function 1", + "func": "msg.payload = msg.payload.map(item => {\n // extraction de l'acquisition_id\n const acq = item.acquisition_id; // ex: \"A_2\"\n\n // création du chemin export\n item.export = `/ps/data/browse/files/export/ecotaxa/ecotaxa_${acq}.zip`;\n\n return item;\n});\n\nreturn msg;\n", "outputs": 1, - "x": 280, - "y": 40, + "timeout": 0, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 360, + "y": 180, "wires": [ [ - "5676b794b1a00c6a" + "6532974092861ef7" ] ] }, - { - "id": "5676b794b1a00c6a", - "type": "switch", - "z": "2d14eef8a9f51595", - "name": "msg.payload.page.path === \"/export\"", - "property": "payload.page.path", - "propertyType": "msg", - "rules": [ - { - "t": "eq", - "v": "/export", - "vt": "str" - } - ], - "checkall": "true", - "repair": false, - "outputs": 1, - "x": 570, - "y": 40, - "wires": [ - [] - ] - }, - { - "id": "3cfcc4372e4a3fb2", - "type": "ui-template", - "z": "2d14eef8a9f51595", - "group": "5d3c24612267b256", - "page": "", - "ui": "", - "name": "Navigation Bottom", - "order": 1, - "width": 0, - "height": 0, - "head": "", - "format": "\n\n", - "storeOutMessages": true, - "passthru": true, - "resendOnRefresh": true, - "templateScope": "local", - "className": "", - "x": 1290, - "y": 160, - "wires": [ - [] - ] - }, - { - "id": "32d0387ec0cb419f", - "type": "ui-template", - "z": "2d14eef8a9f51595", - "group": "1c2cd2bf4d4ab815", - "page": "", - "ui": "", - "name": "Navigation Top", - "order": 1, - "width": 0, - "height": 0, - "head": "", - "format": "\n\n", - "storeOutMessages": true, - "passthru": true, - "resendOnRefresh": true, - "templateScope": "local", - "className": "", - "x": 1300, - "y": 40, - "wires": [ - [] - ] - }, - { - "id": "6521e33d1a35408e", - "type": "ui-template", - "z": "2d14eef8a9f51595", - "group": "d1c2dcd580716d02", - "page": "", - "ui": "", - "name": "Viewer", - "order": 1, - "width": "12", - "height": "16", - "head": "", - "format": " \n ", - "storeOutMessages": true, - "passthru": true, - "resendOnRefresh": true, - "templateScope": "local", - "className": "", - "x": 1330, - "y": 100, - "wires": [ - [] - ] - }, { "id": "64ae825a687fb054", "type": "ecotaxa", @@ -6151,729 +5758,5 @@ "x": 470, "y": 1100, "wires": [] - }, - { - "id": "7451ef4165b79c87", - "type": "exec", - "z": "807baf16b84dfb29", - "command": "sudo", - "addpay": "payload", - "append": "now", - "useSpawn": "false", - "timer": "2", - "winHide": false, - "oldrc": false, - "name": "sudo cmd now", - "x": 480, - "y": 580, - "wires": [ - [], - [], - [] - ] - }, - { - "id": "425b04bad228f33e", - "type": "exec", - "z": "807baf16b84dfb29", - "command": "sudo systemctl restart planktoscope-org.controller.service", - "addpay": false, - "append": "", - "useSpawn": "false", - "timer": "", - "winHide": false, - "oldrc": false, - "name": "Restart hardware controller", - "x": 780, - "y": 660, - "wires": [ - [ - "fd74a406ecf48578" - ], - [], - [] - ] - }, - { - "id": "3dac096ae08896f0", - "type": "exec", - "z": "807baf16b84dfb29", - "command": "sudo systemctl restart planktoscope-org.segmenter.service", - "addpay": false, - "append": "", - "useSpawn": "false", - "timer": "", - "winHide": false, - "oldrc": false, - "name": "Restart segmenter", - "x": 750, - "y": 800, - "wires": [ - [], - [], - [] - ] - }, - { - "id": "fd74a406ecf48578", - "type": "exec", - "z": "807baf16b84dfb29", - "command": "sudo systemctl restart nodered.service", - "addpay": false, - "append": "", - "useSpawn": "false", - "timer": "", - "winHide": false, - "oldrc": false, - "name": "Restart Node Red", - "x": 1050, - "y": 660, - "wires": [ - [], - [], - [] - ] - }, - { - "id": "53d72b9f4f4cb00f", - "type": "ui-button", - "z": "807baf16b84dfb29", - "group": "070185f2b69cf66d", - "name": "Reboot", - "label": "Reboot", - "order": 1, - "width": 0, - "height": 0, - "emulateClick": false, - "tooltip": "", - "color": "", - "bgcolor": "", - "className": "", - "icon": "", - "iconPosition": "left", - "payload": "reboot", - "payloadType": "str", - "topic": "topic", - "topicType": "msg", - "buttonColor": "", - "textColor": "", - "iconColor": "", - "enableClick": true, - "enablePointerdown": false, - "pointerdownPayload": "", - "pointerdownPayloadType": "str", - "enablePointerup": false, - "pointerupPayload": "", - "pointerupPayloadType": "str", - "x": 200, - "y": 560, - "wires": [ - [ - "7451ef4165b79c87" - ] - ] - }, - { - "id": "f040d18d96b705c3", - "type": "ui-button", - "z": "807baf16b84dfb29", - "group": "070185f2b69cf66d", - "name": "Shutdown", - "label": "Shutdown", - "order": 5, - "width": 0, - "height": 0, - "emulateClick": false, - "tooltip": "", - "color": "", - "bgcolor": "", - "className": "", - "icon": "", - "iconPosition": "left", - "payload": "shutdown", - "payloadType": "str", - "topic": "topic", - "topicType": "msg", - "buttonColor": "", - "textColor": "", - "iconColor": "", - "enableClick": true, - "enablePointerdown": false, - "pointerdownPayload": "", - "pointerdownPayloadType": "str", - "enablePointerup": false, - "pointerupPayload": "", - "pointerupPayloadType": "str", - "x": 210, - "y": 600, - "wires": [ - [ - "7451ef4165b79c87" - ] - ] - }, - { - "id": "bab342f965d345f2", - "type": "ui-button", - "z": "807baf16b84dfb29", - "group": "070185f2b69cf66d", - "name": "Restart Hardware controller", - "label": "Restart Hardware controller", - "order": 4, - "width": 0, - "height": 0, - "emulateClick": false, - "tooltip": "", - "color": "", - "bgcolor": "", - "className": "", - "icon": "", - "iconPosition": "left", - "payload": "", - "payloadType": "str", - "topic": "topic", - "topicType": "msg", - "buttonColor": "", - "textColor": "", - "iconColor": "", - "enableClick": true, - "enablePointerdown": false, - "pointerdownPayload": "", - "pointerdownPayloadType": "str", - "enablePointerup": false, - "pointerupPayload": "", - "pointerupPayloadType": "str", - "x": 260, - "y": 640, - "wires": [ - [ - "425b04bad228f33e" - ] - ] - }, - { - "id": "1f2fde836eabbb26", - "type": "ui-button", - "z": "807baf16b84dfb29", - "group": "070185f2b69cf66d", - "name": "Restart Segmenter", - "label": "Restart Segmenter", - "order": 3, - "width": 0, - "height": 0, - "emulateClick": false, - "tooltip": "", - "color": "", - "bgcolor": "", - "className": "", - "icon": "", - "iconPosition": "left", - "payload": "", - "payloadType": "str", - "topic": "topic", - "topicType": "msg", - "buttonColor": "", - "textColor": "", - "iconColor": "", - "enableClick": true, - "enablePointerdown": false, - "pointerdownPayload": "", - "pointerdownPayloadType": "str", - "enablePointerup": false, - "pointerupPayload": "", - "pointerupPayloadType": "str", - "x": 230, - "y": 680, - "wires": [ - [ - "3dac096ae08896f0" - ] - ] - }, - { - "id": "5f45f5ce424a2d43", - "type": "ui-event", - "z": "807baf16b84dfb29", - "ui": "e6ae26617c24c3ea", - "name": "UI Event", - "x": 80, - "y": 40, - "wires": [ - [ - "fc0520ca0e28fea9" - ] - ] - }, - { - "id": "fc0520ca0e28fea9", - "type": "switch", - "z": "807baf16b84dfb29", - "name": "msg.topic === \"$pageview\"", - "property": "topic", - "propertyType": "msg", - "rules": [ - { - "t": "eq", - "v": "$pageview", - "vt": "str" - } - ], - "checkall": "true", - "repair": false, - "outputs": 1, - "x": 280, - "y": 40, - "wires": [ - [ - "32fd5e03278f12c0" - ] - ] - }, - { - "id": "32fd5e03278f12c0", - "type": "switch", - "z": "807baf16b84dfb29", - "name": "msg.payload.page.path === \"/settings\"", - "property": "payload.page.path", - "propertyType": "msg", - "rules": [ - { - "t": "eq", - "v": "/settings", - "vt": "str" - } - ], - "checkall": "true", - "repair": false, - "outputs": 1, - "x": 580, - "y": 40, - "wires": [ - [ - "1a7bb2d868fe8707", - "9c3e4701c6b1687a" - ] - ] - }, - { - "id": "1a7bb2d868fe8707", - "type": "function", - "z": "807baf16b84dfb29", - "name": "Get process_min_ESD", - "func": "msg.payload = global.get(\"process_min_ESD\");\n\nreturn msg;\n", - "outputs": 1, - "timeout": 0, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 260, - "y": 140, - "wires": [ - [ - "b757ea59f07dc1f9" - ] - ] - }, - { - "id": "b757ea59f07dc1f9", - "type": "ui-text-input", - "z": "807baf16b84dfb29", - "group": "070185f2b69cf66d", - "name": "", - "label": "process_min_ESD", - "order": 6, - "width": 0, - "height": 0, - "topic": "process_min_ESD", - "topicType": "str", - "mode": "number", - "tooltip": "", - "delay": 300, - "passthru": true, - "sendOnDelay": false, - "sendOnBlur": true, - "sendOnEnter": true, - "className": "", - "clearable": false, - "sendOnClear": false, - "icon": "", - "iconPosition": "left", - "iconInnerPosition": "inside", - "x": 530, - "y": 140, - "wires": [ - [ - "aa0070c82f8fe4b6" - ] - ] - }, - { - "id": "aa0070c82f8fe4b6", - "type": "function", - "z": "807baf16b84dfb29", - "name": "set process_min_ESD", - "func": "global.set(\"process_min_ESD\", Number(msg.payload));\n", - "outputs": 1, - "timeout": "", - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 780, - "y": 140, - "wires": [ - [] - ] - }, - { - "id": "73ce979b9b49a446", - "type": "ui-text-input", - "z": "807baf16b84dfb29", - "group": "070185f2b69cf66d", - "name": "", - "label": "acq_delay_stab", - "order": 2, - "width": 0, - "height": 0, - "topic": "acq_delay_stab", - "topicType": "str", - "mode": "number", - "tooltip": "", - "delay": 300, - "passthru": true, - "sendOnDelay": false, - "sendOnBlur": true, - "sendOnEnter": true, - "className": "", - "clearable": false, - "sendOnClear": false, - "icon": "", - "iconPosition": "left", - "iconInnerPosition": "inside", - "x": 520, - "y": 180, - "wires": [ - [ - "9fccbaf294903c2c" - ] - ] - }, - { - "id": "9c3e4701c6b1687a", - "type": "function", - "z": "807baf16b84dfb29", - "name": "Get acq_delay_stab", - "func": "msg.payload = global.get(\"acq_delay_stab\");\n\nreturn msg;\n", - "outputs": 1, - "timeout": 0, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 260, - "y": 180, - "wires": [ - [ - "73ce979b9b49a446" - ] - ] - }, - { - "id": "9fccbaf294903c2c", - "type": "function", - "z": "807baf16b84dfb29", - "name": "set acq_delay_stab", - "func": "global.set(\"acq_delay_stab\", Number(msg.payload));\n", - "outputs": 1, - "timeout": "", - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 770, - "y": 180, - "wires": [ - [] - ] - }, - { - "id": "e6a8ee249bf33950", - "type": "ui-event", - "z": "1b99100829ae7f41", - "ui": "e6ae26617c24c3ea", - "name": "UI Event", - "x": 80, - "y": 40, - "wires": [ - [ - "f3b093b749e56ace" - ] - ] - }, - { - "id": "f3b093b749e56ace", - "type": "switch", - "z": "1b99100829ae7f41", - "name": "msg.topic === \"$pageview\"", - "property": "topic", - "propertyType": "msg", - "rules": [ - { - "t": "eq", - "v": "$pageview", - "vt": "str" - } - ], - "checkall": "true", - "repair": false, - "outputs": 1, - "x": 280, - "y": 40, - "wires": [ - [ - "dfcec9d1920037d5" - ] - ] - }, - { - "id": "dfcec9d1920037d5", - "type": "switch", - "z": "1b99100829ae7f41", - "name": "msg.payload.page.path === \"/hardware\"", - "property": "payload.page.path", - "propertyType": "msg", - "rules": [ - { - "t": "eq", - "v": "/hardware", - "vt": "str" - } - ], - "checkall": "true", - "repair": false, - "outputs": 1, - "x": 580, - "y": 40, - "wires": [ - [ - "921eb70f1795b70e", - "a41da1c9f8661415", - "629dbc1e59dceaea" - ] - ] - }, - { - "id": "921eb70f1795b70e", - "type": "function", - "z": "1b99100829ae7f41", - "name": "Get calibration_sensor_height", - "func": "msg.payload = global.get(\"calibration_sensor_height\");\n\nreturn msg;\n", - "outputs": 1, - "timeout": 0, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 290, - "y": 140, - "wires": [ - [ - "74597418566e278f" - ] - ] - }, - { - "id": "74597418566e278f", - "type": "ui-text-input", - "z": "1b99100829ae7f41", - "group": "9d212165f2367f01", - "name": "", - "label": "calibration_sensor_height", - "order": 1, - "width": 0, - "height": 0, - "topic": "calibration_sensor_height", - "topicType": "str", - "mode": "number", - "tooltip": "", - "delay": 300, - "passthru": true, - "sendOnDelay": false, - "sendOnBlur": true, - "sendOnEnter": true, - "className": "", - "clearable": false, - "sendOnClear": false, - "icon": "", - "iconPosition": "left", - "iconInnerPosition": "inside", - "x": 550, - "y": 140, - "wires": [ - [ - "40cb70ede83af087" - ] - ] - }, - { - "id": "40cb70ede83af087", - "type": "function", - "z": "1b99100829ae7f41", - "name": "set calibration_sensor_height", - "func": "global.set(\"calibration_sensor_height\", Number(msg.payload));\n", - "outputs": 1, - "timeout": "", - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 800, - "y": 140, - "wires": [ - [] - ] - }, - { - "id": "2e6c2109cb3995b8", - "type": "ui-text-input", - "z": "1b99100829ae7f41", - "group": "9d212165f2367f01", - "name": "", - "label": "calibration_sensor_width", - "order": 3, - "width": 0, - "height": 0, - "topic": "calibration_sensor_width", - "topicType": "str", - "mode": "number", - "tooltip": "", - "delay": 300, - "passthru": true, - "sendOnDelay": false, - "sendOnBlur": true, - "sendOnEnter": true, - "className": "", - "clearable": false, - "sendOnClear": false, - "icon": "", - "iconPosition": "left", - "iconInnerPosition": "inside", - "x": 550, - "y": 180, - "wires": [ - [ - "068171405cf7937a" - ] - ] - }, - { - "id": "068171405cf7937a", - "type": "function", - "z": "1b99100829ae7f41", - "name": "set calibration_sensor_width", - "func": "global.set(\"calibration_sensor_width\", Number(msg.payload));\n", - "outputs": 1, - "timeout": "", - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 800, - "y": 180, - "wires": [ - [] - ] - }, - { - "id": "a41da1c9f8661415", - "type": "function", - "z": "1b99100829ae7f41", - "name": "Get calibration_sensor_width", - "func": "msg.payload = global.get(\"calibration_sensor_width\");\n\nreturn msg;\n", - "outputs": 1, - "timeout": 0, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 280, - "y": 180, - "wires": [ - [ - "2e6c2109cb3995b8" - ] - ] - }, - { - "id": "629dbc1e59dceaea", - "type": "function", - "z": "1b99100829ae7f41", - "name": "Get acq_flowcell_thickness", - "func": "msg.payload = global.get(\"acq_flowcell_thickness\");\n\nreturn msg;\n", - "outputs": 1, - "timeout": 0, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 280, - "y": 220, - "wires": [ - [ - "c757aa3b1e6cbae8" - ] - ] - }, - { - "id": "c757aa3b1e6cbae8", - "type": "ui-text-input", - "z": "1b99100829ae7f41", - "group": "9d212165f2367f01", - "name": "", - "label": "acq_flowcell_thickness", - "order": 2, - "width": 0, - "height": 0, - "topic": "acq_flowcell_thickness", - "topicType": "str", - "mode": "number", - "tooltip": "", - "delay": 300, - "passthru": true, - "sendOnDelay": false, - "sendOnBlur": true, - "sendOnEnter": true, - "className": "", - "clearable": false, - "sendOnClear": false, - "icon": "", - "iconPosition": "left", - "iconInnerPosition": "inside", - "x": 550, - "y": 220, - "wires": [ - [ - "ae3e82e107244b10" - ] - ] - }, - { - "id": "ae3e82e107244b10", - "type": "function", - "z": "1b99100829ae7f41", - "name": "set acq_flowcell_thickness", - "func": "global.set(\"acq_flowcell_thickness\", Number(msg.payload));\n", - "outputs": 1, - "timeout": "", - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 800, - "y": 220, - "wires": [ - [] - ] } ] \ No newline at end of file From 4a881ead21dac0ed97012fa013fd2c89896a181d Mon Sep 17 00:00:00 2001 From: Thibaut Pollina Date: Tue, 25 Nov 2025 08:54:21 +0000 Subject: [PATCH 07/36] update --- node-red/projects/dashboard/flows.json | 522 ++++++++++++++++++------- 1 file changed, 370 insertions(+), 152 deletions(-) diff --git a/node-red/projects/dashboard/flows.json b/node-red/projects/dashboard/flows.json index 4d36a872a..8e006ce54 100644 --- a/node-red/projects/dashboard/flows.json +++ b/node-red/projects/dashboard/flows.json @@ -1399,7 +1399,7 @@ "ui": "e6ae26617c24c3ea", "path": "/visualization", "icon": "chart-scatter-plot", - "layout": "grid", + "layout": "tabs", "theme": "f7770f0b818c3a67", "breakpoints": [ { @@ -1425,12 +1425,13 @@ ], "order": 6, "className": "", - "visible": true, - "disabled": false + "visible": "true", + "disabled": "false" }, { "id": "b570f76ef526af45", "type": "ui-group", + "d": true, "name": "Navigation Top", "page": "d129fac8e7742d5b", "width": "12", @@ -1445,11 +1446,12 @@ { "id": "1d3abb201c51ff47", "type": "ui-group", + "d": true, "name": "Navigation Bottom", "page": "d129fac8e7742d5b", "width": "12", "height": 1, - "order": 3, + "order": 8, "showTitle": false, "className": "", "visible": "true", @@ -1473,7 +1475,7 @@ { "id": "fa6393a7d7e3b7d7", "type": "ui-group", - "name": "Table", + "name": "List of Segmentation", "page": "d129fac8e7742d5b", "width": "12", "height": 1, @@ -1484,6 +1486,76 @@ "disabled": "false", "groupType": "default" }, + { + "id": "44d42558adf7baf7", + "type": "ui-group", + "name": "ChatGPT 5.1 Pro v1", + "page": "d129fac8e7742d5b", + "width": "12", + "height": 1, + "order": 3, + "showTitle": true, + "className": "", + "visible": "true", + "disabled": "false", + "groupType": "default" + }, + { + "id": "bdf41008b379e80e", + "type": "ui-group", + "name": "Gemini 3 Pro v1", + "page": "d129fac8e7742d5b", + "width": "12", + "height": 1, + "order": 4, + "showTitle": true, + "className": "", + "visible": "true", + "disabled": "false", + "groupType": "default" + }, + { + "id": "e3bd8883700f1ab8", + "type": "ui-group", + "name": "ChatGPT 5.1 Pro v2", + "page": "d129fac8e7742d5b", + "width": "12", + "height": 1, + "order": 5, + "showTitle": true, + "className": "", + "visible": "true", + "disabled": "false", + "groupType": "default" + }, + { + "id": "0cb96323818204ca", + "type": "ui-group", + "name": "Gemini 3 Pro v2", + "page": "d129fac8e7742d5b", + "width": 6, + "height": 1, + "order": 6, + "showTitle": true, + "className": "", + "visible": "true", + "disabled": "false", + "groupType": "default" + }, + { + "id": "fd07716b2cd88ccd", + "type": "ui-group", + "name": "ChatGPT 5.1 Pro v3", + "page": "d129fac8e7742d5b", + "width": "12", + "height": 1, + "order": 7, + "showTitle": true, + "className": "", + "visible": "true", + "disabled": "false", + "groupType": "default" + }, { "id": "82099021.9ceb08", "type": "file", @@ -1961,7 +2033,7 @@ [ "52902327e2363f8b", "cd5cd3a0277b8dba", - "0c0eb2a907745142" + "37fa78ad741d783c" ] ] }, @@ -1977,7 +2049,7 @@ "width": "12", "height": "6", "head": "", - "format": "\n\n\n\n\n\n", + "format": "\n\n", "storeOutMessages": true, "passthru": true, "resendOnRefresh": true, @@ -2039,7 +2111,8 @@ "y": 200, "wires": [ [ - "39c7e4b018ce16cb" + "39c7e4b018ce16cb", + "1044b154a90406dc" ], [ "8efc52e6ee9206f6" @@ -2058,7 +2131,7 @@ "width": "0", "height": "0", "head": "", - "format": "\n", + "format": "\n", "storeOutMessages": true, "passthru": true, "resendOnRefresh": true, @@ -2071,20 +2144,39 @@ ] }, { - "id": "0c0eb2a907745142", + "id": "1044b154a90406dc", + "type": "exec", + "z": "1b667c6443413ced", + "command": "sudo reboot now", + "addpay": "", + "append": "", + "useSpawn": "false", + "timer": "", + "winHide": false, + "oldrc": false, + "name": "", + "x": 1190, + "y": 300, + "wires": [ + [], + [], + [] + ] + }, + { + "id": "37fa78ad741d783c", "type": "debug", "z": "1b667c6443413ced", - "name": "debug 15", + "name": "debug 7", "active": true, "tosidebar": true, "console": false, "tostatus": false, - "complete": "true", - "targetType": "full", + "complete": "false", "statusVal": "", "statusType": "auto", - "x": 520, - "y": 280, + "x": 420, + "y": 320, "wires": [] }, { @@ -2127,8 +2219,7 @@ "y": 140, "wires": [ [ - "f514a138d38d3c61", - "4f5f150680be40da" + "f514a138d38d3c61" ] ] }, @@ -2144,7 +2235,7 @@ "width": 0, "height": 0, "head": "", - "format": "\n", + "format": "", "storeOutMessages": true, "passthru": true, "resendOnRefresh": true, @@ -2460,22 +2551,6 @@ ] ] }, - { - "id": "4f5f150680be40da", - "type": "debug", - "z": "ab58b3fd0e6bcd77", - "name": "debug 12", - "active": true, - "tosidebar": true, - "console": false, - "tostatus": false, - "complete": "false", - "statusVal": "", - "statusType": "auto", - "x": 380, - "y": 340, - "wires": [] - }, { "id": "349d0f7644f26a62", "type": "ui-template", @@ -2488,7 +2563,7 @@ "width": 0, "height": 0, "head": "", - "format": "\n\n", + "format": "", "storeOutMessages": true, "passthru": true, "resendOnRefresh": true, @@ -3075,7 +3150,7 @@ "width": 0, "height": 0, "head": "", - "format": "\n\n", + "format": "", "storeOutMessages": true, "passthru": true, "resendOnRefresh": true, @@ -3602,7 +3677,7 @@ "width": 0, "height": 0, "head": "", - "format": "\n\n", + "format": "", "storeOutMessages": true, "passthru": true, "resendOnRefresh": true, @@ -3674,8 +3749,7 @@ "y": 260, "wires": [ [ - "33c0d1b8251b5d00", - "eed6aba5c7ab3b6c" + "33c0d1b8251b5d00" ] ] }, @@ -3788,23 +3862,6 @@ ] ] }, - { - "id": "eed6aba5c7ab3b6c", - "type": "debug", - "z": "0fd76ac156d78937", - "name": "debug 5", - "active": true, - "tosidebar": true, - "console": false, - "tostatus": false, - "complete": "true", - "targetType": "full", - "statusVal": "", - "statusType": "auto", - "x": 320, - "y": 460, - "wires": [] - }, { "id": "7fde0897e436c334", "type": "ui-template", @@ -3931,7 +3988,7 @@ "width": 0, "height": 0, "head": "", - "format": "\n\n", + "format": "", "storeOutMessages": true, "passthru": true, "resendOnRefresh": true, @@ -3969,7 +4026,7 @@ "maxrows": "10", "passthru": false, "autocols": false, - "showSearch": true, + "showSearch": false, "deselect": true, "selectionType": "click", "columns": [ @@ -4025,25 +4082,27 @@ "mobileBreakpoint": "sm", "mobileBreakpointType": "defaults", "action": "replace", - "x": 650, + "x": 570, "y": 180, "wires": [ - [] + [ + "aefbf6f96248c398" + ] ] }, { "id": "18a10804663dbaa1", "type": "function", "z": "14f8c9b5ce1235cc", - "name": "function 1", - "func": "msg.payload = msg.payload.map(item => {\n // extraction de l'acquisition_id\n const acq = item.acquisition_id; // ex: \"A_2\"\n\n // création du chemin export\n item.export = `/ps/data/browse/files/export/ecotaxa/ecotaxa_${acq}.zip`;\n\n return item;\n});\n\nreturn msg;\n", + "name": "Insert export column", + "func": "msg.payload = msg.payload.map(item => {\n // extraction de l'acquisition_id\n const acq = item.acquisition_id; // ex: \"A_2\"\n\n // création du chemin export\n item.export = `/ps/data/browse/api/raw/export/ecotaxa/ecotaxa_${acq}.zip`;\n\n return item;\n});\n\nreturn msg;\n", "outputs": 1, "timeout": 0, "noerr": 0, "initialize": "", "finalize": "", "libs": [], - "x": 360, + "x": 380, "y": 180, "wires": [ [ @@ -4051,6 +4110,235 @@ ] ] }, + { + "id": "5ec998719a884cfe", + "type": "file in", + "z": "14f8c9b5ce1235cc", + "name": "", + "filename": "payload.path", + "filenameType": "msg", + "format": "utf8", + "chunk": false, + "sendError": false, + "encoding": "none", + "allProps": false, + "x": 880, + "y": 180, + "wires": [ + [ + "70ec28e6b50bec41" + ] + ] + }, + { + "id": "aefbf6f96248c398", + "type": "function", + "z": "14f8c9b5ce1235cc", + "name": "Get tsv path", + "func": "// 1. On s'assure que le path existe\nif (msg.payload.path) {\n \n // 2. On récupère le path actuel\n // ex: \"/home/pi/data/objects/2025-11-19/S_1/A_2\"\n const currentPath = msg.payload.path;\n\n // 3. On extrait l'identifiant (le dernier élément après le /)\n // Si le path finit par un /, on le retire d'abord pour éviter un ID vide\n const cleanPath = currentPath.endsWith('/') ? currentPath.slice(0, -1) : currentPath;\n const acqId = cleanPath.split('/').pop(); \n\n // 4. On modifie le path dans l'objet\n // Résultat: .../A_2/ecotaxa_A_2.tsv\n msg.payload.path = `${cleanPath}/ecotaxa_${acqId}.tsv`;\n}\n\nreturn msg;", + "outputs": 1, + "timeout": 0, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 730, + "y": 180, + "wires": [ + [ + "5ec998719a884cfe" + ] + ] + }, + { + "id": "70ec28e6b50bec41", + "type": "csv", + "z": "14f8c9b5ce1235cc", + "name": "", + "spec": "rfc", + "sep": "\\t", + "hdrin": true, + "hdrout": "none", + "multi": "mult", + "ret": "\\r\\n", + "temp": "", + "skip": "0", + "strings": true, + "include_empty_strings": "", + "include_null_values": "", + "x": 1010, + "y": 180, + "wires": [ + [ + "8dc194f764cd61ef" + ] + ] + }, + { + "id": "e8fbb01b7a38c29a", + "type": "ui-template", + "z": "14f8c9b5ce1235cc", + "group": "bdf41008b379e80e", + "page": "", + "ui": "", + "name": "Gemini 3 Pro v1", + "order": 1, + "width": 0, + "height": 0, + "head": "", + "format": "\n\n\n\n\n\n", + "storeOutMessages": true, + "passthru": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 1280, + "y": 320, + "wires": [ + [] + ] + }, + { + "id": "86bff301d6790945", + "type": "ui-template", + "z": "14f8c9b5ce1235cc", + "group": "44d42558adf7baf7", + "page": "", + "ui": "", + "name": "ChatGPT 5.1 Pro v1", + "order": 1, + "width": 0, + "height": 0, + "head": "", + "format": "\n\n\n\n\n\n\n", + "storeOutMessages": true, + "passthru": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 1300, + "y": 240, + "wires": [ + [] + ] + }, + { + "id": "c42d0eb4a7bc6eb4", + "type": "ui-template", + "z": "14f8c9b5ce1235cc", + "d": true, + "group": "e3bd8883700f1ab8", + "page": "", + "ui": "", + "name": "ChatGPT 5.1 Pro v2", + "order": 1, + "width": 0, + "height": 0, + "head": "", + "format": "\n\n\n\n\n\n\n", + "storeOutMessages": true, + "passthru": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 1300, + "y": 380, + "wires": [ + [] + ] + }, + { + "id": "687050b30c8d78f3", + "type": "debug", + "z": "14f8c9b5ce1235cc", + "name": "debug 6", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "true", + "targetType": "full", + "statusVal": "", + "statusType": "auto", + "x": 1260, + "y": 580, + "wires": [] + }, + { + "id": "8dc194f764cd61ef", + "type": "function", + "z": "14f8c9b5ce1235cc", + "name": "function 1", + "func": "// Vérifie que payload est un tableau\nif (Array.isArray(msg.payload)) {\n // Supprime la première entrée du tableau\n msg.payload = msg.payload.slice(1);\n}\n\nreturn msg;\n", + "outputs": 1, + "timeout": 0, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 1000, + "y": 400, + "wires": [ + [ + "687050b30c8d78f3", + "c42d0eb4a7bc6eb4", + "86bff301d6790945", + "ddac1dedf1ed0bb3", + "39ab3eeb12643cc6", + "e8fbb01b7a38c29a" + ] + ] + }, + { + "id": "ddac1dedf1ed0bb3", + "type": "ui-template", + "z": "14f8c9b5ce1235cc", + "d": true, + "group": "0cb96323818204ca", + "page": "", + "ui": "", + "name": "Gemini 3 Pro v2", + "order": 1, + "width": 0, + "height": 0, + "head": "", + "format": "\n\n\n\n\n\n", + "storeOutMessages": true, + "passthru": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 1280, + "y": 460, + "wires": [ + [] + ] + }, + { + "id": "39ab3eeb12643cc6", + "type": "ui-template", + "z": "14f8c9b5ce1235cc", + "d": true, + "group": "fd07716b2cd88ccd", + "page": "", + "ui": "", + "name": "ChatGPT 5.1 Pro v3", + "order": 1, + "width": 0, + "height": 0, + "head": "", + "format": "\n\n\n\n\n\n\n\n\n\n\n", + "storeOutMessages": true, + "passthru": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 1300, + "y": 520, + "wires": [ + [] + ] + }, { "id": "64ae825a687fb054", "type": "ecotaxa", @@ -4197,7 +4485,7 @@ "page": "", "ui": "", "name": "Step Bar", - "order": 1, + "order": 2, "width": 0, "height": 0, "head": "", @@ -4309,8 +4597,7 @@ "y": 140, "wires": [ [ - "94a287dea58faa4d", - "b8b60ce8a0cf6764" + "94a287dea58faa4d" ] ] }, @@ -4322,7 +4609,7 @@ "page": "", "ui": "", "name": "Calibration - WB", - "order": 2, + "order": 3, "width": "0", "height": "0", "head": "", @@ -4340,22 +4627,6 @@ ] ] }, - { - "id": "9c40c3932475ea84", - "type": "debug", - "z": "6426e7bea6900426", - "name": "debug 1", - "active": true, - "tosidebar": true, - "console": false, - "tostatus": false, - "complete": "false", - "statusVal": "", - "statusType": "auto", - "x": 940, - "y": 260, - "wires": [] - }, { "id": "e52cb70984e0d25d", "type": "mqtt out", @@ -4387,23 +4658,6 @@ [] ] }, - { - "id": "b8b60ce8a0cf6764", - "type": "debug", - "z": "6426e7bea6900426", - "name": "debug 2", - "active": true, - "tosidebar": true, - "console": false, - "tostatus": false, - "complete": "true", - "targetType": "full", - "statusVal": "", - "statusType": "auto", - "x": 320, - "y": 220, - "wires": [] - }, { "id": "615edad94a4f77e7", "type": "switch", @@ -4421,13 +4675,12 @@ "checkall": "true", "repair": false, "outputs": 1, - "x": 650, - "y": 180, + "x": 590, + "y": 140, "wires": [ [ "d8f732f8fe251222", - "e52cb70984e0d25d", - "9c40c3932475ea84" + "e52cb70984e0d25d" ] ] }, @@ -4552,8 +4805,7 @@ "y": 140, "wires": [ [ - "740188ac42d17f44", - "d41870c7f587821b" + "740188ac42d17f44" ] ] }, @@ -4569,7 +4821,7 @@ "width": "0", "height": "0", "head": "", - "format": "\n\n\n\n\n", + "format": "\n\n\n\n", "storeOutMessages": true, "passthru": true, "resendOnRefresh": true, @@ -4583,22 +4835,6 @@ ] ] }, - { - "id": "21af2e731c368d3e", - "type": "debug", - "z": "14c685bd04db8be5", - "name": "debug 3", - "active": true, - "tosidebar": true, - "console": false, - "tostatus": false, - "complete": "false", - "statusVal": "", - "statusType": "auto", - "x": 940, - "y": 260, - "wires": [] - }, { "id": "785c59c553d71326", "type": "mqtt out", @@ -4616,37 +4852,20 @@ "id": "0283e992ff5da0f6", "type": "function", "z": "14c685bd04db8be5", - "name": "set pump settings", - "func": "if (msg.topic) {\n global.set(\"calibration_wbg_red\", msg.payload.settings.white_balance_gain.red);\n global.set(\"calibration_wbg_blue\", msg.payload.settings.white_balance_gain.blue);\n}\nreturn msg;", + "name": "set led_intensity", + "func": "if (msg.topic) {\n global.set(\"led_intensity\", msg.payload.settings.led_intensity);}\nreturn msg;", "outputs": 1, "timeout": "", "noerr": 0, "initialize": "", "finalize": "", "libs": [], - "x": 990, + "x": 980, "y": 180, "wires": [ [] ] }, - { - "id": "d41870c7f587821b", - "type": "debug", - "z": "14c685bd04db8be5", - "name": "debug 4", - "active": true, - "tosidebar": true, - "console": false, - "tostatus": false, - "complete": "true", - "targetType": "full", - "statusVal": "", - "statusType": "auto", - "x": 320, - "y": 220, - "wires": [] - }, { "id": "959de9b1292e047d", "type": "switch", @@ -4664,12 +4883,11 @@ "checkall": "true", "repair": false, "outputs": 1, - "x": 650, - "y": 180, + "x": 590, + "y": 140, "wires": [ [ - "0283e992ff5da0f6", - "21af2e731c368d3e" + "0283e992ff5da0f6" ] ] }, @@ -4841,7 +5059,7 @@ "checkall": "true", "repair": false, "outputs": 1, - "x": 610, + "x": 590, "y": 140, "wires": [ [ @@ -4861,8 +5079,8 @@ "initialize": "", "finalize": "", "libs": [], - "x": 810, - "y": 140, + "x": 1010, + "y": 180, "wires": [ [] ] @@ -5094,8 +5312,8 @@ "initialize": "", "finalize": "", "libs": [], - "x": 920, - "y": 160, + "x": 1000, + "y": 180, "wires": [ [] ] From da8bd588dcd65cd9885e87512e738c43109cbf69 Mon Sep 17 00:00:00 2001 From: Thibaut Pollina Date: Tue, 25 Nov 2025 14:28:45 +0000 Subject: [PATCH 08/36] update --- node-red/projects/dashboard/flows.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/node-red/projects/dashboard/flows.json b/node-red/projects/dashboard/flows.json index 8e006ce54..416716978 100644 --- a/node-red/projects/dashboard/flows.json +++ b/node-red/projects/dashboard/flows.json @@ -1916,7 +1916,7 @@ "width": 0, "height": 0, "head": "", - "format": " .v-btn--variant-outlined.v-btn--active {\n background-color: #1976d2 !important;\n color: white !important;\n }\n\n\n.v-row+.v-row {\n margin-top: 0px !important;\n}\n\n.v-row {\n margin: 0px !important;\n}", + "format": " .v-btn--variant-outlined.v-btn--active {\n background-color: #1976d2 !important;\n color: white !important;\n }\n\n\n.v-row+.v-row {\n margin-top: 0px !important;\n}\n\n.v-row {\n margin: 0px !important;\n}\n\n\n .v-field__field {\n background: #eef3ff;\n border-radius: 4px;\n }\n .v-btn-group .v-btn {\n flex-grow: 1;\n background: #eef3ff;\n }", "storeOutMessages": true, "passthru": true, "resendOnRefresh": true, @@ -2049,7 +2049,7 @@ "width": "12", "height": "6", "head": "", - "format": "\n\n", + "format": "\n\n", "storeOutMessages": true, "passthru": true, "resendOnRefresh": true, @@ -2435,7 +2435,7 @@ "width": "0", "height": "0", "head": "", - "format": "\n\n\n\n", + "format": "\n\n\n\n", "storeOutMessages": true, "passthru": true, "resendOnRefresh": true, From 742121b68912c4196d0785dc52241ba75d511fa6 Mon Sep 17 00:00:00 2001 From: Thibaut Pollina Date: Tue, 25 Nov 2025 18:54:03 +0000 Subject: [PATCH 09/36] update --- node-red/projects/dashboard/flows.json | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/node-red/projects/dashboard/flows.json b/node-red/projects/dashboard/flows.json index 416716978..f4eef4e5f 100644 --- a/node-red/projects/dashboard/flows.json +++ b/node-red/projects/dashboard/flows.json @@ -3971,7 +3971,7 @@ "templateScope": "local", "className": "", "x": 1290, - "y": 160, + "y": 180, "wires": [ [] ] @@ -4226,7 +4226,6 @@ "id": "c42d0eb4a7bc6eb4", "type": "ui-template", "z": "14f8c9b5ce1235cc", - "d": true, "group": "e3bd8883700f1ab8", "page": "", "ui": "", @@ -4293,7 +4292,6 @@ "id": "ddac1dedf1ed0bb3", "type": "ui-template", "z": "14f8c9b5ce1235cc", - "d": true, "group": "0cb96323818204ca", "page": "", "ui": "", @@ -4318,7 +4316,6 @@ "id": "39ab3eeb12643cc6", "type": "ui-template", "z": "14f8c9b5ce1235cc", - "d": true, "group": "fd07716b2cd88ccd", "page": "", "ui": "", @@ -4485,7 +4482,7 @@ "page": "", "ui": "", "name": "Step Bar", - "order": 2, + "order": 1, "width": 0, "height": 0, "head": "", @@ -4609,7 +4606,7 @@ "page": "", "ui": "", "name": "Calibration - WB", - "order": 3, + "order": 2, "width": "0", "height": "0", "head": "", From efbf7713c245fd8786f060ea86e1188ac191209d Mon Sep 17 00:00:00 2001 From: Thibaut Pollina Date: Tue, 25 Nov 2025 20:24:17 +0000 Subject: [PATCH 10/36] update --- node-red/projects/dashboard/flows.json | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/node-red/projects/dashboard/flows.json b/node-red/projects/dashboard/flows.json index f4eef4e5f..2bbf9b726 100644 --- a/node-red/projects/dashboard/flows.json +++ b/node-red/projects/dashboard/flows.json @@ -676,10 +676,10 @@ }, "sizes": { "density": "default", - "pagePadding": "10px", - "groupGap": "10px", + "pagePadding": "1rem", + "groupGap": "1rem", "groupBorderRadius": "4px", - "widgetGap": "10px" + "widgetGap": "0px" } }, { @@ -1916,7 +1916,7 @@ "width": 0, "height": 0, "head": "", - "format": " .v-btn--variant-outlined.v-btn--active {\n background-color: #1976d2 !important;\n color: white !important;\n }\n\n\n.v-row+.v-row {\n margin-top: 0px !important;\n}\n\n.v-row {\n margin: 0px !important;\n}\n\n\n .v-field__field {\n background: #eef3ff;\n border-radius: 4px;\n }\n .v-btn-group .v-btn {\n flex-grow: 1;\n background: #eef3ff;\n }", + "format": " .v-btn--variant-outlined.v-btn--active {\n background-color: #1976d2 !important;\n color: white !important;\n }\n\n\n.v-row+.v-row {\n margin-top: 0px !important;\n}\n\n.v-row {\n margin: 0px !important;\n}\n\n\n .v-field__field {\n background: #eef3ff;\n border-radius: 4px;\n }\n .v-btn-group .v-btn, .v-field__overlay {\n background: #eef3ff;\n }\n \n\n .v-container{\n padding: 0 !important;\n }", "storeOutMessages": true, "passthru": true, "resendOnRefresh": true, @@ -2188,10 +2188,10 @@ "ui": "", "name": "Streaming", "order": 1, - "width": "6", - "height": "18", + "width": "0", + "height": "0", "head": "", - "format": "
\n \n
\n\n\n", + "format": "
\n \n
\n\n", "storeOutMessages": true, "passthru": true, "resendOnRefresh": true, @@ -2613,7 +2613,7 @@ "width": 0, "height": 0, "head": "", - "format": "\n\n\n", + "format": "\n\n\n", "storeOutMessages": true, "passthru": true, "resendOnRefresh": true, @@ -2639,7 +2639,7 @@ "width": 0, "height": 0, "head": "", - "format": "\n\n\n\n\n", + "format": "\n\n\n\n\n", "storeOutMessages": true, "passthru": true, "resendOnRefresh": true, @@ -2665,7 +2665,7 @@ "width": 0, "height": 0, "head": "", - "format": "\n\n\n\n\n", + "format": "\n\n\n\n\n", "storeOutMessages": true, "passthru": true, "resendOnRefresh": true, @@ -2691,7 +2691,7 @@ "width": 0, "height": 0, "head": "", - "format": "\n\n\n\n\n", + "format": "\n\n\n\n\n", "storeOutMessages": true, "passthru": true, "resendOnRefresh": true, @@ -3124,7 +3124,7 @@ "width": 0, "height": 0, "head": "", - "format": "\n\n\n\n", + "format": "\n\n\n\n", "storeOutMessages": true, "passthru": true, "resendOnRefresh": true, From b8ac594ca4e4309df1302ac89eeef3f1905f1e5b Mon Sep 17 00:00:00 2001 From: Thibaut Pollina Date: Wed, 26 Nov 2025 16:39:44 +0000 Subject: [PATCH 11/36] update --- node-red/projects/dashboard/flows.json | 748 +++++++++++++++---------- 1 file changed, 451 insertions(+), 297 deletions(-) diff --git a/node-red/projects/dashboard/flows.json b/node-red/projects/dashboard/flows.json index 2bbf9b726..647126a3d 100644 --- a/node-red/projects/dashboard/flows.json +++ b/node-red/projects/dashboard/flows.json @@ -1,8 +1,16 @@ [ + { + "id": "07f3b717f2a2c8c7", + "type": "tab", + "label": "Setup", + "disabled": false, + "info": "", + "env": [] + }, { "id": "1b667c6443413ced", "type": "tab", - "label": "Home dashboard2", + "label": "Home", "disabled": false, "info": "", "env": [] @@ -47,14 +55,6 @@ "info": "", "env": [] }, - { - "id": "8555b76c53e789e0", - "type": "tab", - "label": "[TEST] EcoTaxa", - "disabled": false, - "info": "", - "env": [] - }, { "id": "8018bd5586fd4054", "type": "tab", @@ -95,6 +95,14 @@ "info": "", "env": [] }, + { + "id": "8555b76c53e789e0", + "type": "tab", + "label": "[TEST] EcoTaxa", + "disabled": false, + "info": "", + "env": [] + }, { "id": "a02961610bc3982a", "type": "tab", @@ -713,7 +721,7 @@ "cols": "12" } ], - "order": 3, + "order": 4, "className": "", "visible": "true", "disabled": "false" @@ -847,7 +855,7 @@ "cols": "12" } ], - "order": 5, + "order": 6, "className": "", "visible": "true", "disabled": "false" @@ -897,7 +905,7 @@ "cols": "12" } ], - "order": 9, + "order": 10, "className": "", "visible": "false", "disabled": "false" @@ -947,7 +955,7 @@ "cols": "12" } ], - "order": 10, + "order": 11, "className": "", "visible": "false", "disabled": "false" @@ -997,7 +1005,7 @@ "cols": "12" } ], - "order": 11, + "order": 12, "className": "", "visible": "false", "disabled": "false" @@ -1047,7 +1055,7 @@ "cols": "12" } ], - "order": 8, + "order": 9, "className": "", "visible": "false", "disabled": "false" @@ -1097,7 +1105,7 @@ "cols": "12" } ], - "order": 7, + "order": 8, "className": "", "visible": "true", "disabled": "false" @@ -1161,7 +1169,7 @@ "cols": "12" } ], - "order": 2, + "order": 3, "className": "", "visible": "true", "disabled": "false" @@ -1225,7 +1233,7 @@ "cols": "12" } ], - "order": 4, + "order": 5, "className": "", "visible": "true", "disabled": "false" @@ -1289,7 +1297,7 @@ "cols": "12" } ], - "order": 1, + "order": 2, "className": "", "visible": "true", "disabled": "false" @@ -1399,7 +1407,7 @@ "ui": "e6ae26617c24c3ea", "path": "/visualization", "icon": "chart-scatter-plot", - "layout": "tabs", + "layout": "grid", "theme": "f7770f0b818c3a67", "breakpoints": [ { @@ -1423,7 +1431,7 @@ "cols": "12" } ], - "order": 6, + "order": 7, "className": "", "visible": "true", "disabled": "false" @@ -1431,7 +1439,6 @@ { "id": "b570f76ef526af45", "type": "ui-group", - "d": true, "name": "Navigation Top", "page": "d129fac8e7742d5b", "width": "12", @@ -1446,12 +1453,11 @@ { "id": "1d3abb201c51ff47", "type": "ui-group", - "d": true, "name": "Navigation Bottom", "page": "d129fac8e7742d5b", "width": "12", "height": 1, - "order": 8, + "order": 6, "showTitle": false, "className": "", "visible": "true", @@ -1493,7 +1499,7 @@ "page": "d129fac8e7742d5b", "width": "12", "height": 1, - "order": 3, + "order": 4, "showTitle": true, "className": "", "visible": "true", @@ -1501,56 +1507,78 @@ "groupType": "default" }, { - "id": "bdf41008b379e80e", + "id": "0cb96323818204ca", "type": "ui-group", - "name": "Gemini 3 Pro v1", + "name": "Gemini 3 Pro v2", "page": "d129fac8e7742d5b", "width": "12", "height": 1, - "order": 4, - "showTitle": true, + "order": 5, + "showTitle": false, "className": "", "visible": "true", "disabled": "false", "groupType": "default" }, { - "id": "e3bd8883700f1ab8", - "type": "ui-group", - "name": "ChatGPT 5.1 Pro v2", - "page": "d129fac8e7742d5b", - "width": "12", - "height": 1, - "order": 5, - "showTitle": true, + "id": "34112984a39c35bd", + "type": "ui-page", + "name": "Setup", + "ui": "e6ae26617c24c3ea", + "path": "/setup", + "icon": "cog", + "layout": "grid", + "theme": "f7770f0b818c3a67", + "breakpoints": [ + { + "name": "Default", + "px": "0", + "cols": "3" + }, + { + "name": "Tablet", + "px": "576", + "cols": "6" + }, + { + "name": "Small Desktop", + "px": "768", + "cols": "9" + }, + { + "name": "Desktop", + "px": "1024", + "cols": "12" + } + ], + "order": 1, "className": "", "visible": "true", - "disabled": "false", - "groupType": "default" + "disabled": "false" }, { - "id": "0cb96323818204ca", + "id": "de680effc3e27451", "type": "ui-group", - "name": "Gemini 3 Pro v2", - "page": "d129fac8e7742d5b", - "width": 6, + "name": "body", + "page": "34112984a39c35bd", + "width": "12", "height": 1, - "order": 6, - "showTitle": true, + "order": 1, + "showTitle": false, "className": "", "visible": "true", "disabled": "false", "groupType": "default" }, { - "id": "fd07716b2cd88ccd", + "id": "3d290173d833fd04", "type": "ui-group", - "name": "ChatGPT 5.1 Pro v3", + "name": "V3", "page": "d129fac8e7742d5b", "width": "12", "height": 1, - "order": 7, - "showTitle": true, + "order": 3, + "showTitle": false, "className": "", "visible": "true", "disabled": "false", @@ -1814,6 +1842,182 @@ ] ] }, + { + "id": "7e7d02f3ea356eff", + "type": "switch", + "z": "07f3b717f2a2c8c7", + "name": "msg.payload.page.path === \"/home\"", + "property": "payload.page.path", + "propertyType": "msg", + "rules": [ + { + "t": "eq", + "v": "/home", + "vt": "str" + } + ], + "checkall": "true", + "repair": false, + "outputs": 1, + "x": 570, + "y": 40, + "wires": [ + [ + "8faec1a8f5c2527a" + ] + ] + }, + { + "id": "f49d711b2738e87e", + "type": "switch", + "z": "07f3b717f2a2c8c7", + "name": "msg.topic === \"$pageview\"", + "property": "topic", + "propertyType": "msg", + "rules": [ + { + "t": "eq", + "v": "$pageview", + "vt": "str" + } + ], + "checkall": "true", + "repair": false, + "outputs": 1, + "x": 280, + "y": 40, + "wires": [ + [ + "7e7d02f3ea356eff" + ] + ] + }, + { + "id": "b1f5b8b5f26121e6", + "type": "ui-event", + "z": "07f3b717f2a2c8c7", + "ui": "e6ae26617c24c3ea", + "name": "UI Event", + "x": 80, + "y": 40, + "wires": [ + [ + "f49d711b2738e87e" + ] + ] + }, + { + "id": "8faec1a8f5c2527a", + "type": "delay", + "z": "07f3b717f2a2c8c7", + "name": "", + "pauseType": "delay", + "timeout": "1", + "timeoutUnits": "seconds", + "rate": "1", + "nbRateUnits": "1", + "rateUnits": "second", + "randomFirst": "1", + "randomLast": "5", + "randomUnits": "seconds", + "drop": false, + "allowrate": false, + "outputs": 1, + "x": 180, + "y": 140, + "wires": [ + [ + "d55333906aeb7517" + ] + ] + }, + { + "id": "d55333906aeb7517", + "type": "function", + "z": "07f3b717f2a2c8c7", + "name": "Get Global Variables", + "func": "const keys = global.keys(); // Get all global variable keys\nmsg.payload = {}; // Initialize the payload object\n\nkeys.forEach(key => {\n // Ignore keys that start with \"$\"\n if (!key.startsWith('$')) {\n msg.payload[key] = global.get(key);\n }\n});\n\nreturn msg;\n", + "outputs": 1, + "timeout": 0, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 360, + "y": 140, + "wires": [ + [ + "87b0cc67cfa07120" + ] + ] + }, + { + "id": "87b0cc67cfa07120", + "type": "ui-template", + "z": "07f3b717f2a2c8c7", + "group": "de680effc3e27451", + "page": "", + "ui": "", + "name": "body", + "order": 1, + "width": "0", + "height": "0", + "head": "", + "format": "\n\n", + "storeOutMessages": true, + "passthru": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 530, + "y": 140, + "wires": [ + [ + "b49055c50dca065a" + ] + ] + }, + { + "id": "b49055c50dca065a", + "type": "debug", + "z": "07f3b717f2a2c8c7", + "name": "debug 2", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "true", + "targetType": "full", + "statusVal": "", + "statusType": "auto", + "x": 660, + "y": 140, + "wires": [] + }, + { + "id": "c8caad8136e6c5e3", + "type": "ui-template", + "z": "07f3b717f2a2c8c7", + "group": "", + "page": "34112984a39c35bd", + "ui": "", + "name": "CSS (All Pages)", + "order": 0, + "width": 0, + "height": 0, + "head": "", + "format": "\n .v-toolbar__content {\n display:none;\n }\n\n.v-main{\n --v-layout-top: 0px !important;\n\n} \n", + "storeOutMessages": true, + "passthru": true, + "resendOnRefresh": true, + "templateScope": "page:style", + "className": "", + "x": 920, + "y": 40, + "wires": [ + [] + ] + }, { "id": "1cf1b20e425ec126", "type": "ui-template", @@ -2031,41 +2235,15 @@ "y": 280, "wires": [ [ - "52902327e2363f8b", "cd5cd3a0277b8dba", - "37fa78ad741d783c" + "37fa78ad741d783c", + "04386808e694e97e" ] ] }, { - "id": "52902327e2363f8b", - "type": "ui-template", - "z": "1b667c6443413ced", - "group": "", - "page": "", - "ui": "e6ae26617c24c3ea", - "name": "toolbar", - "order": 2, - "width": "12", - "height": "6", - "head": "", - "format": "\n\n", - "storeOutMessages": true, - "passthru": true, - "resendOnRefresh": true, - "templateScope": "widget:ui", - "className": "", - "x": 820, - "y": 200, - "wires": [ - [ - "1db1c2e3e19e85ff" - ] - ] - }, - { - "id": "8efc52e6ee9206f6", - "type": "poweroff", + "id": "8efc52e6ee9206f6", + "type": "poweroff", "z": "1b667c6443413ced", "name": "", "x": 1160, @@ -2179,6 +2357,32 @@ "y": 320, "wires": [] }, + { + "id": "04386808e694e97e", + "type": "ui-template", + "z": "1b667c6443413ced", + "group": "", + "page": "", + "ui": "e6ae26617c24c3ea", + "name": "toolbar", + "order": 2, + "width": "12", + "height": "6", + "head": "", + "format": "\n\n", + "storeOutMessages": true, + "passthru": true, + "resendOnRefresh": true, + "templateScope": "widget:ui", + "className": "", + "x": 820, + "y": 240, + "wires": [ + [ + "1db1c2e3e19e85ff" + ] + ] + }, { "id": "a57a165cd0ce511b", "type": "ui-template", @@ -2435,7 +2639,7 @@ "width": "0", "height": "0", "head": "", - "format": "\n\n\n\n", + "format": "\n\n\n\n", "storeOutMessages": true, "passthru": true, "resendOnRefresh": true, @@ -3124,7 +3328,7 @@ "width": 0, "height": 0, "head": "", - "format": "\n\n\n\n", + "format": "\n\n\n\n", "storeOutMessages": true, "passthru": true, "resendOnRefresh": true, @@ -4175,29 +4379,45 @@ ] }, { - "id": "e8fbb01b7a38c29a", - "type": "ui-template", + "id": "8dc194f764cd61ef", + "type": "function", "z": "14f8c9b5ce1235cc", - "group": "bdf41008b379e80e", - "page": "", - "ui": "", - "name": "Gemini 3 Pro v1", - "order": 1, - "width": 0, - "height": 0, - "head": "", - "format": "\n\n\n\n\n\n", - "storeOutMessages": true, - "passthru": true, - "resendOnRefresh": true, - "templateScope": "local", - "className": "", - "x": 1280, - "y": 320, + "name": "function 1", + "func": "// Vérifie que payload est un tableau\nif (Array.isArray(msg.payload)) {\n // Supprime la première entrée du tableau\n msg.payload = msg.payload.slice(1);\n}\n\nreturn msg;\n", + "outputs": 1, + "timeout": 0, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 900, + "y": 420, "wires": [ - [] + [ + "687050b30c8d78f3", + "86bff301d6790945", + "ddac1dedf1ed0bb3", + "mega_plankto_v3" + ] ] }, + { + "id": "687050b30c8d78f3", + "type": "debug", + "z": "14f8c9b5ce1235cc", + "name": "debug 6", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "true", + "targetType": "full", + "statusVal": "", + "statusType": "auto", + "x": 1260, + "y": 580, + "wires": [] + }, { "id": "86bff301d6790945", "type": "ui-template", @@ -4217,77 +4437,11 @@ "templateScope": "local", "className": "", "x": 1300, - "y": 240, - "wires": [ - [] - ] - }, - { - "id": "c42d0eb4a7bc6eb4", - "type": "ui-template", - "z": "14f8c9b5ce1235cc", - "group": "e3bd8883700f1ab8", - "page": "", - "ui": "", - "name": "ChatGPT 5.1 Pro v2", - "order": 1, - "width": 0, - "height": 0, - "head": "", - "format": "\n\n\n\n\n\n\n", - "storeOutMessages": true, - "passthru": true, - "resendOnRefresh": true, - "templateScope": "local", - "className": "", - "x": 1300, - "y": 380, + "y": 360, "wires": [ [] ] }, - { - "id": "687050b30c8d78f3", - "type": "debug", - "z": "14f8c9b5ce1235cc", - "name": "debug 6", - "active": true, - "tosidebar": true, - "console": false, - "tostatus": false, - "complete": "true", - "targetType": "full", - "statusVal": "", - "statusType": "auto", - "x": 1260, - "y": 580, - "wires": [] - }, - { - "id": "8dc194f764cd61ef", - "type": "function", - "z": "14f8c9b5ce1235cc", - "name": "function 1", - "func": "// Vérifie que payload est un tableau\nif (Array.isArray(msg.payload)) {\n // Supprime la première entrée du tableau\n msg.payload = msg.payload.slice(1);\n}\n\nreturn msg;\n", - "outputs": 1, - "timeout": 0, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 1000, - "y": 400, - "wires": [ - [ - "687050b30c8d78f3", - "c42d0eb4a7bc6eb4", - "86bff301d6790945", - "ddac1dedf1ed0bb3", - "39ab3eeb12643cc6", - "e8fbb01b7a38c29a" - ] - ] - }, { "id": "ddac1dedf1ed0bb3", "type": "ui-template", @@ -4313,118 +4467,28 @@ ] }, { - "id": "39ab3eeb12643cc6", + "id": "mega_plankto_v3", "type": "ui-template", "z": "14f8c9b5ce1235cc", - "group": "fd07716b2cd88ccd", + "group": "3d290173d833fd04", "page": "", "ui": "", - "name": "ChatGPT 5.1 Pro v3", + "name": "PlanktoScope Mega V3", "order": 1, - "width": 0, - "height": 0, - "head": "", - "format": "\n\n\n\n\n\n\n\n\n\n\n", + "width": "0", + "height": "0", + "format": "\n\n\n\n\n\n\n", "storeOutMessages": true, "passthru": true, "resendOnRefresh": true, "templateScope": "local", "className": "", - "x": 1300, - "y": 520, + "x": 1310, + "y": 280, "wires": [ [] ] }, - { - "id": "64ae825a687fb054", - "type": "ecotaxa", - "z": "8555b76c53e789e0", - "name": "Import to Ecotaxa Project", - "api_url": "https://ecotaxa.obs-vlfr.fr/api/", - "project_id": "9366", - "x": 830, - "y": 300, - "wires": [ - [ - "c21f20a7a9902ee4" - ] - ] - }, - { - "id": "af1ec6323cc00231", - "type": "inject", - "z": "8555b76c53e789e0", - "name": "Lancer import", - "props": [], - "repeat": "", - "crontab": "", - "once": false, - "onceDelay": "0", - "topic": "", - "x": 310, - "y": 300, - "wires": [ - [ - "28f8eb0318735246" - ] - ] - }, - { - "id": "c21f20a7a9902ee4", - "type": "debug", - "z": "8555b76c53e789e0", - "name": "Show import result", - "active": true, - "tosidebar": true, - "console": false, - "tostatus": false, - "complete": "payload", - "targetType": "msg", - "statusVal": "", - "statusType": "auto", - "x": 1110, - "y": 300, - "wires": [] - }, - { - "id": "28f8eb0318735246", - "type": "function", - "z": "8555b76c53e789e0", - "name": "Set file_path", - "func": "msg.payload = {}\nmsg.payload.file_path = \"/home/pi/data/export/ecotaxa/ecotaxa_A_2.zip\"\n\n\nreturn msg;", - "outputs": 1, - "timeout": 0, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 570, - "y": 300, - "wires": [ - [ - "64ae825a687fb054", - "1457a0786f787415" - ] - ] - }, - { - "id": "1457a0786f787415", - "type": "debug", - "z": "8555b76c53e789e0", - "name": "debug 9", - "active": true, - "tosidebar": true, - "console": false, - "tostatus": false, - "complete": "true", - "targetType": "full", - "statusVal": "", - "statusType": "auto", - "x": 820, - "y": 200, - "wires": [] - }, { "id": "8e11bb014b9664be", "type": "ui-template", @@ -4594,33 +4658,7 @@ "y": 140, "wires": [ [ - "94a287dea58faa4d" - ] - ] - }, - { - "id": "94a287dea58faa4d", - "type": "ui-template", - "z": "6426e7bea6900426", - "group": "af8acdfe9afbad74", - "page": "", - "ui": "", - "name": "Calibration - WB", - "order": 2, - "width": "0", - "height": "0", - "head": "", - "format": "\n\n\n\n\n", - "storeOutMessages": true, - "passthru": true, - "resendOnRefresh": true, - "templateScope": "local", - "className": "", - "x": 400, - "y": 140, - "wires": [ - [ - "615edad94a4f77e7" + "5ec541d6e203def5" ] ] }, @@ -4681,6 +4719,32 @@ ] ] }, + { + "id": "5ec541d6e203def5", + "type": "ui-template", + "z": "6426e7bea6900426", + "group": "af8acdfe9afbad74", + "page": "", + "ui": "", + "name": "Calibration - WB", + "order": 2, + "width": "0", + "height": "0", + "head": "", + "format": "\n\n\n", + "storeOutMessages": true, + "passthru": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 380, + "y": 180, + "wires": [ + [ + "615edad94a4f77e7" + ] + ] + }, { "id": "5fbdba7e61973f9a", "type": "ui-template", @@ -4818,7 +4882,7 @@ "width": "0", "height": "0", "head": "", - "format": "\n\n\n\n", + "format": "\n\n\n\n", "storeOutMessages": true, "passthru": true, "resendOnRefresh": true, @@ -4850,15 +4914,15 @@ "type": "function", "z": "14c685bd04db8be5", "name": "set led_intensity", - "func": "if (msg.topic) {\n global.set(\"led_intensity\", msg.payload.settings.led_intensity);}\nreturn msg;", + "func": "if (msg.topic) {\n global.set(\"led_intensity\", msg.payload.value);}\nreturn msg;", "outputs": 1, "timeout": "", "noerr": 0, "initialize": "", "finalize": "", "libs": [], - "x": 980, - "y": 180, + "x": 880, + "y": 160, "wires": [ [] ] @@ -4873,7 +4937,7 @@ "rules": [ { "t": "eq", - "v": "imager/image", + "v": "light", "vt": "str" } ], @@ -4884,6 +4948,7 @@ "y": 140, "wires": [ [ + "785c59c553d71326", "0283e992ff5da0f6" ] ] @@ -5315,6 +5380,95 @@ [] ] }, + { + "id": "64ae825a687fb054", + "type": "ecotaxa", + "z": "8555b76c53e789e0", + "name": "Import to Ecotaxa Project", + "api_url": "https://ecotaxa.obs-vlfr.fr/api/", + "project_id": "9366", + "x": 830, + "y": 300, + "wires": [ + [ + "c21f20a7a9902ee4" + ] + ] + }, + { + "id": "af1ec6323cc00231", + "type": "inject", + "z": "8555b76c53e789e0", + "name": "Lancer import", + "props": [], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": "0", + "topic": "", + "x": 310, + "y": 300, + "wires": [ + [ + "28f8eb0318735246" + ] + ] + }, + { + "id": "c21f20a7a9902ee4", + "type": "debug", + "z": "8555b76c53e789e0", + "name": "Show import result", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "payload", + "targetType": "msg", + "statusVal": "", + "statusType": "auto", + "x": 1110, + "y": 300, + "wires": [] + }, + { + "id": "28f8eb0318735246", + "type": "function", + "z": "8555b76c53e789e0", + "name": "Set file_path", + "func": "msg.payload = {}\nmsg.payload.file_path = \"/home/pi/data/export/ecotaxa/ecotaxa_A_2.zip\"\n\n\nreturn msg;", + "outputs": 1, + "timeout": 0, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 570, + "y": 300, + "wires": [ + [ + "64ae825a687fb054", + "1457a0786f787415" + ] + ] + }, + { + "id": "1457a0786f787415", + "type": "debug", + "z": "8555b76c53e789e0", + "name": "debug 9", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "true", + "targetType": "full", + "statusVal": "", + "statusType": "auto", + "x": 820, + "y": 200, + "wires": [] + }, { "id": "7ccb5c8c66ad170a", "type": "inject", From 1362391321795546220afa12afc3099e38096f6e Mon Sep 17 00:00:00 2001 From: Thibaut Pollina Date: Thu, 27 Nov 2025 12:01:40 +0000 Subject: [PATCH 12/36] update --- node-red/projects/dashboard/flows.json | 335 ++++++++++++++----------- 1 file changed, 188 insertions(+), 147 deletions(-) diff --git a/node-red/projects/dashboard/flows.json b/node-red/projects/dashboard/flows.json index 647126a3d..07811fb70 100644 --- a/node-red/projects/dashboard/flows.json +++ b/node-red/projects/dashboard/flows.json @@ -713,7 +713,7 @@ { "name": "Small Desktop", "px": "768", - "cols": "9" + "cols": "6" }, { "name": "Desktop", @@ -1457,7 +1457,7 @@ "page": "d129fac8e7742d5b", "width": "12", "height": 1, - "order": 6, + "order": 4, "showTitle": false, "className": "", "visible": "true", @@ -1492,34 +1492,6 @@ "disabled": "false", "groupType": "default" }, - { - "id": "44d42558adf7baf7", - "type": "ui-group", - "name": "ChatGPT 5.1 Pro v1", - "page": "d129fac8e7742d5b", - "width": "12", - "height": 1, - "order": 4, - "showTitle": true, - "className": "", - "visible": "true", - "disabled": "false", - "groupType": "default" - }, - { - "id": "0cb96323818204ca", - "type": "ui-group", - "name": "Gemini 3 Pro v2", - "page": "d129fac8e7742d5b", - "width": "12", - "height": 1, - "order": 5, - "showTitle": false, - "className": "", - "visible": "true", - "disabled": "false", - "groupType": "default" - }, { "id": "34112984a39c35bd", "type": "ui-page", @@ -1580,8 +1552,8 @@ "order": 3, "showTitle": false, "className": "", - "visible": "true", - "disabled": "false", + "visible": true, + "disabled": false, "groupType": "default" }, { @@ -1863,7 +1835,7 @@ "y": 40, "wires": [ [ - "8faec1a8f5c2527a" + "d55333906aeb7517" ] ] }, @@ -1906,31 +1878,6 @@ ] ] }, - { - "id": "8faec1a8f5c2527a", - "type": "delay", - "z": "07f3b717f2a2c8c7", - "name": "", - "pauseType": "delay", - "timeout": "1", - "timeoutUnits": "seconds", - "rate": "1", - "nbRateUnits": "1", - "rateUnits": "second", - "randomFirst": "1", - "randomLast": "5", - "randomUnits": "seconds", - "drop": false, - "allowrate": false, - "outputs": 1, - "x": 180, - "y": 140, - "wires": [ - [ - "d55333906aeb7517" - ] - ] - }, { "id": "d55333906aeb7517", "type": "function", @@ -1943,7 +1890,7 @@ "initialize": "", "finalize": "", "libs": [], - "x": 360, + "x": 120, "y": 140, "wires": [ [ @@ -1963,13 +1910,13 @@ "width": "0", "height": "0", "head": "", - "format": "\n\n", + "format": "\n\n", "storeOutMessages": true, "passthru": true, "resendOnRefresh": true, "templateScope": "local", "className": "", - "x": 530, + "x": 370, "y": 140, "wires": [ [ @@ -2634,7 +2581,7 @@ "group": "39cbd2658f16d608", "page": "", "ui": "", - "name": "Settings", + "name": "body", "order": 1, "width": "0", "height": "0", @@ -2645,7 +2592,7 @@ "resendOnRefresh": true, "templateScope": "local", "className": "", - "x": 380, + "x": 370, "y": 140, "wires": [ [ @@ -3323,18 +3270,18 @@ "group": "d2f77573ed4317e4", "page": "", "ui": "", - "name": "Acquisition settings", + "name": "body", "order": 1, "width": 0, "height": 0, "head": "", - "format": "\n\n\n\n", + "format": "\n\n\n\n", "storeOutMessages": true, "passthru": true, "resendOnRefresh": true, "templateScope": "local", "className": "", - "x": 370, + "x": 350, "y": 140, "wires": [ [ @@ -3819,6 +3766,14 @@ "type": "tickcross", "width": "", "align": "start" + }, + { + "title": "", + "key": "", + "keyType": "str", + "type": "html", + "width": "", + "align": "start" } ], "mobileBreakpoint": "sm", @@ -3975,8 +3930,8 @@ "resendOnRefresh": true, "templateScope": "local", "className": "", - "x": 610, - "y": 240, + "x": 370, + "y": 220, "wires": [ [] ] @@ -4062,30 +4017,48 @@ "y": 140, "wires": [ [ + "0ec6b9337acadf6f", "7fde0897e436c334" ] ] }, + { + "id": "0ec6b9337acadf6f", + "type": "debug", + "z": "0fd76ac156d78937", + "name": "debug 1", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "true", + "targetType": "full", + "statusVal": "", + "statusType": "auto", + "x": 880, + "y": 180, + "wires": [] + }, { "id": "7fde0897e436c334", "type": "ui-template", "z": "0fd76ac156d78937", - "group": "", - "page": "7a4e042a60b734a6", + "group": "bfd4acb7b243514f", + "page": "", "ui": "", "name": "Dialog", "order": 2, "width": 0, "height": 0, "head": "", - "format": "\n\n\n", + "format": "\n", "storeOutMessages": true, "passthru": true, "resendOnRefresh": true, - "templateScope": "widget:page", + "templateScope": "local", "className": "", "x": 850, - "y": 140, + "y": 380, "wires": [ [ "24c71b69e60e41bd" @@ -4152,7 +4125,8 @@ "y": 40, "wires": [ [ - "164baa371893de7a" + "164baa371893de7a", + "d9c0cf13cfa21a19" ] ] }, @@ -4310,7 +4284,8 @@ "y": 180, "wires": [ [ - "6532974092861ef7" + "6532974092861ef7", + "5abf944e23a57b18" ] ] }, @@ -4330,7 +4305,7 @@ "y": 180, "wires": [ [ - "70ec28e6b50bec41" + "c66ff37635c5da7a" ] ] }, @@ -4355,136 +4330,202 @@ ] }, { - "id": "70ec28e6b50bec41", - "type": "csv", + "id": "c66ff37635c5da7a", + "type": "function", "z": "14f8c9b5ce1235cc", - "name": "", - "spec": "rfc", - "sep": "\\t", - "hdrin": true, - "hdrout": "none", - "multi": "mult", - "ret": "\\r\\n", - "temp": "", - "skip": "0", - "strings": true, - "include_empty_strings": "", - "include_null_values": "", - "x": 1010, - "y": 180, + "name": "Data Processor", + "func": "// 1. Parse TSV\nconst lines = msg.payload.toString().trim().split(/\\r?\\n/);\n// Need at least header + 1 data line\nif (lines.length < 2) return null; \n\nconst headers = lines[0].split('\\t');\n// Helper to safely get value\nconst val = (row, colName) => {\n const idx = headers.indexOf(colName);\n return idx > -1 ? row[idx] : null;\n};\n\n// 2. Extract Metadata from the first valid data row (skipping [f]/[t] lines)\nlet firstRow = null;\nfor(let i=1; i {\n const v = parseFloat(val(row, key));\n item[key] = isNaN(v) ? 0 : v;\n });\n\n // Biovolume estimate (Sphere from ESD): 4/3 * pi * (r)^3\n if(item.object_equivalent_diameter) {\n const r = item.object_equivalent_diameter / 2;\n totalBiovolume += (4/3) * Math.PI * Math.pow(r, 3);\n }\n\n items.push(item);\n}\n\n// 5. Final calculations for Header\nmeta.total_objects = items.length;\nmeta.abundance = meta.vol_imaged > 0 ? (items.length / meta.vol_imaged).toFixed(0) : 0;\n// Biovolume in Millions of µm³ per mL\nmeta.est_biovolume = meta.vol_imaged > 0 ? ((totalBiovolume / 1000000) / meta.vol_imaged).toFixed(2) : 0;\n\nmsg.payload = {\n data: items,\n keys: usefulKeys,\n meta: meta\n};\n\nreturn msg;", + "outputs": 1, + "timeout": 0, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 1060, + "y": 320, "wires": [ [ - "8dc194f764cd61ef" + "bff00eae3f65f644", + "bbb2d0709f064549", + "45375ce0b837d4fb", + "905cfb1ad4803a3a", + "4244d2ed880cca66" ] ] }, { - "id": "8dc194f764cd61ef", + "id": "bff00eae3f65f644", + "type": "ui-template", + "z": "14f8c9b5ce1235cc", + "group": "3d290173d833fd04", + "page": "", + "ui": "", + "name": "Gemini 3 Pro v3", + "order": 1, + "width": 0, + "height": 0, + "head": "", + "format": "\n\n", + "storeOutMessages": true, + "passthru": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 1260, + "y": 320, + "wires": [ + [] + ] + }, + { + "id": "bbb2d0709f064549", + "type": "ui-template", + "z": "14f8c9b5ce1235cc", + "group": "3d290173d833fd04", + "page": "", + "ui": "", + "name": "UMAP", + "order": 3, + "width": 0, + "height": 0, + "head": "", + "format": "\n\n", + "storeOutMessages": true, + "passthru": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 1290, + "y": 400, + "wires": [ + [] + ] + }, + { + "id": "4b133307e9244dd1", "type": "function", "z": "14f8c9b5ce1235cc", - "name": "function 1", - "func": "// Vérifie que payload est un tableau\nif (Array.isArray(msg.payload)) {\n // Supprime la première entrée du tableau\n msg.payload = msg.payload.slice(1);\n}\n\nreturn msg;\n", + "name": "Eraser", + "func": "// Construct a \"Blank\" payload structure\n// This matches the structure the UI expects, preventing \"undefined\" errors.\n\nmsg.payload = {\n // 1. Empty Data Array (Clears all plots and the explorer)\n data: [],\n \n // 2. Empty Keys (Clears dropdown selectors)\n keys: [],\n \n // 3. Reset Metadata (Sets header text to default/zero)\n meta: {\n sample_id: 'Select a sample from the table',\n project: '-',\n acq_id: '-',\n vol_imaged: 0,\n resolution: 0,\n \n // Computed stats reset\n total_objects: 0,\n abundance: 0,\n est_biovolume: 0\n }\n};\n\nreturn msg;", "outputs": 1, "timeout": 0, "noerr": 0, "initialize": "", "finalize": "", "libs": [], - "x": 900, - "y": 420, + "x": 1090, + "y": 360, "wires": [ [ - "687050b30c8d78f3", - "86bff301d6790945", - "ddac1dedf1ed0bb3", - "mega_plankto_v3" + "bff00eae3f65f644", + "bbb2d0709f064549", + "45375ce0b837d4fb", + "4244d2ed880cca66" ] ] }, { - "id": "687050b30c8d78f3", + "id": "905cfb1ad4803a3a", "type": "debug", "z": "14f8c9b5ce1235cc", - "name": "debug 6", + "name": "debug 4", "active": true, "tosidebar": true, "console": false, "tostatus": false, - "complete": "true", - "targetType": "full", + "complete": "false", "statusVal": "", "statusType": "auto", - "x": 1260, - "y": 580, + "x": 1440, + "y": 780, "wires": [] }, { - "id": "86bff301d6790945", + "id": "45375ce0b837d4fb", "type": "ui-template", "z": "14f8c9b5ce1235cc", - "group": "44d42558adf7baf7", + "group": "3d290173d833fd04", "page": "", "ui": "", - "name": "ChatGPT 5.1 Pro v1", - "order": 1, + "name": "t-sne", + "order": 4, "width": 0, "height": 0, "head": "", - "format": "\n\n\n\n\n\n\n", + "format": "\n\n", "storeOutMessages": true, "passthru": true, "resendOnRefresh": true, "templateScope": "local", "className": "", - "x": 1300, - "y": 360, + "x": 1290, + "y": 440, "wires": [ [] ] }, { - "id": "ddac1dedf1ed0bb3", - "type": "ui-template", + "id": "d9c0cf13cfa21a19", + "type": "delay", "z": "14f8c9b5ce1235cc", - "group": "0cb96323818204ca", - "page": "", - "ui": "", - "name": "Gemini 3 Pro v2", - "order": 1, - "width": 0, - "height": 0, - "head": "", - "format": "\n\n\n\n\n\n", - "storeOutMessages": true, - "passthru": true, - "resendOnRefresh": true, - "templateScope": "local", - "className": "", - "x": 1280, - "y": 460, + "name": "", + "pauseType": "delay", + "timeout": "1", + "timeoutUnits": "seconds", + "rate": "1", + "nbRateUnits": "1", + "rateUnits": "second", + "randomFirst": "1", + "randomLast": "5", + "randomUnits": "seconds", + "drop": false, + "allowrate": false, + "outputs": 1, + "x": 780, + "y": 360, "wires": [ - [] + [ + "4b133307e9244dd1" + ] ] }, { - "id": "mega_plankto_v3", + "id": "5abf944e23a57b18", + "type": "debug", + "z": "14f8c9b5ce1235cc", + "name": "debug 3", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "true", + "targetType": "full", + "statusVal": "", + "statusType": "auto", + "x": 460, + "y": 280, + "wires": [] + }, + { + "id": "4244d2ed880cca66", "type": "ui-template", "z": "14f8c9b5ce1235cc", "group": "3d290173d833fd04", "page": "", "ui": "", - "name": "PlanktoScope Mega V3", - "order": 1, - "width": "0", - "height": "0", - "format": "\n\n\n\n\n\n\n", + "name": "Gallery", + "order": 2, + "width": 0, + "height": 0, + "head": "", + "format": "\n\n\n\n", "storeOutMessages": true, "passthru": true, "resendOnRefresh": true, "templateScope": "local", "className": "", - "x": 1310, - "y": 280, + "x": 1300, + "y": 360, "wires": [ [] ] @@ -4738,7 +4779,7 @@ "templateScope": "local", "className": "", "x": 380, - "y": 180, + "y": 140, "wires": [ [ "615edad94a4f77e7" @@ -4888,7 +4929,7 @@ "resendOnRefresh": true, "templateScope": "local", "className": "", - "x": 420, + "x": 400, "y": 140, "wires": [ [ @@ -5012,7 +5053,7 @@ "resendOnRefresh": true, "templateScope": "local", "className": "", - "x": 420, + "x": 400, "y": 140, "wires": [ [ @@ -5288,7 +5329,7 @@ "resendOnRefresh": true, "templateScope": "local", "className": "", - "x": 410, + "x": 390, "y": 140, "wires": [ [ @@ -5331,7 +5372,7 @@ "checkall": "true", "repair": false, "outputs": 2, - "x": 610, + "x": 590, "y": 140, "wires": [ [ From ad68a284455b87a1ed1bbf6656d1c4f22df38d6a Mon Sep 17 00:00:00 2001 From: Thibaut Pollina Date: Thu, 27 Nov 2025 15:14:22 +0000 Subject: [PATCH 13/36] update --- node-red/projects/dashboard/flows.json | 146 +++++-------------------- 1 file changed, 30 insertions(+), 116 deletions(-) diff --git a/node-red/projects/dashboard/flows.json b/node-red/projects/dashboard/flows.json index 07811fb70..3cb9f5f5e 100644 --- a/node-red/projects/dashboard/flows.json +++ b/node-red/projects/dashboard/flows.json @@ -4191,83 +4191,6 @@ ] ] }, - { - "id": "6532974092861ef7", - "type": "ui-table", - "z": "14f8c9b5ce1235cc", - "group": "fa6393a7d7e3b7d7", - "name": "List of acq", - "label": "", - "order": 1, - "width": 0, - "height": 0, - "maxrows": "10", - "passthru": false, - "autocols": false, - "showSearch": false, - "deselect": true, - "selectionType": "click", - "columns": [ - { - "title": "Project name", - "key": "project_name", - "keyType": "key", - "type": "text", - "width": "", - "align": "start" - }, - { - "title": "Sample ID", - "key": "sample_id", - "keyType": "key", - "type": "text", - "width": "", - "align": "start" - }, - { - "title": "Acquisition ID", - "key": "acquisition_id", - "keyType": "key", - "type": "text", - "width": "", - "align": "start" - }, - { - "title": "Number of objects", - "key": "image_acquired_count", - "keyType": "key", - "type": "text", - "width": "", - "align": "start" - }, - { - "title": "Gallery", - "key": "gallery", - "keyType": "key", - "type": "link", - "width": "", - "align": "start" - }, - { - "title": "Download", - "key": "export", - "keyType": "key", - "type": "link", - "width": "", - "align": "start" - } - ], - "mobileBreakpoint": "sm", - "mobileBreakpointType": "defaults", - "action": "replace", - "x": 570, - "y": 180, - "wires": [ - [ - "aefbf6f96248c398" - ] - ] - }, { "id": "18a10804663dbaa1", "type": "function", @@ -4284,8 +4207,7 @@ "y": 180, "wires": [ [ - "6532974092861ef7", - "5abf944e23a57b18" + "92e2ed9f115c3991" ] ] }, @@ -4301,7 +4223,7 @@ "sendError": false, "encoding": "none", "allProps": false, - "x": 880, + "x": 860, "y": 180, "wires": [ [ @@ -4321,7 +4243,7 @@ "initialize": "", "finalize": "", "libs": [], - "x": 730, + "x": 710, "y": 180, "wires": [ [ @@ -4348,7 +4270,6 @@ "bff00eae3f65f644", "bbb2d0709f064549", "45375ce0b837d4fb", - "905cfb1ad4803a3a", "4244d2ed880cca66" ] ] @@ -4424,22 +4345,6 @@ ] ] }, - { - "id": "905cfb1ad4803a3a", - "type": "debug", - "z": "14f8c9b5ce1235cc", - "name": "debug 4", - "active": true, - "tosidebar": true, - "console": false, - "tostatus": false, - "complete": "false", - "statusVal": "", - "statusType": "auto", - "x": 1440, - "y": 780, - "wires": [] - }, { "id": "45375ce0b837d4fb", "type": "ui-template", @@ -4489,23 +4394,6 @@ ] ] }, - { - "id": "5abf944e23a57b18", - "type": "debug", - "z": "14f8c9b5ce1235cc", - "name": "debug 3", - "active": true, - "tosidebar": true, - "console": false, - "tostatus": false, - "complete": "true", - "targetType": "full", - "statusVal": "", - "statusType": "auto", - "x": 460, - "y": 280, - "wires": [] - }, { "id": "4244d2ed880cca66", "type": "ui-template", @@ -4518,7 +4406,7 @@ "width": 0, "height": 0, "head": "", - "format": "\n\n\n\n", + "format": "\n\n\n\n", "storeOutMessages": true, "passthru": true, "resendOnRefresh": true, @@ -4530,6 +4418,32 @@ [] ] }, + { + "id": "92e2ed9f115c3991", + "type": "ui-template", + "z": "14f8c9b5ce1235cc", + "group": "fa6393a7d7e3b7d7", + "page": "", + "ui": "", + "name": "Selector", + "order": 1, + "width": 0, + "height": 0, + "head": "", + "format": "\n\n", + "storeOutMessages": true, + "passthru": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 560, + "y": 180, + "wires": [ + [ + "aefbf6f96248c398" + ] + ] + }, { "id": "8e11bb014b9664be", "type": "ui-template", From 7fc6eb41ad1ba13c28924f507873d6dcc035aae0 Mon Sep 17 00:00:00 2001 From: Thibaut Pollina Date: Sat, 29 Nov 2025 20:14:34 +0000 Subject: [PATCH 14/36] update --- node-red/projects/dashboard/flows.json | 336 +++++++++++++++++++++---- 1 file changed, 281 insertions(+), 55 deletions(-) diff --git a/node-red/projects/dashboard/flows.json b/node-red/projects/dashboard/flows.json index 3cb9f5f5e..601153df1 100644 --- a/node-red/projects/dashboard/flows.json +++ b/node-red/projects/dashboard/flows.json @@ -111,6 +111,14 @@ "info": "", "env": [] }, + { + "id": "7c57cfad252356b9", + "type": "tab", + "label": "Test", + "disabled": false, + "info": "", + "env": [] + }, { "id": "4ed26b8b.253504", "type": "subflow", @@ -678,15 +686,15 @@ "colors": { "surface": "#ffffff", "primary": "#1976d2", - "bgPage": "#dedede", + "bgPage": "#eef3ff", "groupBg": "#ffffff", - "groupOutline": "#9c9c9c" + "groupOutline": "#c6d1dc" }, "sizes": { "density": "default", - "pagePadding": "1rem", - "groupGap": "1rem", - "groupBorderRadius": "4px", + "pagePadding": "1.5rem", + "groupGap": "1.5rem", + "groupBorderRadius": "12px", "widgetGap": "0px" } }, @@ -721,7 +729,7 @@ "cols": "12" } ], - "order": 4, + "order": 5, "className": "", "visible": "true", "disabled": "false" @@ -855,7 +863,7 @@ "cols": "12" } ], - "order": 6, + "order": 7, "className": "", "visible": "true", "disabled": "false" @@ -905,7 +913,7 @@ "cols": "12" } ], - "order": 10, + "order": 11, "className": "", "visible": "false", "disabled": "false" @@ -955,7 +963,7 @@ "cols": "12" } ], - "order": 11, + "order": 12, "className": "", "visible": "false", "disabled": "false" @@ -1005,7 +1013,7 @@ "cols": "12" } ], - "order": 12, + "order": 13, "className": "", "visible": "false", "disabled": "false" @@ -1055,7 +1063,7 @@ "cols": "12" } ], - "order": 9, + "order": 10, "className": "", "visible": "false", "disabled": "false" @@ -1105,7 +1113,7 @@ "cols": "12" } ], - "order": 8, + "order": 9, "className": "", "visible": "true", "disabled": "false" @@ -1169,7 +1177,7 @@ "cols": "12" } ], - "order": 3, + "order": 4, "className": "", "visible": "true", "disabled": "false" @@ -1233,7 +1241,7 @@ "cols": "12" } ], - "order": 5, + "order": 6, "className": "", "visible": "true", "disabled": "false" @@ -1297,7 +1305,7 @@ "cols": "12" } ], - "order": 2, + "order": 3, "className": "", "visible": "true", "disabled": "false" @@ -1431,7 +1439,7 @@ "cols": "12" } ], - "order": 7, + "order": 8, "className": "", "visible": "true", "disabled": "false" @@ -1457,7 +1465,7 @@ "page": "d129fac8e7742d5b", "width": "12", "height": 1, - "order": 4, + "order": 6, "showTitle": false, "className": "", "visible": "true", @@ -1523,7 +1531,7 @@ "cols": "12" } ], - "order": 1, + "order": 2, "className": "", "visible": "true", "disabled": "false" @@ -1545,15 +1553,135 @@ { "id": "3d290173d833fd04", "type": "ui-group", - "name": "V3", + "name": "Experiment", + "page": "d129fac8e7742d5b", + "width": "12", + "height": 1, + "order": 5, + "showTitle": false, + "className": "", + "visible": "true", + "disabled": "false", + "groupType": "default" + }, + { + "id": "c5651e1a3e56f3f5", + "type": "ui-group", + "name": "Plots", "page": "d129fac8e7742d5b", "width": "12", "height": 1, "order": 3, "showTitle": false, "className": "", - "visible": true, - "disabled": false, + "visible": "true", + "disabled": "false", + "groupType": "default" + }, + { + "id": "0c1537ce71affc2b", + "type": "ui-group", + "name": "Gallery", + "page": "d129fac8e7742d5b", + "width": "12", + "height": 1, + "order": 4, + "showTitle": false, + "className": "", + "visible": "true", + "disabled": "false", + "groupType": "default" + }, + { + "id": "e6c6c12fea382047", + "type": "ui-page", + "name": "Test", + "ui": "e6ae26617c24c3ea", + "path": "/test", + "icon": "home", + "layout": "grid", + "theme": "f7770f0b818c3a67", + "breakpoints": [ + { + "name": "Default", + "px": "0", + "cols": "3" + }, + { + "name": "Tablet", + "px": "576", + "cols": "6" + }, + { + "name": "Small Desktop", + "px": "768", + "cols": "6" + }, + { + "name": "Desktop", + "px": "1024", + "cols": "12" + } + ], + "order": 1, + "className": "", + "visible": "true", + "disabled": "false" + }, + { + "id": "8ab8d09f947dced4", + "type": "ui-group", + "name": "Group 34", + "page": "e6c6c12fea382047", + "width": "6", + "height": 1, + "order": 1, + "showTitle": true, + "className": "", + "visible": "true", + "disabled": "false", + "groupType": "default" + }, + { + "id": "2567fe5c6e1ac69f", + "type": "ui-group", + "name": "Group 35", + "page": "e6c6c12fea382047", + "width": 6, + "height": 1, + "order": 2, + "showTitle": true, + "className": "", + "visible": "true", + "disabled": "false", + "groupType": "default" + }, + { + "id": "ec5fe3d2a48f9c58", + "type": "ui-group", + "name": "Group 36", + "page": "e6c6c12fea382047", + "width": 6, + "height": 1, + "order": 3, + "showTitle": true, + "className": "", + "visible": "true", + "disabled": "false", + "groupType": "default" + }, + { + "id": "fbdbc0b5e681eb9d", + "type": "ui-group", + "name": "Group 37", + "page": "e6c6c12fea382047", + "width": 6, + "height": 1, + "order": 4, + "showTitle": true, + "className": "", + "visible": "true", + "disabled": "false", "groupType": "default" }, { @@ -1953,7 +2081,7 @@ "width": 0, "height": 0, "head": "", - "format": "\n .v-toolbar__content {\n display:none;\n }\n\n.v-main{\n --v-layout-top: 0px !important;\n\n} \n", + "format": ".v-toolbar__content {\n display:none;\n}\n\n.v-main{\n --v-layout-top: 0px !important;\n\n} \n.v-card {\n box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1) !important;\n}", "storeOutMessages": true, "passthru": true, "resendOnRefresh": true, @@ -1984,7 +2112,7 @@ "templateScope": "widget:ui", "className": "", "x": 510, - "y": 440, + "y": 540, "wires": [ [] ] @@ -2183,7 +2311,6 @@ "wires": [ [ "cd5cd3a0277b8dba", - "37fa78ad741d783c", "04386808e694e97e" ] ] @@ -2288,22 +2415,6 @@ [] ] }, - { - "id": "37fa78ad741d783c", - "type": "debug", - "z": "1b667c6443413ced", - "name": "debug 7", - "active": true, - "tosidebar": true, - "console": false, - "tostatus": false, - "complete": "false", - "statusVal": "", - "statusType": "auto", - "x": 420, - "y": 320, - "wires": [] - }, { "id": "04386808e694e97e", "type": "ui-template", @@ -2342,7 +2453,7 @@ "width": "0", "height": "0", "head": "", - "format": "
\n \n
\n\n", + "format": "
\n \n
\n\n", "storeOutMessages": true, "passthru": true, "resendOnRefresh": true, @@ -3248,10 +3359,10 @@ "ui": "", "name": "Streaming", "order": 1, - "width": "7", - "height": "18", + "width": "0", + "height": "0", "head": "", - "format": " \n ", + "format": "
\n \n
\n\n", "storeOutMessages": true, "passthru": true, "resendOnRefresh": true, @@ -3275,7 +3386,7 @@ "width": 0, "height": 0, "head": "", - "format": "\n\n\n\n", + "format": "\n\n\n\n", "storeOutMessages": true, "passthru": true, "resendOnRefresh": true, @@ -3526,7 +3637,8 @@ "y": 200, "wires": [ [ - "46b98c54eb680c2e" + "46b98c54eb680c2e", + "b162f94dfd6bfdad" ] ] }, @@ -3625,10 +3737,28 @@ "y": 280, "wires": [ [ - "46b98c54eb680c2e" + "46b98c54eb680c2e", + "b162f94dfd6bfdad" ] ] }, + { + "id": "b162f94dfd6bfdad", + "type": "debug", + "z": "35d7387466dd0bc0", + "name": "debug 3", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "true", + "targetType": "full", + "statusVal": "", + "statusType": "auto", + "x": 1080, + "y": 140, + "wires": [] + }, { "id": "250979b4672d81b6", "type": "ui-event", @@ -4278,10 +4408,10 @@ "id": "bff00eae3f65f644", "type": "ui-template", "z": "14f8c9b5ce1235cc", - "group": "3d290173d833fd04", + "group": "c5651e1a3e56f3f5", "page": "", "ui": "", - "name": "Gemini 3 Pro v3", + "name": "Plots", "order": 1, "width": 0, "height": 0, @@ -4292,7 +4422,7 @@ "resendOnRefresh": true, "templateScope": "local", "className": "", - "x": 1260, + "x": 1230, "y": 320, "wires": [ [] @@ -4306,7 +4436,7 @@ "page": "", "ui": "", "name": "UMAP", - "order": 3, + "order": 1, "width": 0, "height": 0, "head": "", @@ -4353,7 +4483,7 @@ "page": "", "ui": "", "name": "t-sne", - "order": 4, + "order": 2, "width": 0, "height": 0, "head": "", @@ -4398,11 +4528,11 @@ "id": "4244d2ed880cca66", "type": "ui-template", "z": "14f8c9b5ce1235cc", - "group": "3d290173d833fd04", + "group": "0c1537ce71affc2b", "page": "", "ui": "", "name": "Gallery", - "order": 2, + "order": 1, "width": 0, "height": 0, "head": "", @@ -4837,7 +4967,7 @@ "width": "0", "height": "0", "head": "", - "format": "\n\n\n\n", + "format": "\n\n\n\n", "storeOutMessages": true, "passthru": true, "resendOnRefresh": true, @@ -6082,5 +6212,101 @@ "x": 470, "y": 1100, "wires": [] + }, + { + "id": "680d994257a7bd2c", + "type": "ui-template", + "z": "7c57cfad252356b9", + "group": "2567fe5c6e1ac69f", + "page": "", + "ui": "", + "name": "", + "order": 1, + "width": "0", + "height": "0", + "head": "", + "format": "
LED Control
\n\n\n Off\n On\n\n", + "storeOutMessages": true, + "passthru": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 380, + "y": 320, + "wires": [ + [] + ] + }, + { + "id": "1ed9a730b3a79981", + "type": "ui-template", + "z": "7c57cfad252356b9", + "group": "ec5fe3d2a48f9c58", + "page": "", + "ui": "", + "name": "", + "order": 1, + "width": "0", + "height": "0", + "head": "", + "format": "\n
Focus Distance
\n\n\n\n \n \n \n\n \n \n \n\n\n\n\n \n {{ value }}\n \n\n\n\n\n Up\n Stop\n Down\n\n\n\n\n \n {{ infoContent.title }}\n \n

Unit: {{ infoContent.unit }}

\n

Description: {{ infoContent.description }}

\n
\n \n \n Close\n \n
\n
\n", + "storeOutMessages": true, + "passthru": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 400, + "y": 360, + "wires": [ + [] + ] + }, + { + "id": "cebd60d90d7e3995", + "type": "ui-template", + "z": "7c57cfad252356b9", + "group": "8ab8d09f947dced4", + "page": "", + "ui": "", + "name": "Streaming", + "order": 1, + "width": "0", + "height": "0", + "head": "", + "format": "
\n \n
\n\n", + "storeOutMessages": true, + "passthru": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 370, + "y": 280, + "wires": [ + [] + ] + }, + { + "id": "365c9ec67e3efa9a", + "type": "ui-template", + "z": "7c57cfad252356b9", + "group": "fbdbc0b5e681eb9d", + "page": "", + "ui": "", + "name": "", + "order": 1, + "width": "0", + "height": "0", + "head": "", + "format": "\n
Pump Flowrate
\n\n\n \n \n \n\n \n \n \n\n\n\n\n \n {{ value }}\n \n\n\n\n
Pump Volume
\n\n\n \n \n \n\n \n \n \n\n\n\n\n \n {{ value }}\n \n\n\n\n\n Backward\n Stop\n Forward\n\n\n\n\n \n {{ infoContent.title }}\n \n

Unit: {{ infoContent.unit }}

\n

Description: {{ infoContent.description }}

\n
\n \n \n Close\n \n
\n
\n", + "storeOutMessages": true, + "passthru": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 420, + "y": 420, + "wires": [ + [] + ] } ] \ No newline at end of file From f6f22e5006557a05430473b0a90155488661b511 Mon Sep 17 00:00:00 2001 From: Thibaut Pollina Date: Sat, 29 Nov 2025 21:42:09 +0000 Subject: [PATCH 15/36] update --- node-red/projects/dashboard/flows.json | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/node-red/projects/dashboard/flows.json b/node-red/projects/dashboard/flows.json index 601153df1..13d4590b6 100644 --- a/node-red/projects/dashboard/flows.json +++ b/node-red/projects/dashboard/flows.json @@ -694,7 +694,7 @@ "density": "default", "pagePadding": "1.5rem", "groupGap": "1.5rem", - "groupBorderRadius": "12px", + "groupBorderRadius": "8px", "widgetGap": "0px" } }, @@ -2195,7 +2195,7 @@ "width": 0, "height": 0, "head": "", - "format": " .v-btn--variant-outlined.v-btn--active {\n background-color: #1976d2 !important;\n color: white !important;\n }\n\n\n.v-row+.v-row {\n margin-top: 0px !important;\n}\n\n.v-row {\n margin: 0px !important;\n}\n\n\n .v-field__field {\n background: #eef3ff;\n border-radius: 4px;\n }\n .v-btn-group .v-btn, .v-field__overlay {\n background: #eef3ff;\n }\n \n\n .v-container{\n padding: 0 !important;\n }", + "format": ".v-btn--variant-outlined.v-btn--active {\n background-color: #1976d2 !important;\n color: white !important;\n}\n\n\n.v-field__field {\n background: #eef3ff;\n border-radius: 4px;\n}\n\n.v-btn-group .v-btn, .v-field__overlay {\n background: #eef3ff;\n}\n \n", "storeOutMessages": true, "passthru": true, "resendOnRefresh": true, @@ -2453,7 +2453,7 @@ "width": "0", "height": "0", "head": "", - "format": "
\n \n
\n\n", + "format": "\n\n", "storeOutMessages": true, "passthru": true, "resendOnRefresh": true, @@ -2497,7 +2497,7 @@ "width": 0, "height": 0, "head": "", - "format": "", + "format": "", "storeOutMessages": true, "passthru": true, "resendOnRefresh": true, @@ -2697,7 +2697,7 @@ "width": "0", "height": "0", "head": "", - "format": "\n\n\n\n", + "format": "\n\n\n\n", "storeOutMessages": true, "passthru": true, "resendOnRefresh": true, @@ -2763,7 +2763,7 @@ "width": 0, "height": 0, "head": "", - "format": "\n", + "format": "", "storeOutMessages": true, "passthru": true, "resendOnRefresh": true, @@ -2849,7 +2849,7 @@ "width": 0, "height": 0, "head": "", - "format": "\n\n\n\n", + "format": "\n\n\n\n", "storeOutMessages": true, "passthru": true, "resendOnRefresh": true, @@ -2979,7 +2979,7 @@ "width": 0, "height": 0, "head": "", - "format": "\n\n", + "format": "", "storeOutMessages": true, "passthru": true, "resendOnRefresh": true, From 4e94cb249f9b190d5a48f31fb56c3602cdd3b4f1 Mon Sep 17 00:00:00 2001 From: Thibaut Pollina Date: Sat, 29 Nov 2025 22:06:16 +0000 Subject: [PATCH 16/36] update --- node-red/projects/dashboard/flows.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/node-red/projects/dashboard/flows.json b/node-red/projects/dashboard/flows.json index 13d4590b6..49cc2d927 100644 --- a/node-red/projects/dashboard/flows.json +++ b/node-red/projects/dashboard/flows.json @@ -2497,7 +2497,7 @@ "width": 0, "height": 0, "head": "", - "format": "", + "format": "", "storeOutMessages": true, "passthru": true, "resendOnRefresh": true, @@ -2697,7 +2697,7 @@ "width": "0", "height": "0", "head": "", - "format": "\n\n\n\n", + "format": "\n\n\n\n", "storeOutMessages": true, "passthru": true, "resendOnRefresh": true, @@ -2763,7 +2763,7 @@ "width": 0, "height": 0, "head": "", - "format": "", + "format": "", "storeOutMessages": true, "passthru": true, "resendOnRefresh": true, From 8e80405d06531c34582fe80c1aaf9456a2c70e5b Mon Sep 17 00:00:00 2001 From: Thibaut Pollina Date: Sat, 29 Nov 2025 22:41:35 +0000 Subject: [PATCH 17/36] update --- node-red/projects/dashboard/flows.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/node-red/projects/dashboard/flows.json b/node-red/projects/dashboard/flows.json index 49cc2d927..5b5ee8c9d 100644 --- a/node-red/projects/dashboard/flows.json +++ b/node-red/projects/dashboard/flows.json @@ -2105,7 +2105,7 @@ "width": 0, "height": 0, "head": "", - "format": "\n\n\n", + "format": "\n\n\n", "storeOutMessages": true, "passthru": true, "resendOnRefresh": true, @@ -2453,7 +2453,7 @@ "width": "0", "height": "0", "head": "", - "format": "\n\n", + "format": "\n\n", "storeOutMessages": true, "passthru": true, "resendOnRefresh": true, @@ -2497,7 +2497,7 @@ "width": 0, "height": 0, "head": "", - "format": "", + "format": "", "storeOutMessages": true, "passthru": true, "resendOnRefresh": true, @@ -2697,7 +2697,7 @@ "width": "0", "height": "0", "head": "", - "format": "\n\n\n\n", + "format": "\n\n\n\n", "storeOutMessages": true, "passthru": true, "resendOnRefresh": true, @@ -2763,7 +2763,7 @@ "width": 0, "height": 0, "head": "", - "format": "", + "format": "", "storeOutMessages": true, "passthru": true, "resendOnRefresh": true, From 6ce95d2b1ba867950fbeac67f3f57841f2d5b8a5 Mon Sep 17 00:00:00 2001 From: Thibaut Pollina Date: Sun, 30 Nov 2025 11:47:58 +0000 Subject: [PATCH 18/36] update --- node-red/projects/dashboard/flows.json | 494 ++++++++++++++++--------- 1 file changed, 315 insertions(+), 179 deletions(-) diff --git a/node-red/projects/dashboard/flows.json b/node-red/projects/dashboard/flows.json index 5b5ee8c9d..e5c001aef 100644 --- a/node-red/projects/dashboard/flows.json +++ b/node-red/projects/dashboard/flows.json @@ -111,14 +111,6 @@ "info": "", "env": [] }, - { - "id": "7c57cfad252356b9", - "type": "tab", - "label": "Test", - "disabled": false, - "info": "", - "env": [] - }, { "id": "4ed26b8b.253504", "type": "subflow", @@ -729,7 +721,7 @@ "cols": "12" } ], - "order": 5, + "order": 4, "className": "", "visible": "true", "disabled": "false" @@ -863,7 +855,7 @@ "cols": "12" } ], - "order": 7, + "order": 6, "className": "", "visible": "true", "disabled": "false" @@ -913,7 +905,7 @@ "cols": "12" } ], - "order": 11, + "order": 10, "className": "", "visible": "false", "disabled": "false" @@ -963,7 +955,7 @@ "cols": "12" } ], - "order": 12, + "order": 11, "className": "", "visible": "false", "disabled": "false" @@ -1013,7 +1005,7 @@ "cols": "12" } ], - "order": 13, + "order": 12, "className": "", "visible": "false", "disabled": "false" @@ -1063,7 +1055,7 @@ "cols": "12" } ], - "order": 10, + "order": 9, "className": "", "visible": "false", "disabled": "false" @@ -1113,7 +1105,7 @@ "cols": "12" } ], - "order": 9, + "order": 8, "className": "", "visible": "true", "disabled": "false" @@ -1177,7 +1169,7 @@ "cols": "12" } ], - "order": 4, + "order": 3, "className": "", "visible": "true", "disabled": "false" @@ -1241,7 +1233,7 @@ "cols": "12" } ], - "order": 6, + "order": 5, "className": "", "visible": "true", "disabled": "false" @@ -1292,12 +1284,12 @@ { "name": "Tablet", "px": "576", - "cols": "6" + "cols": "3" }, { "name": "Small Desktop", "px": "768", - "cols": "9" + "cols": "12" }, { "name": "Desktop", @@ -1305,7 +1297,7 @@ "cols": "12" } ], - "order": 3, + "order": 2, "className": "", "visible": "true", "disabled": "false" @@ -1439,7 +1431,7 @@ "cols": "12" } ], - "order": 8, + "order": 7, "className": "", "visible": "true", "disabled": "false" @@ -1531,7 +1523,7 @@ "cols": "12" } ], - "order": 2, + "order": 1, "className": "", "visible": "true", "disabled": "false" @@ -1593,92 +1585,112 @@ "groupType": "default" }, { - "id": "e6c6c12fea382047", - "type": "ui-page", - "name": "Test", - "ui": "e6ae26617c24c3ea", - "path": "/test", - "icon": "home", - "layout": "grid", - "theme": "f7770f0b818c3a67", - "breakpoints": [ - { - "name": "Default", - "px": "0", - "cols": "3" - }, - { - "name": "Tablet", - "px": "576", - "cols": "6" - }, - { - "name": "Small Desktop", - "px": "768", - "cols": "6" - }, - { - "name": "Desktop", - "px": "1024", - "cols": "12" - } - ], - "order": 1, + "id": "36931a9722892790", + "type": "ui-group", + "name": "Software Version", + "page": "632260133d581caa", + "width": "3", + "height": 1, + "order": 6, + "showTitle": false, "className": "", "visible": "true", - "disabled": "false" + "disabled": "false", + "groupType": "default" }, { - "id": "8ab8d09f947dced4", + "id": "2aa235120084abe4", "type": "ui-group", - "name": "Group 34", - "page": "e6c6c12fea382047", - "width": "6", + "name": "Images Acquired", + "page": "632260133d581caa", + "width": "3", "height": 1, - "order": 1, - "showTitle": true, + "order": 7, + "showTitle": false, + "className": "", + "visible": "true", + "disabled": "false", + "groupType": "default" + }, + { + "id": "df7c60bd8b265e48", + "type": "ui-group", + "name": "Objects Segmented", + "page": "632260133d581caa", + "width": "3", + "height": 1, + "order": 8, + "showTitle": false, "className": "", "visible": "true", "disabled": "false", "groupType": "default" }, { - "id": "2567fe5c6e1ac69f", + "id": "dc304678d9a6b53b", "type": "ui-group", - "name": "Group 35", - "page": "e6c6c12fea382047", - "width": 6, + "name": "Storage", + "page": "632260133d581caa", + "width": "3", + "height": 1, + "order": 9, + "showTitle": false, + "className": "", + "visible": "true", + "disabled": "false", + "groupType": "default" + }, + { + "id": "8a113b6c8e1eadb7", + "type": "ui-group", + "name": "Learn the basic", + "page": "632260133d581caa", + "width": "12", + "height": 1, + "order": 5, + "showTitle": false, + "className": "", + "visible": "true", + "disabled": "false", + "groupType": "default" + }, + { + "id": "97e552a2d05b0800", + "type": "ui-group", + "name": "Lanch the preview", + "page": "632260133d581caa", + "width": "4", "height": 1, "order": 2, - "showTitle": true, + "showTitle": false, "className": "", "visible": "true", "disabled": "false", "groupType": "default" }, { - "id": "ec5fe3d2a48f9c58", + "id": "82480e386ed6f8bd", "type": "ui-group", - "name": "Group 36", - "page": "e6c6c12fea382047", - "width": 6, + "name": "Explore your data", + "page": "632260133d581caa", + "width": "4", "height": 1, "order": 3, - "showTitle": true, + "showTitle": false, "className": "", "visible": "true", "disabled": "false", "groupType": "default" }, { - "id": "fbdbc0b5e681eb9d", + "id": "9d3e8bdd535f0e0d", "type": "ui-group", - "name": "Group 37", - "page": "e6c6c12fea382047", - "width": 6, + "name": "Run the Calibration", + "page": "632260133d581caa", + "width": "4", "height": 1, "order": 4, - "showTitle": true, + "showTitle": false, "className": "", "visible": "true", "disabled": "false", @@ -2112,7 +2124,7 @@ "templateScope": "widget:ui", "className": "", "x": 510, - "y": 540, + "y": 700, "wires": [ [] ] @@ -2310,8 +2322,12 @@ "y": 280, "wires": [ [ - "cd5cd3a0277b8dba", - "04386808e694e97e" + "04386808e694e97e", + "e68b686e94d78c9d", + "e431a9253416f9eb", + "b50dce0a86c2668c", + "07c889f3fa18b15b", + "ae546211d7d4413c" ] ] }, @@ -2383,14 +2399,14 @@ "width": "0", "height": "0", "head": "", - "format": "\n", + "format": "\n", "storeOutMessages": true, "passthru": true, "resendOnRefresh": true, "templateScope": "local", "className": "", "x": 510, - "y": 180, + "y": 280, "wires": [ [] ] @@ -2433,14 +2449,230 @@ "resendOnRefresh": true, "templateScope": "widget:ui", "className": "", - "x": 820, - "y": 240, + "x": 800, + "y": 180, "wires": [ [ "1db1c2e3e19e85ff" ] ] }, + { + "id": "e68b686e94d78c9d", + "type": "ui-template", + "z": "1b667c6443413ced", + "group": "36931a9722892790", + "page": "", + "ui": "", + "name": "Software Version", + "order": 1, + "width": "0", + "height": "0", + "head": "", + "format": "\n", + "storeOutMessages": true, + "passthru": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 550, + "y": 480, + "wires": [ + [] + ] + }, + { + "id": "e431a9253416f9eb", + "type": "ui-template", + "z": "1b667c6443413ced", + "group": "2aa235120084abe4", + "page": "", + "ui": "", + "name": "Images Acquired", + "order": 1, + "width": "0", + "height": "0", + "head": "", + "format": "\n", + "storeOutMessages": true, + "passthru": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 550, + "y": 520, + "wires": [ + [] + ] + }, + { + "id": "b50dce0a86c2668c", + "type": "ui-template", + "z": "1b667c6443413ced", + "group": "df7c60bd8b265e48", + "page": "", + "ui": "", + "name": "Objects Segmented", + "order": 1, + "width": "0", + "height": "0", + "head": "", + "format": "", + "storeOutMessages": true, + "passthru": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 560, + "y": 560, + "wires": [ + [] + ] + }, + { + "id": "07c889f3fa18b15b", + "type": "ui-template", + "z": "1b667c6443413ced", + "group": "dc304678d9a6b53b", + "page": "", + "ui": "", + "name": "Storage", + "order": 1, + "width": "0", + "height": "0", + "head": "", + "format": "", + "storeOutMessages": true, + "passthru": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 520, + "y": 600, + "wires": [ + [] + ] + }, + { + "id": "ae546211d7d4413c", + "type": "ui-template", + "z": "1b667c6443413ced", + "group": "8a113b6c8e1eadb7", + "page": "", + "ui": "", + "name": "Learn the basic", + "order": 1, + "width": "0", + "height": "0", + "head": "", + "format": "\n", + "storeOutMessages": true, + "passthru": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 540, + "y": 440, + "wires": [ + [] + ] + }, + { + "id": "6440d3bb6107d79d", + "type": "ui-template", + "z": "1b667c6443413ced", + "group": "97e552a2d05b0800", + "page": "", + "ui": "", + "name": "Lanch the preview", + "order": 1, + "width": "0", + "height": "0", + "head": "", + "format": "\n", + "storeOutMessages": true, + "passthru": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 550, + "y": 320, + "wires": [ + [] + ] + }, + { + "id": "19ee8d1758dd9c06", + "type": "ui-template", + "z": "1b667c6443413ced", + "group": "82480e386ed6f8bd", + "page": "", + "ui": "", + "name": "Explore your data", + "order": 1, + "width": "0", + "height": "0", + "head": "", + "format": "\n", + "storeOutMessages": true, + "passthru": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 550, + "y": 360, + "wires": [ + [] + ] + }, + { + "id": "0152d19f6346a058", + "type": "ui-template", + "z": "1b667c6443413ced", + "group": "9d3e8bdd535f0e0d", + "page": "", + "ui": "", + "name": "Run the Calibration", + "order": 1, + "width": "0", + "height": "0", + "head": "", + "format": "\n", + "storeOutMessages": true, + "passthru": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 550, + "y": 400, + "wires": [ + [] + ] + }, + { + "id": "683fec6ef9b340f7", + "type": "ui-template", + "z": "1b667c6443413ced", + "group": "", + "page": "632260133d581caa", + "ui": "", + "name": "CSS (All Pages)", + "order": 0, + "width": 0, + "height": 0, + "head": "", + "format": ".v-card-text{\n padding : 0 !important;\n}\n\n\n \n", + "storeOutMessages": true, + "passthru": true, + "resendOnRefresh": true, + "templateScope": "page:style", + "className": "", + "x": 1300, + "y": 80, + "wires": [ + [] + ] + }, { "id": "a57a165cd0ce511b", "type": "ui-template", @@ -2697,7 +2929,7 @@ "width": "0", "height": "0", "head": "", - "format": "\n\n\n\n", + "format": "\n\n\n\n", "storeOutMessages": true, "passthru": true, "resendOnRefresh": true, @@ -6212,101 +6444,5 @@ "x": 470, "y": 1100, "wires": [] - }, - { - "id": "680d994257a7bd2c", - "type": "ui-template", - "z": "7c57cfad252356b9", - "group": "2567fe5c6e1ac69f", - "page": "", - "ui": "", - "name": "", - "order": 1, - "width": "0", - "height": "0", - "head": "", - "format": "
LED Control
\n\n\n Off\n On\n\n", - "storeOutMessages": true, - "passthru": true, - "resendOnRefresh": true, - "templateScope": "local", - "className": "", - "x": 380, - "y": 320, - "wires": [ - [] - ] - }, - { - "id": "1ed9a730b3a79981", - "type": "ui-template", - "z": "7c57cfad252356b9", - "group": "ec5fe3d2a48f9c58", - "page": "", - "ui": "", - "name": "", - "order": 1, - "width": "0", - "height": "0", - "head": "", - "format": "\n
Focus Distance
\n\n\n\n \n \n \n\n \n \n \n\n\n\n\n \n {{ value }}\n \n\n\n\n\n Up\n Stop\n Down\n\n\n\n\n \n {{ infoContent.title }}\n \n

Unit: {{ infoContent.unit }}

\n

Description: {{ infoContent.description }}

\n
\n \n \n Close\n \n
\n
\n", - "storeOutMessages": true, - "passthru": true, - "resendOnRefresh": true, - "templateScope": "local", - "className": "", - "x": 400, - "y": 360, - "wires": [ - [] - ] - }, - { - "id": "cebd60d90d7e3995", - "type": "ui-template", - "z": "7c57cfad252356b9", - "group": "8ab8d09f947dced4", - "page": "", - "ui": "", - "name": "Streaming", - "order": 1, - "width": "0", - "height": "0", - "head": "", - "format": "
\n \n
\n\n", - "storeOutMessages": true, - "passthru": true, - "resendOnRefresh": true, - "templateScope": "local", - "className": "", - "x": 370, - "y": 280, - "wires": [ - [] - ] - }, - { - "id": "365c9ec67e3efa9a", - "type": "ui-template", - "z": "7c57cfad252356b9", - "group": "fbdbc0b5e681eb9d", - "page": "", - "ui": "", - "name": "", - "order": 1, - "width": "0", - "height": "0", - "head": "", - "format": "\n
Pump Flowrate
\n\n\n \n \n \n\n \n \n \n\n\n\n\n \n {{ value }}\n \n\n\n\n
Pump Volume
\n\n\n \n \n \n\n \n \n \n\n\n\n\n \n {{ value }}\n \n\n\n\n\n Backward\n Stop\n Forward\n\n\n\n\n \n {{ infoContent.title }}\n \n

Unit: {{ infoContent.unit }}

\n

Description: {{ infoContent.description }}

\n
\n \n \n Close\n \n
\n
\n", - "storeOutMessages": true, - "passthru": true, - "resendOnRefresh": true, - "templateScope": "local", - "className": "", - "x": 420, - "y": 420, - "wires": [ - [] - ] } ] \ No newline at end of file From 055274ce6aa69b981a19d38e724e47e18fb57d9d Mon Sep 17 00:00:00 2001 From: Thibaut Pollina Date: Sun, 30 Nov 2025 11:52:42 +0000 Subject: [PATCH 19/36] update --- node-red/projects/dashboard/flows.json | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/node-red/projects/dashboard/flows.json b/node-red/projects/dashboard/flows.json index e5c001aef..fcce4739d 100644 --- a/node-red/projects/dashboard/flows.json +++ b/node-red/projects/dashboard/flows.json @@ -2326,8 +2326,7 @@ "e68b686e94d78c9d", "e431a9253416f9eb", "b50dce0a86c2668c", - "07c889f3fa18b15b", - "ae546211d7d4413c" + "07c889f3fa18b15b" ] ] }, @@ -2565,7 +2564,7 @@ "width": "0", "height": "0", "head": "", - "format": "\n", + "format": "\n", "storeOutMessages": true, "passthru": true, "resendOnRefresh": true, @@ -2685,7 +2684,7 @@ "width": "0", "height": "0", "head": "", - "format": "\n\n", + "format": "\n\n", "storeOutMessages": true, "passthru": true, "resendOnRefresh": true, @@ -2929,7 +2928,7 @@ "width": "0", "height": "0", "head": "", - "format": "\n\n\n\n", + "format": "\n\n\n\n", "storeOutMessages": true, "passthru": true, "resendOnRefresh": true, @@ -3594,7 +3593,7 @@ "width": "0", "height": "0", "head": "", - "format": "
\n \n
\n\n", + "format": "\n\n", "storeOutMessages": true, "passthru": true, "resendOnRefresh": true, From 951abb2b97f428476b7730d25b20157cf33528f5 Mon Sep 17 00:00:00 2001 From: Thibaut Pollina Date: Sun, 30 Nov 2025 11:53:31 +0000 Subject: [PATCH 20/36] update --- node-red/projects/dashboard/flows.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/node-red/projects/dashboard/flows.json b/node-red/projects/dashboard/flows.json index fcce4739d..bbd6886de 100644 --- a/node-red/projects/dashboard/flows.json +++ b/node-red/projects/dashboard/flows.json @@ -2728,7 +2728,7 @@ "width": 0, "height": 0, "head": "", - "format": "", + "format": "", "storeOutMessages": true, "passthru": true, "resendOnRefresh": true, @@ -2994,7 +2994,7 @@ "width": 0, "height": 0, "head": "", - "format": "", + "format": "", "storeOutMessages": true, "passthru": true, "resendOnRefresh": true, From 06ef9b510ef75c590227fdb9f3dc9d66a7c8d43e Mon Sep 17 00:00:00 2001 From: Thibaut Pollina Date: Sun, 30 Nov 2025 19:06:40 +0000 Subject: [PATCH 21/36] update --- node-red/projects/dashboard/flows.json | 870 ++++++++++++++++++++----- 1 file changed, 712 insertions(+), 158 deletions(-) diff --git a/node-red/projects/dashboard/flows.json b/node-red/projects/dashboard/flows.json index bbd6886de..013285c41 100644 --- a/node-red/projects/dashboard/flows.json +++ b/node-red/projects/dashboard/flows.json @@ -863,12 +863,12 @@ { "id": "bfd4acb7b243514f", "type": "ui-group", - "name": "Table", + "name": "List of Acquisitions", "page": "7a4e042a60b734a6", "width": "12", "height": 1, "order": 3, - "showTitle": false, + "showTitle": true, "className": "", "visible": "true", "disabled": "false", @@ -1105,7 +1105,7 @@ "cols": "12" } ], - "order": 8, + "order": 7, "className": "", "visible": "true", "disabled": "false" @@ -1431,7 +1431,7 @@ "cols": "12" } ], - "order": 7, + "order": 8, "className": "", "visible": "true", "disabled": "false" @@ -1457,7 +1457,7 @@ "page": "d129fac8e7742d5b", "width": "12", "height": 1, - "order": 6, + "order": 15, "showTitle": false, "className": "", "visible": "true", @@ -1472,7 +1472,7 @@ "width": "12", "height": 1, "order": 2, - "showTitle": false, + "showTitle": true, "className": "", "visible": "true", "disabled": "false", @@ -1486,7 +1486,7 @@ "width": "12", "height": 1, "order": 2, - "showTitle": false, + "showTitle": true, "className": "", "visible": "true", "disabled": "false", @@ -1542,29 +1542,15 @@ "disabled": "false", "groupType": "default" }, - { - "id": "3d290173d833fd04", - "type": "ui-group", - "name": "Experiment", - "page": "d129fac8e7742d5b", - "width": "12", - "height": 1, - "order": 5, - "showTitle": false, - "className": "", - "visible": "true", - "disabled": "false", - "groupType": "default" - }, { "id": "c5651e1a3e56f3f5", "type": "ui-group", - "name": "Plots", + "name": "Explorer", "page": "d129fac8e7742d5b", "width": "12", "height": 1, - "order": 3, - "showTitle": false, + "order": 13, + "showTitle": true, "className": "", "visible": "true", "disabled": "false", @@ -1577,7 +1563,7 @@ "page": "d129fac8e7742d5b", "width": "12", "height": 1, - "order": 4, + "order": 14, "showTitle": false, "className": "", "visible": "true", @@ -1696,6 +1682,146 @@ "disabled": "false", "groupType": "default" }, + { + "id": "53490f9c39e2065d", + "type": "ui-group", + "name": "Informations", + "page": "d129fac8e7742d5b", + "width": "12", + "height": 1, + "order": 3, + "showTitle": true, + "className": "", + "visible": "true", + "disabled": "false", + "groupType": "default" + }, + { + "id": "90b33e458dd29d04", + "type": "ui-group", + "name": "Heat Map", + "page": "d129fac8e7742d5b", + "width": 6, + "height": 1, + "order": 4, + "showTitle": true, + "className": "", + "visible": "true", + "disabled": "false", + "groupType": "default" + }, + { + "id": "47800358cf7cee25", + "type": "ui-group", + "name": "ESD Histogram", + "page": "d129fac8e7742d5b", + "width": 6, + "height": 1, + "order": 5, + "showTitle": true, + "className": "", + "visible": "true", + "disabled": "false", + "groupType": "default" + }, + { + "id": "1d9c6927a0e0a71b", + "type": "ui-group", + "name": "Timeline", + "page": "d129fac8e7742d5b", + "width": "12", + "height": 1, + "order": 6, + "showTitle": true, + "className": "", + "visible": "true", + "disabled": "false", + "groupType": "default" + }, + { + "id": "03ad1c8f517e8769", + "type": "ui-group", + "name": "Colorspace", + "page": "d129fac8e7742d5b", + "width": "4", + "height": 1, + "order": 7, + "showTitle": true, + "className": "", + "visible": "true", + "disabled": "false", + "groupType": "default" + }, + { + "id": "0be15f8190e6fd43", + "type": "ui-group", + "name": "Aspect", + "page": "d129fac8e7742d5b", + "width": "4", + "height": 1, + "order": 8, + "showTitle": true, + "className": "", + "visible": "true", + "disabled": "false", + "groupType": "default" + }, + { + "id": "1edc962c44888abc", + "type": "ui-group", + "name": "Greenness", + "page": "d129fac8e7742d5b", + "width": "4", + "height": 1, + "order": 9, + "showTitle": true, + "className": "", + "visible": "true", + "disabled": "false", + "groupType": "default" + }, + { + "id": "67c63cc92c23c4b1", + "type": "ui-group", + "name": "Complexity", + "page": "d129fac8e7742d5b", + "width": "4", + "height": 1, + "order": 10, + "showTitle": true, + "className": "", + "visible": "true", + "disabled": "false", + "groupType": "default" + }, + { + "id": "95a152a68b7ba779", + "type": "ui-group", + "name": "Texture", + "page": "d129fac8e7742d5b", + "width": "4", + "height": 1, + "order": 11, + "showTitle": true, + "className": "", + "visible": "true", + "disabled": "false", + "groupType": "default" + }, + { + "id": "1baa943e505febf1", + "type": "ui-group", + "name": "Solidity", + "page": "d129fac8e7742d5b", + "width": "4", + "height": 1, + "order": 12, + "showTitle": true, + "className": "", + "visible": "true", + "disabled": "false", + "groupType": "default" + }, { "id": "82099021.9ceb08", "type": "file", @@ -2059,28 +2185,9 @@ "x": 370, "y": 140, "wires": [ - [ - "b49055c50dca065a" - ] + [] ] }, - { - "id": "b49055c50dca065a", - "type": "debug", - "z": "07f3b717f2a2c8c7", - "name": "debug 2", - "active": true, - "tosidebar": true, - "console": false, - "tostatus": false, - "complete": "true", - "targetType": "full", - "statusVal": "", - "statusType": "auto", - "x": 660, - "y": 140, - "wires": [] - }, { "id": "c8caad8136e6c5e3", "type": "ui-template", @@ -2684,7 +2791,7 @@ "width": "0", "height": "0", "head": "", - "format": "\n\n", + "format": "\n\n", "storeOutMessages": true, "passthru": true, "resendOnRefresh": true, @@ -2728,7 +2835,7 @@ "width": 0, "height": 0, "head": "", - "format": "", + "format": "", "storeOutMessages": true, "passthru": true, "resendOnRefresh": true, @@ -2994,7 +3101,7 @@ "width": 0, "height": 0, "head": "", - "format": "", + "format": "", "storeOutMessages": true, "passthru": true, "resendOnRefresh": true, @@ -3593,7 +3700,7 @@ "width": "0", "height": "0", "head": "", - "format": "\n\n", + "format": "\n\n", "storeOutMessages": true, "passthru": true, "resendOnRefresh": true, @@ -4486,8 +4593,8 @@ "y": 40, "wires": [ [ - "164baa371893de7a", - "d9c0cf13cfa21a19" + "384aa9ab83bfe566", + "164baa371893de7a" ] ] }, @@ -4584,11 +4691,23 @@ "sendError": false, "encoding": "none", "allProps": false, - "x": 860, - "y": 180, + "x": 180, + "y": 380, "wires": [ [ - "c66ff37635c5da7a" + "4caf5c846b3b9d01", + "a5ef1df890e7bb36", + "8cdf3c5527b9cb21", + "e26832811411f850", + "448a5c6830c6514f", + "ac9d555964747130", + "11323481847790c0", + "8425012e07c3700f", + "512e86195d14e773", + "9be1cc713ac79a7b", + "63ce87d36ae8f172", + "9abf70a949e76eaf", + "bdf84e9e1e0caa90" ] ] }, @@ -4604,207 +4723,642 @@ "initialize": "", "finalize": "", "libs": [], - "x": 710, + "x": 790, "y": 180, "wires": [ [ - "5ec998719a884cfe" - ] - ] - }, - { - "id": "c66ff37635c5da7a", - "type": "function", - "z": "14f8c9b5ce1235cc", - "name": "Data Processor", - "func": "// 1. Parse TSV\nconst lines = msg.payload.toString().trim().split(/\\r?\\n/);\n// Need at least header + 1 data line\nif (lines.length < 2) return null; \n\nconst headers = lines[0].split('\\t');\n// Helper to safely get value\nconst val = (row, colName) => {\n const idx = headers.indexOf(colName);\n return idx > -1 ? row[idx] : null;\n};\n\n// 2. Extract Metadata from the first valid data row (skipping [f]/[t] lines)\nlet firstRow = null;\nfor(let i=1; i {\n const v = parseFloat(val(row, key));\n item[key] = isNaN(v) ? 0 : v;\n });\n\n // Biovolume estimate (Sphere from ESD): 4/3 * pi * (r)^3\n if(item.object_equivalent_diameter) {\n const r = item.object_equivalent_diameter / 2;\n totalBiovolume += (4/3) * Math.PI * Math.pow(r, 3);\n }\n\n items.push(item);\n}\n\n// 5. Final calculations for Header\nmeta.total_objects = items.length;\nmeta.abundance = meta.vol_imaged > 0 ? (items.length / meta.vol_imaged).toFixed(0) : 0;\n// Biovolume in Millions of µm³ per mL\nmeta.est_biovolume = meta.vol_imaged > 0 ? ((totalBiovolume / 1000000) / meta.vol_imaged).toFixed(2) : 0;\n\nmsg.payload = {\n data: items,\n keys: usefulKeys,\n meta: meta\n};\n\nreturn msg;", - "outputs": 1, - "timeout": 0, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 1060, - "y": 320, - "wires": [ - [ - "bff00eae3f65f644", - "bbb2d0709f064549", - "45375ce0b837d4fb", - "4244d2ed880cca66" + "d77ed8081a657507", + "384aa9ab83bfe566" ] ] }, { - "id": "bff00eae3f65f644", + "id": "4244d2ed880cca66", "type": "ui-template", "z": "14f8c9b5ce1235cc", - "group": "c5651e1a3e56f3f5", + "group": "0c1537ce71affc2b", "page": "", "ui": "", - "name": "Plots", + "name": "Gallery", "order": 1, "width": 0, "height": 0, "head": "", - "format": "\n\n", + "format": "\n\n\n\n\n\n", "storeOutMessages": true, "passthru": true, "resendOnRefresh": true, "templateScope": "local", "className": "", - "x": 1230, - "y": 320, + "x": 820, + "y": 960, "wires": [ [] ] }, { - "id": "bbb2d0709f064549", + "id": "92e2ed9f115c3991", "type": "ui-template", "z": "14f8c9b5ce1235cc", - "group": "3d290173d833fd04", + "group": "fa6393a7d7e3b7d7", "page": "", "ui": "", - "name": "UMAP", + "name": "List of Segmentation", "order": 1, "width": 0, "height": 0, "head": "", - "format": "\n\n", + "format": "\n\n", "storeOutMessages": true, "passthru": true, "resendOnRefresh": true, "templateScope": "local", "className": "", - "x": 1290, - "y": 400, - "wires": [ - [] - ] - }, - { - "id": "4b133307e9244dd1", - "type": "function", - "z": "14f8c9b5ce1235cc", - "name": "Eraser", - "func": "// Construct a \"Blank\" payload structure\n// This matches the structure the UI expects, preventing \"undefined\" errors.\n\nmsg.payload = {\n // 1. Empty Data Array (Clears all plots and the explorer)\n data: [],\n \n // 2. Empty Keys (Clears dropdown selectors)\n keys: [],\n \n // 3. Reset Metadata (Sets header text to default/zero)\n meta: {\n sample_id: 'Select a sample from the table',\n project: '-',\n acq_id: '-',\n vol_imaged: 0,\n resolution: 0,\n \n // Computed stats reset\n total_objects: 0,\n abundance: 0,\n est_biovolume: 0\n }\n};\n\nreturn msg;", - "outputs": 1, - "timeout": 0, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 1090, - "y": 360, + "x": 600, + "y": 180, "wires": [ [ - "bff00eae3f65f644", - "bbb2d0709f064549", - "45375ce0b837d4fb", - "4244d2ed880cca66" + "aefbf6f96248c398" ] ] }, { - "id": "45375ce0b837d4fb", + "id": "2ca2f83cc939607f", "type": "ui-template", "z": "14f8c9b5ce1235cc", - "group": "3d290173d833fd04", + "group": "53490f9c39e2065d", "page": "", "ui": "", - "name": "t-sne", - "order": 2, + "name": "Infos", + "order": 1, "width": 0, "height": 0, "head": "", - "format": "\n\n", + "format": "\n\n\n\n\n", "storeOutMessages": true, "passthru": true, "resendOnRefresh": true, "templateScope": "local", "className": "", - "x": 1290, + "x": 810, "y": 440, "wires": [ [] ] }, { - "id": "d9c0cf13cfa21a19", - "type": "delay", + "id": "4caf5c846b3b9d01", + "type": "function", "z": "14f8c9b5ce1235cc", - "name": "", - "pauseType": "delay", - "timeout": "1", - "timeoutUnits": "seconds", - "rate": "1", - "nbRateUnits": "1", - "rateUnits": "second", - "randomFirst": "1", - "randomLast": "5", - "randomUnits": "seconds", - "drop": false, - "allowrate": false, + "name": "HEATMAP (object_x, object_y)", + "func": "// Heatmap – Flowcell distribution\n\nconst lines = msg.payload.toString().trim().split(/\\r?\\n/);\nif (lines.length < 2) return null;\n\nconst headers = lines[0].split(\"\\t\");\nconst val = (row, col) => {\n const idx = headers.indexOf(col);\n return idx >= 0 ? row[idx] : null;\n};\n\n// Meta from first valid row\nlet firstRow = null;\nfor (let i = 1; i < lines.length; i++) {\n if (!lines[i].trim().startsWith(\"[\")) {\n firstRow = lines[i].split(\"\\t\");\n break;\n }\n}\nif (!firstRow) return null;\n\nconst meta = {\n sample_id: val(firstRow, \"sample_id\") || \"\",\n project: val(firstRow, \"sample_project\") || \"\",\n acq_id: val(firstRow, \"acq_id\") || \"\"\n};\n\n// Data: x/y for heatmap\nconst data = lines.slice(1)\n .filter(l => !l.trim().startsWith(\"[\"))\n .map(l => {\n const r = l.split(\"\\t\");\n return {\n x: parseFloat(val(r, \"object_x\")),\n y: parseFloat(val(r, \"object_y\"))\n };\n });\n\nmsg.payload = { meta, data };\nreturn msg;\n", "outputs": 1, - "x": 780, - "y": 360, + "timeout": 0, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 510, + "y": 500, "wires": [ [ - "4b133307e9244dd1" + "0c90eb450c8d9b01" ] ] }, { - "id": "4244d2ed880cca66", - "type": "ui-template", + "id": "a5ef1df890e7bb36", + "type": "function", "z": "14f8c9b5ce1235cc", - "group": "0c1537ce71affc2b", - "page": "", - "ui": "", - "name": "Gallery", - "order": 1, - "width": 0, - "height": 0, - "head": "", - "format": "\n\n\n\n", - "storeOutMessages": true, - "passthru": true, + "name": "ESD Histogram (object_equivalent_diameter)", + "func": "// ESD Size Spectrum – TSV → {meta, data[{d}]}\n\nconst lines = msg.payload.toString().trim().split(/\\r?\\n/);\nif (lines.length < 2) return null;\n\nconst headers = lines[0].split(\"\\t\");\nconst val = (row, col) => {\n const idx = headers.indexOf(col);\n return idx >= 0 ? row[idx] : null;\n};\n\nlet firstRow = null;\nfor (let i = 1; i < lines.length; i++) {\n if (!lines[i].startsWith(\"[\")) {\n firstRow = lines[i].split(\"\\t\");\n break;\n }\n}\nconst meta = {\n sample_id: firstRow ? (val(firstRow, \"sample_id\") || \"\") : \"\"\n};\n\nconst data = lines.slice(1)\n .filter(l => !l.startsWith(\"[\"))\n .map(l => {\n const r = l.split(\"\\t\");\n return {\n d: parseFloat(val(r, \"object_equivalent_diameter\"))\n };\n });\n\nmsg.payload = { meta, data };\nreturn msg;\n", + "outputs": 1, + "timeout": 0, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 550, + "y": 540, + "wires": [ + [ + "0a559ab05af2548e" + ] + ] + }, + { + "id": "8cdf3c5527b9cb21", + "type": "function", + "z": "14f8c9b5ce1235cc", + "name": "TIMELINE (sequence index + area)", + "func": "// Timeline – seq index vs object_area\n\nconst lines = msg.payload.toString().trim().split(/\\r?\\n/);\nif (lines.length < 2) return null;\n\nconst headers = lines[0].split(\"\\t\");\nconst val = (row, col) => {\n const idx = headers.indexOf(col);\n return idx >= 0 ? row[idx] : null;\n};\n\nlet seq = 0;\nconst data = lines.slice(1)\n .filter(l => !l.startsWith(\"[\"))\n .map(l => {\n const r = l.split(\"\\t\");\n return {\n seq: seq++,\n area: parseFloat(val(r, \"object_area\"))\n };\n });\n\nmsg.payload = { data };\nreturn msg;\n", + "outputs": 1, + "timeout": 0, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 520, + "y": 580, + "wires": [ + [ + "7b9d103ec5981051" + ] + ] + }, + { + "id": "e26832811411f850", + "type": "function", + "z": "14f8c9b5ce1235cc", + "name": "COLORSPACE (Saturation vs Value)", + "func": "// Colorspace – MeanSaturation vs MeanValue\n\nconst lines = msg.payload.toString().trim().split(/\\r?\\n/);\nif (lines.length < 2) return null;\n\nconst headers = lines[0].split(\"\\t\");\nconst val = (row, col) => {\n const idx = headers.indexOf(col);\n return idx >= 0 ? row[idx] : null;\n};\n\nconst data = lines.slice(1)\n .filter(l => !l.startsWith(\"[\"))\n .map(l => {\n const r = l.split(\"\\t\");\n return {\n s: parseFloat(val(r, \"object_MeanSaturation\")),\n v: parseFloat(val(r, \"object_MeanValue\"))\n };\n });\n\nmsg.payload = { data };\nreturn msg;\n", + "outputs": 1, + "timeout": 0, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 530, + "y": 620, + "wires": [ + [ + "23a78a078fa1eca8" + ] + ] + }, + { + "id": "448a5c6830c6514f", + "type": "function", + "z": "14f8c9b5ce1235cc", + "name": "ASPECT (Width vs Height)", + "func": "// Aspect – width vs height\n\nconst lines = msg.payload.toString().trim().split(/\\r?\\n/);\nif (lines.length < 2) return null;\n\nconst headers = lines[0].split(\"\\t\");\nconst val = (row, col) => {\n const idx = headers.indexOf(col);\n return idx >= 0 ? row[idx] : null;\n};\n\nconst data = lines.slice(1)\n .filter(l => !l.startsWith(\"[\"))\n .map(l => {\n const r = l.split(\"\\t\");\n return {\n w: parseFloat(val(r, \"object_width\")),\n h: parseFloat(val(r, \"object_height\"))\n };\n });\n\nmsg.payload = { data };\nreturn msg;\n", + "outputs": 1, + "timeout": 0, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 500, + "y": 660, + "wires": [ + [ + "09f4914fc0a54566" + ] + ] + }, + { + "id": "ac9d555964747130", + "type": "function", + "z": "14f8c9b5ce1235cc", + "name": "GREENNESS (custom index + circularity)", + "func": "// Greenness vs Circularity – custom_greenness + object_circ.\n\nconst lines = msg.payload.toString().trim().split(/\\r?\\n/);\nif (lines.length < 2) return null;\n\nconst headers = lines[0].split(\"\\t\");\nconst val = (row, col) => {\n const idx = headers.indexOf(col);\n return idx >= 0 ? row[idx] : null;\n};\n\nconst data = lines.slice(1)\n .filter(l => !l.startsWith(\"[\"))\n .map(l => {\n const r = l.split(\"\\t\");\n const hue = parseFloat(val(r, \"object_MeanHue\")) || 0;\n const circ = parseFloat(val(r, \"object_circ.\")) || 0;\n const greenDist = Math.abs(hue - 80);\n const g = Math.max(0, 100 - greenDist);\n return { g, circ };\n });\n\nmsg.payload = { data };\nreturn msg;\n", + "outputs": 1, + "timeout": 0, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 540, + "y": 700, + "wires": [ + [ + "7f4d047a4c73eb61" + ] + ] + }, + { + "id": "11323481847790c0", + "type": "function", + "z": "14f8c9b5ce1235cc", + "name": "COMPLEXITY (Area vs Perimeter)", + "func": "// Complexity – Area vs Perimeter\n\nconst lines = msg.payload.toString().trim().split(/\\r?\\n/);\nif (lines.length < 2) return null;\n\nconst headers = lines[0].split(\"\\t\");\nconst val = (row, col) => {\n const idx = headers.indexOf(col);\n return idx >= 0 ? row[idx] : null;\n};\n\nconst data = lines.slice(1)\n .filter(l => !l.startsWith(\"[\"))\n .map(l => {\n const r = l.split(\"\\t\");\n return {\n area: parseFloat(val(r, \"object_area\")),\n per: parseFloat(val(r, \"object_perim.\"))\n };\n });\n\nmsg.payload = { data };\nreturn msg;\n", + "outputs": 1, + "timeout": 0, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 520, + "y": 740, + "wires": [ + [ + "a7c8654291022da1" + ] + ] + }, + { + "id": "8425012e07c3700f", + "type": "function", + "z": "14f8c9b5ce1235cc", + "name": "TEXTURE (Area vs StdValue)", + "func": "// Texture – Area vs StdValue\n\nconst lines = msg.payload.toString().trim().split(/\\r?\\n/);\nif (lines.length < 2) return null;\n\nconst headers = lines[0].split(\"\\t\");\nconst val = (row, col) => {\n const idx = headers.indexOf(col);\n return idx >= 0 ? row[idx] : null;\n};\n\nconst data = lines.slice(1)\n .filter(l => !l.startsWith(\"[\"))\n .map(l => {\n const r = l.split(\"\\t\");\n return {\n area: parseFloat(val(r, \"object_area\")),\n std: parseFloat(val(r, \"object_StdValue\"))\n };\n });\n\nmsg.payload = { data };\nreturn msg;\n", + "outputs": 1, + "timeout": 0, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 500, + "y": 780, + "wires": [ + [ + "ed9d6e31cc3a01b1" + ] + ] + }, + { + "id": "512e86195d14e773", + "type": "function", + "z": "14f8c9b5ce1235cc", + "name": "SOLIDITY (Histogram)", + "func": "// Solidity – histogram of object_solidity\n\nconst lines = msg.payload.toString().trim().split(/\\r?\\n/);\nif (lines.length < 2) return null;\n\nconst headers = lines[0].split(\"\\t\");\nconst val = (row, col) => {\n const idx = headers.indexOf(col);\n return idx >= 0 ? row[idx] : null;\n};\n\nconst data = lines.slice(1)\n .filter(l => !l.startsWith(\"[\"))\n .map(l => {\n const r = l.split(\"\\t\");\n return {\n sol: parseFloat(val(r, \"object_solidity\"))\n };\n });\n\nmsg.payload = { data };\nreturn msg;\n", + "outputs": 1, + "timeout": 0, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 480, + "y": 820, + "wires": [ + [ + "c15ef66d7e115bd3" + ] + ] + }, + { + "id": "0c90eb450c8d9b01", + "type": "ui-template", + "z": "14f8c9b5ce1235cc", + "group": "90b33e458dd29d04", + "page": "", + "ui": "", + "name": "Heatmap", + "order": 1, + "width": 0, + "height": 0, + "head": "", + "format": "\n\n\n\n\n", + "storeOutMessages": true, + "passthru": true, "resendOnRefresh": true, "templateScope": "local", "className": "", - "x": 1300, - "y": 360, + "x": 820, + "y": 500, "wires": [ [] ] }, { - "id": "92e2ed9f115c3991", + "id": "9be1cc713ac79a7b", + "type": "function", + "z": "14f8c9b5ce1235cc", + "name": "Sample Identity Metadata Only", + "func": "// FUNCTION: Extract minimal metadata for the “Sample Identity” panel\n\nconst lines = msg.payload.toString().trim().split(/\\r?\\n/);\nif (lines.length < 2) return null;\n\nconst headers = lines[0].split(\"\\t\");\nconst val = (row, col) => {\n const idx = headers.indexOf(col);\n return idx >= 0 ? row[idx] : null;\n};\n\n// --- 1. Find first data row (skip [f] / [t] metadata-like lines) ---\nlet firstRow = null;\nfor (let i = 1; i < lines.length; i++) {\n if (!lines[i].trim().startsWith(\"[\")) {\n firstRow = lines[i].split(\"\\t\");\n break;\n }\n}\nif (!firstRow) return null;\n\n// --- 2. Extract minimal metadata ---\nconst meta = {\n sample_id: val(firstRow, \"sample_id\") || \"\",\n project: val(firstRow, \"sample_project\") || \"\",\n acq_id: val(firstRow, \"acq_id\") || \"\"\n};\n\n// --- 3. Output ONLY the metadata ---\nmsg.payload = { meta };\nreturn msg;\n", + "outputs": 1, + "timeout": 0, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 510, + "y": 440, + "wires": [ + [ + "2ca2f83cc939607f" + ] + ] + }, + { + "id": "0a559ab05af2548e", "type": "ui-template", "z": "14f8c9b5ce1235cc", - "group": "fa6393a7d7e3b7d7", + "group": "47800358cf7cee25", "page": "", "ui": "", - "name": "Selector", + "name": "ESD Histogram", "order": 1, "width": 0, "height": 0, "head": "", - "format": "\n\n", + "format": "\n\n\n", "storeOutMessages": true, "passthru": true, "resendOnRefresh": true, "templateScope": "local", "className": "", - "x": 560, - "y": 180, + "x": 840, + "y": 540, + "wires": [ + [] + ] + }, + { + "id": "7b9d103ec5981051", + "type": "ui-template", + "z": "14f8c9b5ce1235cc", + "group": "1d9c6927a0e0a71b", + "page": "", + "ui": "", + "name": "Timeline", + "order": 1, + "width": 0, + "height": 0, + "head": "", + "format": "\n\n\n", + "storeOutMessages": true, + "passthru": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 820, + "y": 580, + "wires": [ + [] + ] + }, + { + "id": "23a78a078fa1eca8", + "type": "ui-template", + "z": "14f8c9b5ce1235cc", + "group": "03ad1c8f517e8769", + "page": "", + "ui": "", + "name": "Colorspace (Saturation vs Value)", + "order": 1, + "width": 0, + "height": 0, + "head": "", + "format": "\n\n\n\n\n", + "storeOutMessages": true, + "passthru": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 890, + "y": 620, + "wires": [ + [] + ] + }, + { + "id": "09f4914fc0a54566", + "type": "ui-template", + "z": "14f8c9b5ce1235cc", + "group": "0be15f8190e6fd43", + "page": "", + "ui": "", + "name": "Aspect (Width vs Height)", + "order": 1, + "width": 0, + "height": 0, + "head": "", + "format": "\n\n\n\n\n", + "storeOutMessages": true, + "passthru": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 870, + "y": 660, + "wires": [ + [] + ] + }, + { + "id": "7f4d047a4c73eb61", + "type": "ui-template", + "z": "14f8c9b5ce1235cc", + "group": "1edc962c44888abc", + "page": "", + "ui": "", + "name": "Greenness", + "order": 1, + "width": 0, + "height": 0, + "head": "", + "format": "\n\n\n\n\n", + "storeOutMessages": true, + "passthru": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 830, + "y": 700, + "wires": [ + [] + ] + }, + { + "id": "a7c8654291022da1", + "type": "ui-template", + "z": "14f8c9b5ce1235cc", + "group": "67c63cc92c23c4b1", + "page": "", + "ui": "", + "name": "Complexity (Area vs Perimeter)", + "order": 1, + "width": 0, + "height": 0, + "head": "", + "format": "\n\n\n\n\n", + "storeOutMessages": true, + "passthru": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 890, + "y": 740, + "wires": [ + [] + ] + }, + { + "id": "ed9d6e31cc3a01b1", + "type": "ui-template", + "z": "14f8c9b5ce1235cc", + "group": "95a152a68b7ba779", + "page": "", + "ui": "", + "name": "Texture (Area vs Std)", + "order": 1, + "width": 0, + "height": 0, + "head": "", + "format": "\n\n\n\n\n", + "storeOutMessages": true, + "passthru": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 860, + "y": 780, + "wires": [ + [] + ] + }, + { + "id": "c15ef66d7e115bd3", + "type": "ui-template", + "z": "14f8c9b5ce1235cc", + "group": "1baa943e505febf1", + "page": "", + "ui": "", + "name": "Solidity Histogram", + "order": 1, + "width": 0, + "height": 0, + "head": "", + "format": "\n\n\n\n\n", + "storeOutMessages": true, + "passthru": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 850, + "y": 820, + "wires": [ + [] + ] + }, + { + "id": "63ce87d36ae8f172", + "type": "function", + "z": "14f8c9b5ce1235cc", + "name": "EXPLORER", + "func": "// Explorer Function Node\n// TSV → {meta, keys, data: [{id, url, ...keys}]}\n\nconst lines = msg.payload.toString().trim().split(/\\r?\\n/);\nif (lines.length < 2) return null;\n\n// 1. Parse Headers\nconst headers = lines[0].split(\"\\t\");\nconst val = (row, col) => {\n const i = headers.indexOf(col);\n return i >= 0 ? row[i] : null;\n};\n\n// 2. Extract Meta from first valid data row\nlet firstRow = null;\nfor (let i = 1; i < lines.length; i++) {\n // Skip rows starting with '[' (metadata comments)\n if (!lines[i].trim().startsWith(\"[\")) {\n firstRow = lines[i].split(\"\\t\");\n break;\n }\n}\n\n// Default meta\nconst meta = {\n sample_id: \"\",\n project: \"\",\n acq_id: \"\",\n resolution: 1.0 // Default fallback\n};\n\nif (firstRow) {\n meta.sample_id = val(firstRow, \"sample_id\") || \"\";\n meta.project = val(firstRow, \"sample_project\") || \"\";\n meta.acq_id = val(firstRow, \"acq_id\") || \"\";\n \n // CRITICAL: Extract pixel size (microns per pixel)\n // If process_pixel is missing or 0, default to 1 to prevent division by zero\n const px = parseFloat(val(firstRow, \"process_pixel\"));\n if (!isNaN(px) && px > 0) {\n meta.resolution = px;\n }\n}\n\n// 3. Explorer Keys to extract\nconst explorerKeys = [\n \"object_area\", \"object_equivalent_diameter\", \"object_perim.\",\n \"object_major\", \"object_minor\", \"object_width\", \"object_height\",\n \"object_circ.\", \"object_elongation\", \"object_solidity\",\n \"object_eccentricity\", \"object_MeanHue\", \"object_MeanSaturation\",\n \"object_MeanValue\", \"object_StdValue\"\n];\n\nlet seq = 0;\nconst data = [];\n\n// 4. Process Data Lines\nfor (let i = 1; i < lines.length; i++) {\n const line = lines[i];\n if (line.trim().startsWith(\"[\")) continue;\n\n const row = line.split(\"\\t\");\n\n const item = {\n id: val(row, \"object_id\"),\n // Construct URL\n url: `/ps/data/browse/api/preview/big/objects/${val(row,\"object_date\")}/${val(row,\"sample_id\")}/${val(row,\"acq_id\")}/${val(row,\"img_file_name\")}`,\n sequence_index: seq++\n };\n\n // Parse numeric keys\n explorerKeys.forEach(k => {\n let v = parseFloat(val(row, k));\n item[k] = isNaN(v) ? 0 : v;\n });\n\n // Custom calc for greenness (example)\n const hue = item.object_MeanHue || 0;\n const dist = Math.abs(hue - 80);\n item.custom_greenness = Math.max(0, 100 - dist);\n\n data.push(item);\n}\n\nmsg.payload = {\n meta,\n keys: explorerKeys,\n data\n};\n\nreturn msg;", + "outputs": 1, + "timeout": 0, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 450, + "y": 900, "wires": [ [ - "aefbf6f96248c398" + "d57a5bca66c04989" + ] + ] + }, + { + "id": "d57a5bca66c04989", + "type": "ui-template", + "z": "14f8c9b5ce1235cc", + "group": "c5651e1a3e56f3f5", + "page": "", + "ui": "", + "name": "Explorer", + "order": 1, + "width": 0, + "height": 0, + "head": "", + "format": "\n\n\n\n", + "storeOutMessages": true, + "passthru": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 820, + "y": 900, + "wires": [ + [] + ] + }, + { + "id": "384aa9ab83bfe566", + "type": "function", + "z": "14f8c9b5ce1235cc", + "name": "Clearing plots", + "func": "// This function clears the plot by sending an empty data array.\n// Any template using msg.payload.data will immediately purge the plot.\n\nmsg.payload = {\n data: []\n};\n\nreturn msg;\n", + "outputs": 1, + "timeout": 0, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 580, + "y": 380, + "wires": [ + [ + "0c90eb450c8d9b01", + "0a559ab05af2548e", + "7b9d103ec5981051", + "23a78a078fa1eca8", + "09f4914fc0a54566", + "7f4d047a4c73eb61", + "a7c8654291022da1", + "ed9d6e31cc3a01b1", + "c15ef66d7e115bd3", + "2ca2f83cc939607f", + "d57a5bca66c04989", + "4244d2ed880cca66" + ] + ] + }, + { + "id": "9abf70a949e76eaf", + "type": "function", + "z": "14f8c9b5ce1235cc", + "name": "GALLERY", + "func": "// GALLERY Function Node\n// TSV → { data: [], meta: { resolution: ... }, keys: ... }\n\n// 1. Handle Clear Signals\nif (msg.clear === true || (msg.payload && msg.payload.clear === true)) {\n msg.payload = { data: [], meta: { resolution: 1 }, keys: [] };\n return msg;\n}\n\n// 2. Validate Payload\nif (!msg.payload) return null;\nconst lines = msg.payload.toString().trim().split(/\\r?\\n/);\nif (lines.length < 2) {\n msg.payload = { data: [], meta: { resolution: 1 }, keys: [] };\n return msg;\n}\n\n// 3. Parse Headers\nconst headers = lines[0].split('\\t');\nconst get = (row, key) => {\n const idx = headers.indexOf(key);\n return idx >= 0 ? row[idx] : null;\n};\n\n// 4. Extract Meta (Resolution is key here)\nlet firstRow = null;\nfor (let i = 1; i < lines.length; i++) {\n const L = lines[i].trim();\n if (!L.startsWith('[') && L !== '') {\n firstRow = lines[i].split('\\t');\n break;\n }\n}\n\nconst meta = {\n sample_id: 'N/A',\n project: 'N/A',\n acq_id: 'N/A',\n resolution: 1.0 // Default fallback\n};\n\nif (firstRow) {\n meta.sample_id = get(firstRow, 'sample_id') || \"\";\n meta.project = get(firstRow, 'sample_project') || \"\";\n meta.acq_id = get(firstRow, 'acq_id') || \"\";\n\n // CRITICAL: Extract microns per pixel\n const px = parseFloat(get(firstRow, 'process_pixel'));\n if (!isNaN(px) && px > 0) {\n meta.resolution = px;\n }\n}\n\n// 5. Select Keys to Display\nconst usefulKeys = [\n \"object_area\", \"object_width\", \"object_height\",\n \"object_equivalent_diameter\", \"object_major\", \"object_minor\",\n \"object_MeanHue\", \"object_elongation\"\n];\n\nconst data = [];\n\n// 6. Process Rows\nfor (let i = 1; i < lines.length; i++) {\n const rowLine = lines[i].trim();\n if (rowLine.startsWith('[') || rowLine === '') continue;\n\n const row = rowLine.split('\\t');\n if (row.length !== headers.length) continue;\n\n const item = {\n id: get(row, 'object_id'),\n // Construct Image URL\n url: `/ps/data/browse/api/preview/big/objects/${get(row, 'object_date')}/${get(row, 'sample_id')}/${get(row, 'acq_id')}/${get(row, 'img_file_name')}`\n };\n\n // Parse numeric values\n usefulKeys.forEach(k => {\n let v = parseFloat(get(row, k));\n item[k] = isNaN(v) ? 0 : v;\n });\n\n data.push(item);\n}\n\nmsg.payload = {\n data,\n keys: usefulKeys,\n meta\n};\n\nreturn msg;", + "outputs": 1, + "timeout": 0, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 440, + "y": 960, + "wires": [ + [ + "4244d2ed880cca66" ] ] }, + { + "id": "d77ed8081a657507", + "type": "delay", + "z": "14f8c9b5ce1235cc", + "name": "", + "pauseType": "delay", + "timeout": "1", + "timeoutUnits": "seconds", + "rate": "1", + "nbRateUnits": "1", + "rateUnits": "second", + "randomFirst": "1", + "randomLast": "5", + "randomUnits": "seconds", + "drop": false, + "allowrate": false, + "outputs": 1, + "x": 180, + "y": 320, + "wires": [ + [ + "5ec998719a884cfe" + ] + ] + }, + { + "id": "bdf84e9e1e0caa90", + "type": "debug", + "z": "14f8c9b5ce1235cc", + "name": "debug 2", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "true", + "targetType": "full", + "statusVal": "", + "statusType": "auto", + "x": 380, + "y": 360, + "wires": [] + }, { "id": "8e11bb014b9664be", "type": "ui-template", From 392b194ea1c4487990ae9122c3898afce492dec6 Mon Sep 17 00:00:00 2001 From: Thibaut Pollina Date: Sun, 30 Nov 2025 19:17:34 +0000 Subject: [PATCH 22/36] update --- node-red/projects/dashboard/flows.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/node-red/projects/dashboard/flows.json b/node-red/projects/dashboard/flows.json index 013285c41..95802682e 100644 --- a/node-red/projects/dashboard/flows.json +++ b/node-red/projects/dashboard/flows.json @@ -4744,7 +4744,7 @@ "width": 0, "height": 0, "head": "", - "format": "\n\n\n\n\n\n", + "format": "\n\n\n\n\n\n", "storeOutMessages": true, "passthru": true, "resendOnRefresh": true, @@ -5254,7 +5254,7 @@ "width": 0, "height": 0, "head": "", - "format": "\n\n\n\n", + "format": "\n\n\n\n", "storeOutMessages": true, "passthru": true, "resendOnRefresh": true, From c0757033c654095d8595e9da3b5253d865b1b3ce Mon Sep 17 00:00:00 2001 From: Thibaut Pollina Date: Sun, 30 Nov 2025 21:33:54 +0000 Subject: [PATCH 23/36] update --- node-red/projects/dashboard/flows.json | 230 +++++++++---------------- 1 file changed, 84 insertions(+), 146 deletions(-) diff --git a/node-red/projects/dashboard/flows.json b/node-red/projects/dashboard/flows.json index 95802682e..37843f6f8 100644 --- a/node-red/projects/dashboard/flows.json +++ b/node-red/projects/dashboard/flows.json @@ -1413,17 +1413,17 @@ { "name": "Default", "px": "0", - "cols": "3" + "cols": "4" }, { "name": "Tablet", "px": "576", - "cols": "6" + "cols": "4" }, { "name": "Small Desktop", "px": "768", - "cols": "9" + "cols": "12" }, { "name": "Desktop", @@ -2762,7 +2762,7 @@ "group": "", "page": "632260133d581caa", "ui": "", - "name": "CSS (All Pages)", + "name": "CSS (This page)", "order": 0, "width": 0, "height": 0, @@ -3724,7 +3724,7 @@ "width": 0, "height": 0, "head": "", - "format": "\n\n\n\n", + "format": "\n\n\n\n", "storeOutMessages": true, "passthru": true, "resendOnRefresh": true, @@ -4162,99 +4162,6 @@ ] ] }, - { - "id": "61c2c281b1c6e055", - "type": "ui-table", - "z": "0fd76ac156d78937", - "group": "bfd4acb7b243514f", - "name": "List of acq", - "label": "", - "order": 1, - "width": 0, - "height": 0, - "maxrows": "100", - "passthru": false, - "autocols": false, - "showSearch": false, - "deselect": true, - "selectionType": "click", - "columns": [ - { - "title": "Project name", - "key": "project_name", - "keyType": "key", - "type": "text", - "width": "", - "align": "start" - }, - { - "title": "Sample ID", - "key": "sample_id", - "keyType": "key", - "type": "text", - "width": "", - "align": "start" - }, - { - "title": "Acquisition ID", - "key": "acquisition_id", - "keyType": "key", - "type": "text", - "width": "", - "align": "start" - }, - { - "title": "Operator Name", - "key": "operator_name", - "keyType": "key", - "type": "text", - "width": "", - "align": "start" - }, - { - "title": "Number of images", - "key": "image_acquired_count", - "keyType": "key", - "type": "text", - "width": "", - "align": "start" - }, - { - "title": "Path", - "key": "path", - "keyType": "key", - "type": "text", - "width": "", - "align": "start" - }, - { - "title": "Segmented", - "key": "is_segmented", - "keyType": "key", - "type": "tickcross", - "width": "", - "align": "start" - }, - { - "title": "", - "key": "", - "keyType": "str", - "type": "html", - "width": "", - "align": "start" - } - ], - "mobileBreakpoint": "sm", - "mobileBreakpointType": "defaults", - "action": "replace", - "x": 330, - "y": 140, - "wires": [ - [ - "cdb586337f186494" - ] - ] - }, { "id": "d4129bf3f5622aa6", "type": "list acquisitions", @@ -4264,7 +4171,7 @@ "y": 140, "wires": [ [ - "61c2c281b1c6e055" + "e23cbd161ffb9970" ] ] }, @@ -4376,7 +4283,8 @@ "y": 260, "wires": [ [ - "33c0d1b8251b5d00" + "33c0d1b8251b5d00", + "6a279f9f754e8ead" ] ] }, @@ -4392,7 +4300,7 @@ "width": "0", "height": "0", "head": "", - "format": "\n\n\n\n", + "format": "\n\n\n\n\n", "storeOutMessages": true, "passthru": true, "resendOnRefresh": true, @@ -4485,28 +4393,10 @@ "y": 140, "wires": [ [ - "0ec6b9337acadf6f", "7fde0897e436c334" ] ] }, - { - "id": "0ec6b9337acadf6f", - "type": "debug", - "z": "0fd76ac156d78937", - "name": "debug 1", - "active": true, - "tosidebar": true, - "console": false, - "tostatus": false, - "complete": "true", - "targetType": "full", - "statusVal": "", - "statusType": "auto", - "x": 880, - "y": 180, - "wires": [] - }, { "id": "7fde0897e436c334", "type": "ui-template", @@ -4525,14 +4415,56 @@ "resendOnRefresh": true, "templateScope": "local", "className": "", - "x": 850, - "y": 380, + "x": 870, + "y": 140, "wires": [ [ "24c71b69e60e41bd" ] ] }, + { + "id": "e23cbd161ffb9970", + "type": "ui-template", + "z": "0fd76ac156d78937", + "group": "bfd4acb7b243514f", + "page": "", + "ui": "", + "name": "List of Acquisitions", + "order": 1, + "width": 0, + "height": 0, + "head": "", + "format": "\n\n\n\n", + "storeOutMessages": true, + "passthru": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 310, + "y": 140, + "wires": [ + [ + "cdb586337f186494" + ] + ] + }, + { + "id": "6a279f9f754e8ead", + "type": "debug", + "z": "0fd76ac156d78937", + "name": "debug 1", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "false", + "statusVal": "", + "statusType": "auto", + "x": 340, + "y": 360, + "wires": [] + }, { "id": "77bb783e65c3ffd6", "type": "ui-event", @@ -4664,7 +4596,7 @@ "type": "function", "z": "14f8c9b5ce1235cc", "name": "Insert export column", - "func": "msg.payload = msg.payload.map(item => {\n // extraction de l'acquisition_id\n const acq = item.acquisition_id; // ex: \"A_2\"\n\n // création du chemin export\n item.export = `/ps/data/browse/api/raw/export/ecotaxa/ecotaxa_${acq}.zip`;\n\n return item;\n});\n\nreturn msg;\n", + "func": "msg.payload = msg.payload.map(item => {\n // extraction de l'acquisition_id\n const acq = item.acquisition_id; // ex: \"A_2\"\n\n // création du chemin export\n item.export = `/ps/data/browse/api/raw/export/ecotaxa/ecotaxa_${acq}.zip`;\n\n return item;\n});\nreturn msg;\n", "outputs": 1, "timeout": 0, "noerr": 0, @@ -4706,8 +4638,7 @@ "512e86195d14e773", "9be1cc713ac79a7b", "63ce87d36ae8f172", - "9abf70a949e76eaf", - "bdf84e9e1e0caa90" + "9abf70a949e76eaf" ] ] }, @@ -4723,7 +4654,7 @@ "initialize": "", "finalize": "", "libs": [], - "x": 790, + "x": 930, "y": 180, "wires": [ [ @@ -4764,17 +4695,17 @@ "page": "", "ui": "", "name": "List of Segmentation", - "order": 1, + "order": 2, "width": 0, "height": 0, "head": "", - "format": "\n\n", + "format": "\n\n", "storeOutMessages": true, "passthru": true, "resendOnRefresh": true, "templateScope": "local", "className": "", - "x": 600, + "x": 740, "y": 180, "wires": [ [ @@ -5066,7 +4997,7 @@ "width": 0, "height": 0, "head": "", - "format": "\n\n\n", + "format": "\n\n\n", "storeOutMessages": true, "passthru": true, "resendOnRefresh": true, @@ -5335,7 +5266,7 @@ "allowrate": false, "outputs": 1, "x": 180, - "y": 320, + "y": 280, "wires": [ [ "5ec998719a884cfe" @@ -5343,21 +5274,28 @@ ] }, { - "id": "bdf84e9e1e0caa90", - "type": "debug", + "id": "9e5c5ec670e343cf", + "type": "ui-template", "z": "14f8c9b5ce1235cc", - "name": "debug 2", - "active": true, - "tosidebar": true, - "console": false, - "tostatus": false, - "complete": "true", - "targetType": "full", - "statusVal": "", - "statusType": "auto", - "x": 380, - "y": 360, - "wires": [] + "group": "", + "page": "d129fac8e7742d5b", + "ui": "", + "name": "CSS (This page)", + "order": 0, + "width": 0, + "height": 0, + "head": "", + "format": ".plot-container.plotly {\n height: 100%;\n}\n", + "storeOutMessages": true, + "passthru": true, + "resendOnRefresh": true, + "templateScope": "page:style", + "className": "", + "x": 1300, + "y": 100, + "wires": [ + [] + ] }, { "id": "8e11bb014b9664be", @@ -5368,8 +5306,8 @@ "ui": "", "name": "body", "order": 1, - "width": "12", - "height": "6", + "width": "0", + "height": "0", "head": "", "format": "\n", "storeOutMessages": true, From 451f99b4afe4b091d15ba53e8b9b5a01d495b2e1 Mon Sep 17 00:00:00 2001 From: Thibaut Pollina Date: Sun, 30 Nov 2025 21:41:59 +0000 Subject: [PATCH 24/36] update --- node-red/projects/dashboard/flows.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/node-red/projects/dashboard/flows.json b/node-red/projects/dashboard/flows.json index 37843f6f8..f6f3f0131 100644 --- a/node-red/projects/dashboard/flows.json +++ b/node-red/projects/dashboard/flows.json @@ -4300,7 +4300,7 @@ "width": "0", "height": "0", "head": "", - "format": "\n\n\n\n\n", + "format": "\n\n\n\n\n", "storeOutMessages": true, "passthru": true, "resendOnRefresh": true, From 74627f034d895cbdf598ed463b2072009e2fd2f2 Mon Sep 17 00:00:00 2001 From: Thibaut Pollina Date: Sun, 30 Nov 2025 22:26:43 +0000 Subject: [PATCH 25/36] update --- node-red/projects/dashboard/flows.json | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/node-red/projects/dashboard/flows.json b/node-red/projects/dashboard/flows.json index f6f3f0131..c519c2e69 100644 --- a/node-red/projects/dashboard/flows.json +++ b/node-red/projects/dashboard/flows.json @@ -2791,7 +2791,7 @@ "width": "0", "height": "0", "head": "", - "format": "\n\n", + "format": "\n\n", "storeOutMessages": true, "passthru": true, "resendOnRefresh": true, @@ -3035,7 +3035,7 @@ "width": "0", "height": "0", "head": "", - "format": "\n\n\n\n", + "format": "\n\n\n\n", "storeOutMessages": true, "passthru": true, "resendOnRefresh": true, @@ -3700,7 +3700,7 @@ "width": "0", "height": "0", "head": "", - "format": "\n\n", + "format": "\n\n", "storeOutMessages": true, "passthru": true, "resendOnRefresh": true, @@ -3724,7 +3724,7 @@ "width": 0, "height": 0, "head": "", - "format": "\n\n\n\n", + "format": "\n\n\n\n", "storeOutMessages": true, "passthru": true, "resendOnRefresh": true, @@ -3750,7 +3750,7 @@ "width": 0, "height": 0, "head": "", - "format": "", + "format": "", "storeOutMessages": true, "passthru": true, "resendOnRefresh": true, @@ -3929,7 +3929,7 @@ "width": 0, "height": 0, "head": "", - "format": "\n\n", + "format": "", "storeOutMessages": true, "passthru": true, "resendOnRefresh": true, @@ -4695,7 +4695,7 @@ "page": "", "ui": "", "name": "List of Segmentation", - "order": 2, + "order": 1, "width": 0, "height": 0, "head": "", From 89ec927b82083c69b4ca2157f34c74901d7d38bf Mon Sep 17 00:00:00 2001 From: Thibaut Pollina Date: Sun, 30 Nov 2025 22:33:17 +0000 Subject: [PATCH 26/36] update --- node-red/projects/dashboard/flows.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/node-red/projects/dashboard/flows.json b/node-red/projects/dashboard/flows.json index c519c2e69..299e449b0 100644 --- a/node-red/projects/dashboard/flows.json +++ b/node-red/projects/dashboard/flows.json @@ -3724,7 +3724,7 @@ "width": 0, "height": 0, "head": "", - "format": "\n\n\n\n", + "format": "\n\n\n\n", "storeOutMessages": true, "passthru": true, "resendOnRefresh": true, @@ -4997,7 +4997,7 @@ "width": 0, "height": 0, "head": "", - "format": "\n\n\n", + "format": "\n\n\n", "storeOutMessages": true, "passthru": true, "resendOnRefresh": true, From 7be00355330cd24f2aa6c1b4cbdfe564d11bf8cc Mon Sep 17 00:00:00 2001 From: Thibaut Pollina Date: Mon, 1 Dec 2025 08:52:30 +0000 Subject: [PATCH 27/36] update --- node-red/projects/dashboard/flows.json | 82 +++++++++++++------------- 1 file changed, 40 insertions(+), 42 deletions(-) diff --git a/node-red/projects/dashboard/flows.json b/node-red/projects/dashboard/flows.json index 299e449b0..bec1b29d4 100644 --- a/node-red/projects/dashboard/flows.json +++ b/node-red/projects/dashboard/flows.json @@ -3975,8 +3975,7 @@ "y": 200, "wires": [ [ - "46b98c54eb680c2e", - "b162f94dfd6bfdad" + "46b98c54eb680c2e" ] ] }, @@ -4075,28 +4074,10 @@ "y": 280, "wires": [ [ - "46b98c54eb680c2e", - "b162f94dfd6bfdad" + "46b98c54eb680c2e" ] ] }, - { - "id": "b162f94dfd6bfdad", - "type": "debug", - "z": "35d7387466dd0bc0", - "name": "debug 3", - "active": true, - "tosidebar": true, - "console": false, - "tostatus": false, - "complete": "true", - "targetType": "full", - "statusVal": "", - "statusType": "auto", - "x": 1080, - "y": 140, - "wires": [] - }, { "id": "250979b4672d81b6", "type": "ui-event", @@ -4283,8 +4264,7 @@ "y": 260, "wires": [ [ - "33c0d1b8251b5d00", - "6a279f9f754e8ead" + "33c0d1b8251b5d00" ] ] }, @@ -4409,7 +4389,7 @@ "width": 0, "height": 0, "head": "", - "format": "\n", + "format": "\n", "storeOutMessages": true, "passthru": true, "resendOnRefresh": true, @@ -4449,22 +4429,6 @@ ] ] }, - { - "id": "6a279f9f754e8ead", - "type": "debug", - "z": "0fd76ac156d78937", - "name": "debug 1", - "active": true, - "tosidebar": true, - "console": false, - "tostatus": false, - "complete": "false", - "statusVal": "", - "statusType": "auto", - "x": 340, - "y": 360, - "wires": [] - }, { "id": "77bb783e65c3ffd6", "type": "ui-event", @@ -4526,7 +4490,8 @@ "wires": [ [ "384aa9ab83bfe566", - "164baa371893de7a" + "164baa371893de7a", + "b8e4f06f40c63fd2" ] ] }, @@ -4587,7 +4552,8 @@ "y": 180, "wires": [ [ - "18a10804663dbaa1" + "18a10804663dbaa1", + "ca563bd4bf522cf9" ] ] }, @@ -5297,6 +5263,38 @@ [] ] }, + { + "id": "b8e4f06f40c63fd2", + "type": "debug", + "z": "14f8c9b5ce1235cc", + "name": "debug 2", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "false", + "statusVal": "", + "statusType": "auto", + "x": 920, + "y": 60, + "wires": [] + }, + { + "id": "ca563bd4bf522cf9", + "type": "debug", + "z": "14f8c9b5ce1235cc", + "name": "debug 4", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "false", + "statusVal": "", + "statusType": "auto", + "x": 360, + "y": 140, + "wires": [] + }, { "id": "8e11bb014b9664be", "type": "ui-template", From a7a0a5940d5d44b19e483e816a2c254539981180 Mon Sep 17 00:00:00 2001 From: Thibaut Pollina Date: Thu, 4 Dec 2025 10:00:30 +0000 Subject: [PATCH 28/36] update --- node-red/projects/dashboard/flows.json | 509 +++++++------------------ 1 file changed, 139 insertions(+), 370 deletions(-) diff --git a/node-red/projects/dashboard/flows.json b/node-red/projects/dashboard/flows.json index bec1b29d4..b437ada7a 100644 --- a/node-red/projects/dashboard/flows.json +++ b/node-red/projects/dashboard/flows.json @@ -867,7 +867,7 @@ "page": "7a4e042a60b734a6", "width": "12", "height": 1, - "order": 3, + "order": 4, "showTitle": true, "className": "", "visible": "true", @@ -1379,7 +1379,7 @@ "page": "7a4e042a60b734a6", "width": "12", "height": 1, - "order": 1, + "order": 2, "showTitle": false, "className": "", "visible": "true", @@ -1393,7 +1393,7 @@ "page": "7a4e042a60b734a6", "width": "12", "height": 1, - "order": 4, + "order": 5, "showTitle": false, "className": "", "visible": "true", @@ -1471,7 +1471,7 @@ "page": "7a4e042a60b734a6", "width": "12", "height": 1, - "order": 2, + "order": 3, "showTitle": true, "className": "", "visible": "true", @@ -2097,7 +2097,7 @@ "checkall": "true", "repair": false, "outputs": 1, - "x": 570, + "x": 310, "y": 40, "wires": [ [ @@ -2105,31 +2105,6 @@ ] ] }, - { - "id": "f49d711b2738e87e", - "type": "switch", - "z": "07f3b717f2a2c8c7", - "name": "msg.topic === \"$pageview\"", - "property": "topic", - "propertyType": "msg", - "rules": [ - { - "t": "eq", - "v": "$pageview", - "vt": "str" - } - ], - "checkall": "true", - "repair": false, - "outputs": 1, - "x": 280, - "y": 40, - "wires": [ - [ - "7e7d02f3ea356eff" - ] - ] - }, { "id": "b1f5b8b5f26121e6", "type": "ui-event", @@ -2140,7 +2115,7 @@ "y": 40, "wires": [ [ - "f49d711b2738e87e" + "7e7d02f3ea356eff" ] ] }, @@ -2253,7 +2228,7 @@ "checkall": "true", "repair": false, "outputs": 1, - "x": 570, + "x": 310, "y": 40, "wires": [ [ @@ -2263,31 +2238,6 @@ ] ] }, - { - "id": "e74e4d04e4a79458", - "type": "switch", - "z": "1b667c6443413ced", - "name": "msg.topic === \"$pageview\"", - "property": "topic", - "propertyType": "msg", - "rules": [ - { - "t": "eq", - "v": "$pageview", - "vt": "str" - } - ], - "checkall": "true", - "repair": false, - "outputs": 1, - "x": 280, - "y": 40, - "wires": [ - [ - "67b871e8f9c64c2b" - ] - ] - }, { "id": "7c16cbd0b2c40ea4", "type": "ui-event", @@ -2298,7 +2248,7 @@ "y": 40, "wires": [ [ - "e74e4d04e4a79458" + "67b871e8f9c64c2b" ] ] }, @@ -2314,7 +2264,7 @@ "width": 0, "height": 0, "head": "", - "format": ".v-btn--variant-outlined.v-btn--active {\n background-color: #1976d2 !important;\n color: white !important;\n}\n\n\n.v-field__field {\n background: #eef3ff;\n border-radius: 4px;\n}\n\n.v-btn-group .v-btn, .v-field__overlay {\n background: #eef3ff;\n}\n \n", + "format": ".v-btn--variant-outlined.v-btn--active {\n background-color: #1976d2 !important;\n color: white !important;\n}\n\n\n.v-field__field {\n background: #eef3ff;\n border-radius: 4px;\n}\n\n.v-btn-group .v-btn, .v-field__overlay {\n background: #eef3ff;\n}\n \n.v-row {\n margin: 0;\n}", "storeOutMessages": true, "passthru": true, "resendOnRefresh": true, @@ -2405,8 +2355,8 @@ "drop": false, "allowrate": false, "outputs": 1, - "x": 180, - "y": 140, + "x": 140, + "y": 180, "wires": [ [ "74f8dad75cdd3c16" @@ -2425,8 +2375,8 @@ "initialize": "", "finalize": "", "libs": [], - "x": 200, - "y": 280, + "x": 320, + "y": 180, "wires": [ [ "04386808e694e97e", @@ -2485,8 +2435,7 @@ "y": 200, "wires": [ [ - "39c7e4b018ce16cb", - "1044b154a90406dc" + "39c7e4b018ce16cb" ], [ "8efc52e6ee9206f6" @@ -2500,7 +2449,7 @@ "group": "5d39a98563150f22", "page": "", "ui": "", - "name": "body", + "name": "empty-state", "order": 1, "width": "0", "height": "0", @@ -2511,32 +2460,12 @@ "resendOnRefresh": true, "templateScope": "local", "className": "", - "x": 510, + "x": 530, "y": 280, "wires": [ [] ] }, - { - "id": "1044b154a90406dc", - "type": "exec", - "z": "1b667c6443413ced", - "command": "sudo reboot now", - "addpay": "", - "append": "", - "useSpawn": "false", - "timer": "", - "winHide": false, - "oldrc": false, - "name": "", - "x": 1190, - "y": 300, - "wires": [ - [], - [], - [] - ] - }, { "id": "04386808e694e97e", "type": "ui-template", @@ -2779,6 +2708,62 @@ [] ] }, + { + "id": "e9eca3996df4f4a2", + "type": "ui-event", + "z": "1b667c6443413ced", + "ui": "e6ae26617c24c3ea", + "name": "UI Event", + "x": 920, + "y": 600, + "wires": [ + [ + "feda608d3423af51" + ] + ] + }, + { + "id": "feda608d3423af51", + "type": "switch", + "z": "1b667c6443413ced", + "name": "msg.payload.page.path === \"/home\"", + "property": "payload.page.path", + "propertyType": "msg", + "rules": [ + { + "t": "eq", + "v": "/home", + "vt": "str" + } + ], + "checkall": "true", + "repair": false, + "outputs": 1, + "x": 1170, + "y": 600, + "wires": [ + [ + "790b19154efc45c3" + ] + ] + }, + { + "id": "790b19154efc45c3", + "type": "debug", + "z": "1b667c6443413ced", + "name": "debug 2", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "true", + "targetType": "full", + "statusVal": "", + "statusType": "auto", + "x": 1100, + "y": 700, + "wires": [] + }, { "id": "a57a165cd0ce511b", "type": "ui-template", @@ -2847,31 +2832,6 @@ [] ] }, - { - "id": "8d5eadf60a2fd54b", - "type": "switch", - "z": "ab58b3fd0e6bcd77", - "name": "msg.topic === \"$pageview\"", - "property": "topic", - "propertyType": "msg", - "rules": [ - { - "t": "eq", - "v": "$pageview", - "vt": "str" - } - ], - "checkall": "true", - "repair": false, - "outputs": 1, - "x": 280, - "y": 40, - "wires": [ - [ - "6074fc31218f9a74" - ] - ] - }, { "id": "da4fbaab826b0d62", "type": "ui-event", @@ -2882,7 +2842,7 @@ "y": 40, "wires": [ [ - "8d5eadf60a2fd54b" + "6074fc31218f9a74" ] ] }, @@ -2903,7 +2863,7 @@ "checkall": "true", "repair": false, "outputs": 1, - "x": 580, + "x": 320, "y": 40, "wires": [ [ @@ -3187,7 +3147,7 @@ "width": 0, "height": 0, "head": "", - "format": "\n\n\n\n", + "format": "\n\n\n\n", "storeOutMessages": true, "passthru": true, "resendOnRefresh": true, @@ -3361,31 +3321,6 @@ "name": "UI Event", "x": 80, "y": 40, - "wires": [ - [ - "a68e52177173f690" - ] - ] - }, - { - "id": "a68e52177173f690", - "type": "switch", - "z": "190b0c9aa75e8843", - "name": "msg.topic === \"$pageview\"", - "property": "topic", - "propertyType": "msg", - "rules": [ - { - "t": "eq", - "v": "$pageview", - "vt": "str" - } - ], - "checkall": "true", - "repair": false, - "outputs": 1, - "x": 280, - "y": 40, "wires": [ [ "b1a74fe5569d418a" @@ -3409,7 +3344,7 @@ "checkall": "true", "repair": false, "outputs": 1, - "x": 580, + "x": 320, "y": 40, "wires": [ [ @@ -3724,7 +3659,7 @@ "width": 0, "height": 0, "head": "", - "format": "\n\n\n\n", + "format": "\n\n\n\n", "storeOutMessages": true, "passthru": true, "resendOnRefresh": true, @@ -3821,31 +3756,6 @@ "name": "UI Event", "x": 80, "y": 40, - "wires": [ - [ - "5bea829ec02ded61" - ] - ] - }, - { - "id": "5bea829ec02ded61", - "type": "switch", - "z": "35d7387466dd0bc0", - "name": "msg.topic === \"$pageview\"", - "property": "topic", - "propertyType": "msg", - "rules": [ - { - "t": "eq", - "v": "$pageview", - "vt": "str" - } - ], - "checkall": "true", - "repair": false, - "outputs": 1, - "x": 280, - "y": 40, "wires": [ [ "54ae95ca6088f307" @@ -3869,7 +3779,7 @@ "checkall": "true", "repair": false, "outputs": 1, - "x": 590, + "x": 330, "y": 40, "wires": [ [ @@ -4086,31 +3996,6 @@ "name": "UI Event", "x": 80, "y": 40, - "wires": [ - [ - "72421c36bb0418bc" - ] - ] - }, - { - "id": "72421c36bb0418bc", - "type": "switch", - "z": "0fd76ac156d78937", - "name": "msg.topic === \"$pageview\"", - "property": "topic", - "propertyType": "msg", - "rules": [ - { - "t": "eq", - "v": "$pageview", - "vt": "str" - } - ], - "checkall": "true", - "repair": false, - "outputs": 1, - "x": 280, - "y": 40, "wires": [ [ "66d295cc27d69cec" @@ -4134,7 +4019,7 @@ "checkall": "true", "repair": false, "outputs": 1, - "x": 590, + "x": 330, "y": 40, "wires": [ [ @@ -4437,31 +4322,6 @@ "name": "UI Event", "x": 80, "y": 40, - "wires": [ - [ - "55814595d9207e95" - ] - ] - }, - { - "id": "55814595d9207e95", - "type": "switch", - "z": "14f8c9b5ce1235cc", - "name": "msg.topic === \"$pageview\"", - "property": "topic", - "propertyType": "msg", - "rules": [ - { - "t": "eq", - "v": "$pageview", - "vt": "str" - } - ], - "checkall": "true", - "repair": false, - "outputs": 1, - "x": 280, - "y": 40, "wires": [ [ "90c920ecdab16908" @@ -4485,13 +4345,12 @@ "checkall": "true", "repair": false, "outputs": 1, - "x": 590, + "x": 330, "y": 40, "wires": [ [ "384aa9ab83bfe566", - "164baa371893de7a", - "b8e4f06f40c63fd2" + "164baa371893de7a" ] ] }, @@ -4553,7 +4412,7 @@ "wires": [ [ "18a10804663dbaa1", - "ca563bd4bf522cf9" + "bdeb6f634e7ecffd" ] ] }, @@ -4624,8 +4483,8 @@ "y": 180, "wires": [ [ - "d77ed8081a657507", - "384aa9ab83bfe566" + "384aa9ab83bfe566", + "9a10d9afb1a0b1ca" ] ] }, @@ -4648,7 +4507,7 @@ "templateScope": "local", "className": "", "x": 820, - "y": 960, + "y": 1000, "wires": [ [] ] @@ -4665,7 +4524,7 @@ "width": 0, "height": 0, "head": "", - "format": "\n\n", + "format": "\n\n", "storeOutMessages": true, "passthru": true, "resendOnRefresh": true, @@ -4716,7 +4575,7 @@ "finalize": "", "libs": [], "x": 510, - "y": 500, + "y": 520, "wires": [ [ "0c90eb450c8d9b01" @@ -4736,7 +4595,7 @@ "finalize": "", "libs": [], "x": 550, - "y": 540, + "y": 560, "wires": [ [ "0a559ab05af2548e" @@ -4756,7 +4615,7 @@ "finalize": "", "libs": [], "x": 520, - "y": 580, + "y": 600, "wires": [ [ "7b9d103ec5981051" @@ -4776,7 +4635,7 @@ "finalize": "", "libs": [], "x": 530, - "y": 620, + "y": 640, "wires": [ [ "23a78a078fa1eca8" @@ -4796,7 +4655,7 @@ "finalize": "", "libs": [], "x": 500, - "y": 660, + "y": 680, "wires": [ [ "09f4914fc0a54566" @@ -4816,7 +4675,7 @@ "finalize": "", "libs": [], "x": 540, - "y": 700, + "y": 720, "wires": [ [ "7f4d047a4c73eb61" @@ -4836,7 +4695,7 @@ "finalize": "", "libs": [], "x": 520, - "y": 740, + "y": 760, "wires": [ [ "a7c8654291022da1" @@ -4856,7 +4715,7 @@ "finalize": "", "libs": [], "x": 500, - "y": 780, + "y": 800, "wires": [ [ "ed9d6e31cc3a01b1" @@ -4876,7 +4735,7 @@ "finalize": "", "libs": [], "x": 480, - "y": 820, + "y": 840, "wires": [ [ "c15ef66d7e115bd3" @@ -4902,7 +4761,7 @@ "templateScope": "local", "className": "", "x": 820, - "y": 500, + "y": 520, "wires": [ [] ] @@ -4946,7 +4805,7 @@ "templateScope": "local", "className": "", "x": 840, - "y": 540, + "y": 560, "wires": [ [] ] @@ -4970,7 +4829,7 @@ "templateScope": "local", "className": "", "x": 820, - "y": 580, + "y": 600, "wires": [ [] ] @@ -4994,7 +4853,7 @@ "templateScope": "local", "className": "", "x": 890, - "y": 620, + "y": 640, "wires": [ [] ] @@ -5018,7 +4877,7 @@ "templateScope": "local", "className": "", "x": 870, - "y": 660, + "y": 680, "wires": [ [] ] @@ -5042,7 +4901,7 @@ "templateScope": "local", "className": "", "x": 830, - "y": 700, + "y": 720, "wires": [ [] ] @@ -5066,7 +4925,7 @@ "templateScope": "local", "className": "", "x": 890, - "y": 740, + "y": 760, "wires": [ [] ] @@ -5090,7 +4949,7 @@ "templateScope": "local", "className": "", "x": 860, - "y": 780, + "y": 800, "wires": [ [] ] @@ -5114,7 +4973,7 @@ "templateScope": "local", "className": "", "x": 850, - "y": 820, + "y": 840, "wires": [ [] ] @@ -5132,7 +4991,7 @@ "finalize": "", "libs": [], "x": 450, - "y": 900, + "y": 920, "wires": [ [ "d57a5bca66c04989" @@ -5158,7 +5017,7 @@ "templateScope": "local", "className": "", "x": 820, - "y": 900, + "y": 920, "wires": [ [] ] @@ -5207,7 +5066,7 @@ "finalize": "", "libs": [], "x": 440, - "y": 960, + "y": 1000, "wires": [ [ "4244d2ed880cca66" @@ -5232,7 +5091,7 @@ "allowrate": false, "outputs": 1, "x": 180, - "y": 280, + "y": 340, "wires": [ [ "5ec998719a884cfe" @@ -5264,31 +5123,41 @@ ] }, { - "id": "b8e4f06f40c63fd2", - "type": "debug", + "id": "9a10d9afb1a0b1ca", + "type": "switch", "z": "14f8c9b5ce1235cc", - "name": "debug 2", - "active": true, - "tosidebar": true, - "console": false, - "tostatus": false, - "complete": "false", - "statusVal": "", - "statusType": "auto", - "x": 920, - "y": 60, - "wires": [] + "name": "", + "property": "topic", + "propertyType": "msg", + "rules": [ + { + "t": "neq", + "v": "$pageview", + "vt": "str" + } + ], + "checkall": "true", + "repair": false, + "outputs": 1, + "x": 170, + "y": 300, + "wires": [ + [ + "d77ed8081a657507" + ] + ] }, { - "id": "ca563bd4bf522cf9", + "id": "bdeb6f634e7ecffd", "type": "debug", "z": "14f8c9b5ce1235cc", - "name": "debug 4", + "name": "debug 1", "active": true, "tosidebar": true, "console": false, "tostatus": false, - "complete": "false", + "complete": "true", + "targetType": "full", "statusVal": "", "statusType": "auto", "x": 360, @@ -5392,31 +5261,6 @@ "name": "UI Event", "x": 80, "y": 40, - "wires": [ - [ - "a0df077ddbb184e3" - ] - ] - }, - { - "id": "a0df077ddbb184e3", - "type": "switch", - "z": "6426e7bea6900426", - "name": "msg.topic === \"$pageview\"", - "property": "topic", - "propertyType": "msg", - "rules": [ - { - "t": "eq", - "v": "$pageview", - "vt": "str" - } - ], - "checkall": "true", - "repair": false, - "outputs": 1, - "x": 280, - "y": 40, "wires": [ [ "874fd69a5398300e" @@ -5440,7 +5284,7 @@ "checkall": "true", "repair": false, "outputs": 1, - "x": 640, + "x": 380, "y": 40, "wires": [ [ @@ -5600,31 +5444,6 @@ "name": "UI Event", "x": 80, "y": 40, - "wires": [ - [ - "068b250f8668e3ea" - ] - ] - }, - { - "id": "068b250f8668e3ea", - "type": "switch", - "z": "14c685bd04db8be5", - "name": "msg.topic === \"$pageview\"", - "property": "topic", - "propertyType": "msg", - "rules": [ - { - "t": "eq", - "v": "$pageview", - "vt": "str" - } - ], - "checkall": "true", - "repair": false, - "outputs": 1, - "x": 280, - "y": 40, "wires": [ [ "72dd2876e74c3ef2" @@ -5648,7 +5467,7 @@ "checkall": "true", "repair": false, "outputs": 1, - "x": 620, + "x": 360, "y": 40, "wires": [ [ @@ -5688,7 +5507,7 @@ "width": "0", "height": "0", "head": "", - "format": "\n\n\n\n", + "format": "\n\n\n\n\n", "storeOutMessages": true, "passthru": true, "resendOnRefresh": true, @@ -5834,31 +5653,6 @@ "name": "UI Event", "x": 80, "y": 40, - "wires": [ - [ - "78cf0ca00b9e1214" - ] - ] - }, - { - "id": "78cf0ca00b9e1214", - "type": "switch", - "z": "3afb1d2b21be9114", - "name": "msg.topic === \"$pageview\"", - "property": "topic", - "propertyType": "msg", - "rules": [ - { - "t": "eq", - "v": "$pageview", - "vt": "str" - } - ], - "checkall": "true", - "repair": false, - "outputs": 1, - "x": 280, - "y": 40, "wires": [ [ "943c62c18d43f2a6" @@ -5882,7 +5676,7 @@ "checkall": "true", "repair": false, "outputs": 1, - "x": 620, + "x": 360, "y": 40, "wires": [ [ @@ -6002,31 +5796,6 @@ "name": "UI Event", "x": 80, "y": 40, - "wires": [ - [ - "2e99999b7cf152b7" - ] - ] - }, - { - "id": "2e99999b7cf152b7", - "type": "switch", - "z": "d5b2c64b84f8ed4f", - "name": "msg.topic === \"$pageview\"", - "property": "topic", - "propertyType": "msg", - "rules": [ - { - "t": "eq", - "v": "$pageview", - "vt": "str" - } - ], - "checkall": "true", - "repair": false, - "outputs": 1, - "x": 280, - "y": 40, "wires": [ [ "138136e6a1917e49" @@ -6050,7 +5819,7 @@ "checkall": "true", "repair": false, "outputs": 1, - "x": 610, + "x": 350, "y": 40, "wires": [ [] From 2d9c33c26b2a48cd6f61dbfb7e2af7adf2093f39 Mon Sep 17 00:00:00 2001 From: Sonny Piers Date: Tue, 2 Dec 2025 10:03:40 +0100 Subject: [PATCH 29/36] controller: Increase range for LED value on 2.6 --- controller/light/LM36011.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/controller/light/LM36011.py b/controller/light/LM36011.py index 3216e0eba..78da079dd 100644 --- a/controller/light/LM36011.py +++ b/controller/light/LM36011.py @@ -90,6 +90,15 @@ def set_torch_current(self, current): value = int(current * 0.34) self._write_byte(self.Register.torch, value) + # 0 - 127 + # 0 is ~ 2.4 mA + # 128 is ~ 376 mA + def set_torch_value(self, value: int) -> None: + self._write_byte(self.Register.torch, int(value)) + + def get_torch_value(self) -> int: + return self._read_byte(self.Register.torch) + def get_torch_current(self): return self._read_byte(self.Register.torch) @@ -152,9 +161,9 @@ def deinit() -> None: def get_value() -> float: - return int(round(led.get_torch_current() / 20)) + return led.get_torch_current() / 127 def set_value(value: float) -> None: - led.set_torch_current(int(round(value * 20))) + led.set_torch_value(int(value * 127)) return From 699c45934fe88fc2fbf3dce6ea28245c7c7d8c83 Mon Sep 17 00:00:00 2001 From: Thibaut Pollina Date: Mon, 8 Dec 2025 15:39:57 +0000 Subject: [PATCH 30/36] update --- node-red/projects/dashboard/flows.json | 267 ++++++++++++++++--------- 1 file changed, 170 insertions(+), 97 deletions(-) diff --git a/node-red/projects/dashboard/flows.json b/node-red/projects/dashboard/flows.json index b437ada7a..8010ae6dc 100644 --- a/node-red/projects/dashboard/flows.json +++ b/node-red/projects/dashboard/flows.json @@ -867,7 +867,7 @@ "page": "7a4e042a60b734a6", "width": "12", "height": 1, - "order": 4, + "order": 3, "showTitle": true, "className": "", "visible": "true", @@ -1379,7 +1379,7 @@ "page": "7a4e042a60b734a6", "width": "12", "height": 1, - "order": 2, + "order": 1, "showTitle": false, "className": "", "visible": "true", @@ -1393,7 +1393,7 @@ "page": "7a4e042a60b734a6", "width": "12", "height": 1, - "order": 5, + "order": 4, "showTitle": false, "className": "", "visible": "true", @@ -1471,7 +1471,7 @@ "page": "7a4e042a60b734a6", "width": "12", "height": 1, - "order": 3, + "order": 2, "showTitle": true, "className": "", "visible": "true", @@ -2160,7 +2160,9 @@ "x": 370, "y": 140, "wires": [ - [] + [ + "0470c8bd5e2507dc" + ] ] }, { @@ -2187,6 +2189,24 @@ [] ] }, + { + "id": "0470c8bd5e2507dc", + "type": "function", + "z": "07f3b717f2a2c8c7", + "name": "set general settings", + "func": "if (msg.topic) {\n global.set(\"countries\", msg.payload.countries);\n global.set(\"timezone\", msg.payload.timezone);\n}\nreturn msg;", + "outputs": 1, + "timeout": "", + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 590, + "y": 140, + "wires": [ + [] + ] + }, { "id": "1cf1b20e425ec126", "type": "ui-template", @@ -2504,7 +2524,7 @@ "width": "0", "height": "0", "head": "", - "format": "\n", + "format": "", "storeOutMessages": true, "passthru": true, "resendOnRefresh": true, @@ -2528,7 +2548,7 @@ "width": "0", "height": "0", "head": "", - "format": "\n", + "format": "", "storeOutMessages": true, "passthru": true, "resendOnRefresh": true, @@ -2552,7 +2572,7 @@ "width": "0", "height": "0", "head": "", - "format": "", + "format": "", "storeOutMessages": true, "passthru": true, "resendOnRefresh": true, @@ -2708,62 +2728,6 @@ [] ] }, - { - "id": "e9eca3996df4f4a2", - "type": "ui-event", - "z": "1b667c6443413ced", - "ui": "e6ae26617c24c3ea", - "name": "UI Event", - "x": 920, - "y": 600, - "wires": [ - [ - "feda608d3423af51" - ] - ] - }, - { - "id": "feda608d3423af51", - "type": "switch", - "z": "1b667c6443413ced", - "name": "msg.payload.page.path === \"/home\"", - "property": "payload.page.path", - "propertyType": "msg", - "rules": [ - { - "t": "eq", - "v": "/home", - "vt": "str" - } - ], - "checkall": "true", - "repair": false, - "outputs": 1, - "x": 1170, - "y": 600, - "wires": [ - [ - "790b19154efc45c3" - ] - ] - }, - { - "id": "790b19154efc45c3", - "type": "debug", - "z": "1b667c6443413ced", - "name": "debug 2", - "active": true, - "tosidebar": true, - "console": false, - "tostatus": false, - "complete": "true", - "targetType": "full", - "statusVal": "", - "statusType": "auto", - "x": 1100, - "y": 700, - "wires": [] - }, { "id": "a57a165cd0ce511b", "type": "ui-template", @@ -3147,7 +3111,7 @@ "width": 0, "height": 0, "head": "", - "format": "\n\n\n\n", + "format": "\n\n\n\n", "storeOutMessages": true, "passthru": true, "resendOnRefresh": true, @@ -3157,7 +3121,8 @@ "y": 140, "wires": [ [ - "13f96cf49ae6d6a6" + "13f96cf49ae6d6a6", + "1185f4705f9643a3" ] ] }, @@ -3199,7 +3164,7 @@ "width": 0, "height": 0, "head": "", - "format": "\n\n\n\n\n", + "format": "\n\n\n\n", "storeOutMessages": true, "passthru": true, "resendOnRefresh": true, @@ -3309,7 +3274,8 @@ "96550b272e3da513", "1c90e219a749e53e", "09b551fa202d8029", - "93a68ffbce74d70d" + "93a68ffbce74d70d", + "c04c3055a84e7597" ] ] }, @@ -3397,7 +3363,7 @@ "initialize": "", "finalize": "", "libs": [], - "x": 760, + "x": 960, "y": 240, "wires": [ [] @@ -3415,7 +3381,7 @@ "initialize": "", "finalize": "", "libs": [], - "x": 770, + "x": 970, "y": 200, "wires": [ [] @@ -3466,7 +3432,7 @@ "initialize": "", "finalize": "", "libs": [], - "x": 780, + "x": 980, "y": 340, "wires": [ [] @@ -3477,14 +3443,14 @@ "type": "function", "z": "190b0c9aa75e8843", "name": "set object_datetime_end", - "func": "if (msg.topic) {\n global.set(\"object_date_end\", msg.payload.object_date_end);\n global.set(\"object_time_end\", msg.payload.object_time_end);\n}\nreturn msg;\n", + "func": "if (msg.topic) {\n global.set(\"object_date_end\", msg.payload.object_date_end);\n global.set(\"object_time_end\", msg.payload.object_time_end);\n}\n\nif (msg.payload.sample_gear !== \"Horizontal Net\") {\n global.set(\"object_date_end\", undefined);\n global.set(\"object_time_end\", undefined);\n}\n\nreturn msg;\n", "outputs": 1, "timeout": "", "noerr": 0, "initialize": "", "finalize": "", "libs": [], - "x": 790, + "x": 990, "y": 300, "wires": [ [] @@ -3527,7 +3493,7 @@ "initialize": "", "finalize": "", "libs": [], - "x": 790, + "x": 990, "y": 400, "wires": [ [] @@ -3570,7 +3536,7 @@ "initialize": "", "finalize": "", "libs": [], - "x": 800, + "x": 1000, "y": 460, "wires": [ [] @@ -3617,12 +3583,102 @@ "initialize": "", "finalize": "", "libs": [], - "x": 790, + "x": 990, "y": 140, "wires": [ [] ] }, + { + "id": "f495c897b992cb33", + "type": "debug", + "z": "190b0c9aa75e8843", + "name": "debug 3", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "payload", + "targetType": "msg", + "statusVal": "", + "statusType": "auto", + "x": 900, + "y": 760, + "wires": [] + }, + { + "id": "c04c3055a84e7597", + "type": "debug", + "z": "190b0c9aa75e8843", + "name": "debug 4", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "payload", + "targetType": "msg", + "statusVal": "", + "statusType": "auto", + "x": 140, + "y": 280, + "wires": [] + }, + { + "id": "1185f4705f9643a3", + "type": "switch", + "z": "190b0c9aa75e8843", + "name": "", + "property": "payload.sample_gear", + "propertyType": "msg", + "rules": [ + { + "t": "eq", + "v": "Horizontal Net", + "vt": "str" + }, + { + "t": "eq", + "v": "Vertical Net", + "vt": "str" + }, + { + "t": "eq", + "v": "Niskin bottle", + "vt": "str" + }, + { + "t": "eq", + "v": "Lab culture", + "vt": "str" + }, + { + "t": "eq", + "v": "Demo / Test", + "vt": "str" + } + ], + "checkall": "true", + "repair": false, + "outputs": 5, + "x": 510, + "y": 640, + "wires": [ + [], + [ + "42d27ae01c6fd201", + "f495c897b992cb33" + ], + [ + "42d27ae01c6fd201" + ], + [ + "42d27ae01c6fd201" + ], + [ + "42d27ae01c6fd201" + ] + ] + }, { "id": "ffe728d961068c62", "type": "ui-template", @@ -3659,7 +3715,7 @@ "width": 0, "height": 0, "head": "", - "format": "\n\n\n\n", + "format": "\n\n\n\n", "storeOutMessages": true, "passthru": true, "resendOnRefresh": true, @@ -4274,7 +4330,7 @@ "width": 0, "height": 0, "head": "", - "format": "\n", + "format": "\n", "storeOutMessages": true, "passthru": true, "resendOnRefresh": true, @@ -4350,7 +4406,8 @@ "wires": [ [ "384aa9ab83bfe566", - "164baa371893de7a" + "164baa371893de7a", + "543715b9199cdb72" ] ] }, @@ -4412,7 +4469,7 @@ "wires": [ [ "18a10804663dbaa1", - "bdeb6f634e7ecffd" + "72c774fc51af957b" ] ] }, @@ -4428,7 +4485,7 @@ "initialize": "", "finalize": "", "libs": [], - "x": 380, + "x": 440, "y": 180, "wires": [ [ @@ -4506,7 +4563,7 @@ "resendOnRefresh": true, "templateScope": "local", "className": "", - "x": 820, + "x": 920, "y": 1000, "wires": [ [] @@ -4556,7 +4613,7 @@ "resendOnRefresh": true, "templateScope": "local", "className": "", - "x": 810, + "x": 910, "y": 440, "wires": [ [] @@ -4760,7 +4817,7 @@ "resendOnRefresh": true, "templateScope": "local", "className": "", - "x": 820, + "x": 920, "y": 520, "wires": [ [] @@ -4804,7 +4861,7 @@ "resendOnRefresh": true, "templateScope": "local", "className": "", - "x": 840, + "x": 940, "y": 560, "wires": [ [] @@ -4828,7 +4885,7 @@ "resendOnRefresh": true, "templateScope": "local", "className": "", - "x": 820, + "x": 920, "y": 600, "wires": [ [] @@ -4852,7 +4909,7 @@ "resendOnRefresh": true, "templateScope": "local", "className": "", - "x": 890, + "x": 990, "y": 640, "wires": [ [] @@ -4876,7 +4933,7 @@ "resendOnRefresh": true, "templateScope": "local", "className": "", - "x": 870, + "x": 970, "y": 680, "wires": [ [] @@ -4900,7 +4957,7 @@ "resendOnRefresh": true, "templateScope": "local", "className": "", - "x": 830, + "x": 930, "y": 720, "wires": [ [] @@ -4924,7 +4981,7 @@ "resendOnRefresh": true, "templateScope": "local", "className": "", - "x": 890, + "x": 990, "y": 760, "wires": [ [] @@ -4948,7 +5005,7 @@ "resendOnRefresh": true, "templateScope": "local", "className": "", - "x": 860, + "x": 960, "y": 800, "wires": [ [] @@ -4972,7 +5029,7 @@ "resendOnRefresh": true, "templateScope": "local", "className": "", - "x": 850, + "x": 950, "y": 840, "wires": [ [] @@ -5016,7 +5073,7 @@ "resendOnRefresh": true, "templateScope": "local", "className": "", - "x": 820, + "x": 920, "y": 920, "wires": [ [] @@ -5148,7 +5205,23 @@ ] }, { - "id": "bdeb6f634e7ecffd", + "id": "543715b9199cdb72", + "type": "debug", + "z": "14f8c9b5ce1235cc", + "name": "debug 5", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "false", + "statusVal": "", + "statusType": "auto", + "x": 700, + "y": 40, + "wires": [] + }, + { + "id": "72c774fc51af957b", "type": "debug", "z": "14f8c9b5ce1235cc", "name": "debug 1", @@ -5156,11 +5229,11 @@ "tosidebar": true, "console": false, "tostatus": false, - "complete": "true", - "targetType": "full", + "complete": "payload", + "targetType": "msg", "statusVal": "", "statusType": "auto", - "x": 360, + "x": 400, "y": 140, "wires": [] }, From a6bb031f6d0ba30df105e2e04848d31e7a97379f Mon Sep 17 00:00:00 2001 From: Thibaut Pollina Date: Mon, 8 Dec 2025 15:48:30 +0000 Subject: [PATCH 31/36] update --- node-red/projects/dashboard/flows.json | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/node-red/projects/dashboard/flows.json b/node-red/projects/dashboard/flows.json index 8010ae6dc..96b803d74 100644 --- a/node-red/projects/dashboard/flows.json +++ b/node-red/projects/dashboard/flows.json @@ -2548,7 +2548,7 @@ "width": "0", "height": "0", "head": "", - "format": "", + "format": "", "storeOutMessages": true, "passthru": true, "resendOnRefresh": true, @@ -2572,7 +2572,7 @@ "width": "0", "height": "0", "head": "", - "format": "", + "format": "", "storeOutMessages": true, "passthru": true, "resendOnRefresh": true, @@ -4380,7 +4380,8 @@ "y": 40, "wires": [ [ - "90c920ecdab16908" + "90c920ecdab16908", + "384aa9ab83bfe566" ] ] }, @@ -4405,7 +4406,6 @@ "y": 40, "wires": [ [ - "384aa9ab83bfe566", "164baa371893de7a", "543715b9199cdb72" ] @@ -4540,7 +4540,6 @@ "y": 180, "wires": [ [ - "384aa9ab83bfe566", "9a10d9afb1a0b1ca" ] ] From ea9d6c4e0a5764a4acc1c56428b5228b8b956c97 Mon Sep 17 00:00:00 2001 From: Thibaut Pollina Date: Tue, 9 Dec 2025 08:31:25 +0000 Subject: [PATCH 32/36] f --- controller/light/LM36011.py | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/controller/light/LM36011.py b/controller/light/LM36011.py index 78da079dd..3216e0eba 100644 --- a/controller/light/LM36011.py +++ b/controller/light/LM36011.py @@ -90,15 +90,6 @@ def set_torch_current(self, current): value = int(current * 0.34) self._write_byte(self.Register.torch, value) - # 0 - 127 - # 0 is ~ 2.4 mA - # 128 is ~ 376 mA - def set_torch_value(self, value: int) -> None: - self._write_byte(self.Register.torch, int(value)) - - def get_torch_value(self) -> int: - return self._read_byte(self.Register.torch) - def get_torch_current(self): return self._read_byte(self.Register.torch) @@ -161,9 +152,9 @@ def deinit() -> None: def get_value() -> float: - return led.get_torch_current() / 127 + return int(round(led.get_torch_current() / 20)) def set_value(value: float) -> None: - led.set_torch_value(int(value * 127)) + led.set_torch_current(int(round(value * 20))) return From c13ecedebd2d91095244818de851cd361fd768b3 Mon Sep 17 00:00:00 2001 From: Thibaut Polina Date: Mon, 2 Feb 2026 15:55:10 +0000 Subject: [PATCH 33/36] save --- node-red/projects/dashboard/flows.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/node-red/projects/dashboard/flows.json b/node-red/projects/dashboard/flows.json index 96b803d74..0d998c8da 100644 --- a/node-red/projects/dashboard/flows.json +++ b/node-red/projects/dashboard/flows.json @@ -3715,7 +3715,7 @@ "width": 0, "height": 0, "head": "", - "format": "\n\n\n\n", + "format": "\n\n\n\n", "storeOutMessages": true, "passthru": true, "resendOnRefresh": true, From e4c28dbd10ff6a863c5a3a41451e95cdba040314 Mon Sep 17 00:00:00 2001 From: Sonny Piers Date: Mon, 2 Feb 2026 16:48:50 +0000 Subject: [PATCH 34/36] update --- node-red/projects/dashboard/flows.json | 122 ++++++++++++++++++------- 1 file changed, 87 insertions(+), 35 deletions(-) diff --git a/node-red/projects/dashboard/flows.json b/node-red/projects/dashboard/flows.json index 0d998c8da..fc4feefab 100644 --- a/node-red/projects/dashboard/flows.json +++ b/node-red/projects/dashboard/flows.json @@ -533,7 +533,7 @@ { "id": "e6ae26617c24c3ea", "type": "ui-base", - "name": "PlanktoScope", + "name": "PlanktoScope Dashboard", "path": "/dashboard", "appIcon": "", "includeClientData": true, @@ -563,6 +563,7 @@ "protocolVersion": 4, "keepalive": "60", "cleansession": true, + "autoUnsubscribe": true, "birthTopic": "", "birthQos": "0", "birthPayload": "", @@ -1297,7 +1298,7 @@ "cols": "12" } ], - "order": 2, + "order": 1, "className": "", "visible": "true", "disabled": "false" @@ -1523,7 +1524,7 @@ "cols": "12" } ], - "order": 1, + "order": 2, "className": "", "visible": "true", "disabled": "false" @@ -2084,13 +2085,13 @@ "id": "7e7d02f3ea356eff", "type": "switch", "z": "07f3b717f2a2c8c7", - "name": "msg.payload.page.path === \"/home\"", + "name": "msg.payload.page.path === \"/setup\"", "property": "payload.page.path", "propertyType": "msg", "rules": [ { "t": "eq", - "v": "/home", + "v": "/setup", "vt": "str" } ], @@ -2101,7 +2102,7 @@ "y": 40, "wires": [ [ - "d55333906aeb7517" + "46b27e4a0a66d96d" ] ] }, @@ -2119,26 +2120,6 @@ ] ] }, - { - "id": "d55333906aeb7517", - "type": "function", - "z": "07f3b717f2a2c8c7", - "name": "Get Global Variables", - "func": "const keys = global.keys(); // Get all global variable keys\nmsg.payload = {}; // Initialize the payload object\n\nkeys.forEach(key => {\n // Ignore keys that start with \"$\"\n if (!key.startsWith('$')) {\n msg.payload[key] = global.get(key);\n }\n});\n\nreturn msg;\n", - "outputs": 1, - "timeout": 0, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 120, - "y": 140, - "wires": [ - [ - "87b0cc67cfa07120" - ] - ] - }, { "id": "87b0cc67cfa07120", "type": "ui-template", @@ -2151,17 +2132,18 @@ "width": "0", "height": "0", "head": "", - "format": "\n\n", + "format": "\n\n", "storeOutMessages": true, "passthru": true, "resendOnRefresh": true, "templateScope": "local", "className": "", - "x": 370, - "y": 140, + "x": 310, + "y": 180, "wires": [ [ - "0470c8bd5e2507dc" + "0470c8bd5e2507dc", + "6f65cb84146c90a6" ] ] }, @@ -2194,19 +2176,89 @@ "type": "function", "z": "07f3b717f2a2c8c7", "name": "set general settings", - "func": "if (msg.topic) {\n global.set(\"countries\", msg.payload.countries);\n global.set(\"timezone\", msg.payload.timezone);\n}\nreturn msg;", + "func": "if (msg.topic) {\n global.set(\"region\", msg.payload.region);\n global.set(\"timezone\", msg.payload.timezone);\n}\nreturn msg;", "outputs": 1, "timeout": "", "noerr": 0, "initialize": "", "finalize": "", "libs": [], - "x": 590, - "y": 140, + "x": 650, + "y": 180, + "wires": [ + [] + ] + }, + { + "id": "46b27e4a0a66d96d", + "type": "read setup", + "z": "07f3b717f2a2c8c7", + "name": "", + "x": 150, + "y": 180, + "wires": [ + [ + "87b0cc67cfa07120" + ] + ] + }, + { + "id": "55194d8dcc16d7d7", + "type": "write setup", + "z": "07f3b717f2a2c8c7", + "name": "", + "x": 670, + "y": 240, "wires": [ [] ] }, + { + "id": "6f65cb84146c90a6", + "type": "switch", + "z": "07f3b717f2a2c8c7", + "name": "", + "property": "topic", + "propertyType": "msg", + "rules": [ + { + "t": "eq", + "v": "setup/saved", + "vt": "str" + } + ], + "checkall": "true", + "repair": false, + "outputs": 1, + "x": 470, + "y": 240, + "wires": [ + [ + "55194d8dcc16d7d7" + ] + ] + }, + { + "id": "647c53658f239b9f", + "type": "mqtt in", + "z": "07f3b717f2a2c8c7", + "name": "", + "topic": "setup/ready", + "qos": "2", + "datatype": "auto-detect", + "broker": "8dc3722c.06efa8", + "nl": false, + "rap": true, + "rh": 0, + "inputs": 0, + "x": 150, + "y": 240, + "wires": [ + [ + "87b0cc67cfa07120" + ] + ] + }, { "id": "1cf1b20e425ec126", "type": "ui-template", @@ -2505,7 +2557,7 @@ "templateScope": "widget:ui", "className": "", "x": 800, - "y": 180, + "y": 200, "wires": [ [ "1db1c2e3e19e85ff" @@ -3715,7 +3767,7 @@ "width": 0, "height": 0, "head": "", - "format": "\n\n\n\n", + "format": "\n\n\n\n", "storeOutMessages": true, "passthru": true, "resendOnRefresh": true, From 698f8a2fe3007b225a5cb176db81718cbe7f8a26 Mon Sep 17 00:00:00 2001 From: Sonny Piers Date: Mon, 2 Feb 2026 16:50:29 +0000 Subject: [PATCH 35/36] update --- node-red/projects/dashboard/flows.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/node-red/projects/dashboard/flows.json b/node-red/projects/dashboard/flows.json index fc4feefab..a1a57414b 100644 --- a/node-red/projects/dashboard/flows.json +++ b/node-red/projects/dashboard/flows.json @@ -2183,8 +2183,8 @@ "initialize": "", "finalize": "", "libs": [], - "x": 650, - "y": 180, + "x": 710, + "y": 140, "wires": [ [] ] From dbf0ce114f8115df42f3a1eed5ee82ed9aee350c Mon Sep 17 00:00:00 2001 From: Adam Date: Mon, 2 Feb 2026 15:31:46 -0800 Subject: [PATCH 36/36] feat: live segmentation with acquisition synchronization fixes Add real-time segmentation preview during image acquisition with robust synchronization to prevent race conditions and interference. Features: - Live segmentation with blur metrics and object detection preview - Real-time feedback during acquisition Fixes: - Prevent retained MQTT "Done" messages from skipping pump cycles - Tag acquisition pump commands with from_acquisition flag - Acquisition lock prevents UI pump controls from interfering - fsync ensures file is written before announcing via MQTT - File stability check before reading captured images --- controller/imager/main.py | 16 +- controller/imager/stopflow.py | 5 +- controller/pump/main.py | 23 + segmenter/planktoscope/segmenter/live.py | 738 +++++++++++++++++++++++ 4 files changed, 780 insertions(+), 2 deletions(-) create mode 100644 segmenter/planktoscope/segmenter/live.py diff --git a/controller/imager/main.py b/controller/imager/main.py index f69df8430..eae1932c3 100644 --- a/controller/imager/main.py +++ b/controller/imager/main.py @@ -382,6 +382,7 @@ def __init__(self) -> None: self._stop_receiving_mqtt = threading.Event() # close() was called self._done = threading.Event() # run_discrete() finished or stop() was called self._discrete_run = threading.Lock() # mutex on starting the pump + self._waiting_for_pump = False # FIX: Only accept Done after pump command sent def open(self) -> None: """Start the pump MQTT client. @@ -412,7 +413,17 @@ def _receive_messages(self) -> None: self._mqtt.read_message() continue + # FIX: Only process Done if we are actually waiting for pump to finish + # This prevents retained/stale Done messages from triggering early return + if not self._waiting_for_pump: + loguru.logger.debug( + f"Ignoring pump Done (not waiting for pump): {self._mqtt.msg['payload']}" + ) + self._mqtt.read_message() + continue + loguru.logger.debug(f"The pump has stopped: {self._mqtt.msg['payload']}") + self._waiting_for_pump = False # FIX: Clear waiting flag self._mqtt.client.unsubscribe("status/pump") self._mqtt.read_message() self._done.set() @@ -436,6 +447,7 @@ def run_discrete(self, settings: stopflow.DiscretePumpSettings) -> None: # We ignore the pylint error here because the lock can only be released from a different # thread (the thread which calls the `handle_status_update()` method): self._discrete_run.acquire() # pylint: disable=consider-using-with + self._waiting_for_pump = False # FIX: Not waiting yet (ignore retained messages) self._done.clear() self._mqtt.client.subscribe("status/pump") self._mqtt.client.publish( @@ -446,9 +458,11 @@ def run_discrete(self, settings: stopflow.DiscretePumpSettings) -> None: "direction": settings.direction.value, "flowrate": settings.flowrate, "volume": settings.volume, + "from_acquisition": True, # FIX: Tag as acquisition command } ), ) + self._waiting_for_pump = True # FIX: NOW we're waiting for pump Done self._done.wait() def stop(self) -> None: @@ -457,7 +471,7 @@ def stop(self) -> None: raise RuntimeError("MQTT client was not initialized yet!") self._mqtt.client.subscribe("status/pump") - self._mqtt.client.publish("actuator/pump", '{"action": "stop"}') + self._mqtt.client.publish("actuator/pump", '{"action": "stop", "from_acquisition": true}') def close(self) -> None: """Close the pump MQTT client, if it's currently open. diff --git a/controller/imager/stopflow.py b/controller/imager/stopflow.py index c64b48c1c..a11e8ae14 100644 --- a/controller/imager/stopflow.py +++ b/controller/imager/stopflow.py @@ -133,7 +133,10 @@ def run_step(self) -> typing.Optional[tuple[int, str]]: + f"{capture_path}...", ) self._camera.capture_file(capture_path) - os.sync() + # FIX: Use fsync on specific file to ensure write completes before MQTT publish + # os.sync() is system-wide and async - doesn't guarantee this file is written + with open(capture_path, "rb") as f: + os.fsync(f.fileno()) # Note(ethanjli): updating the integrity file is the responsibility of the code which # calls this `run_step()` method. diff --git a/controller/pump/main.py b/controller/pump/main.py index 68b27806c..448c1f82f 100644 --- a/controller/pump/main.py +++ b/controller/pump/main.py @@ -21,6 +21,8 @@ pump_max_speed = 50 pump_started = False +# FIX: Track acquisition state to prevent UI commands from interrupting acquisition +acquisition_in_progress = False pump_stepper = Motor(pin=23, spi_bus=0, spi_device=0) pump_stepper.acceleration = 2000 @@ -52,6 +54,7 @@ async def start() -> None: async with client, task_group: _ = await asyncio.gather( client.subscribe("actuator/pump"), + client.subscribe("status/imager"), # FIX: Track acquisition state # publish_status(), ) async for message in client.messages: @@ -59,12 +62,32 @@ async def start() -> None: async def handle_message(message) -> None: + global acquisition_in_progress + + # FIX: Handle imager status messages to track acquisition state + if message.topic.matches("status/imager"): + payload = json.loads(message.payload.decode("utf-8")) + status = payload.get("status", "") + if status == "Started": + print("Acquisition started - locking pump for acquisition commands only") + acquisition_in_progress = True + elif status in ("Done", "Interrupted"): + print("Acquisition ended - unlocking pump for manual control") + acquisition_in_progress = False + return + if not message.topic.matches("actuator/pump"): return payload = json.loads(message.payload.decode("utf-8")) pprint(payload) + # FIX: During acquisition, only accept commands tagged with from_acquisition + is_from_acquisition = payload.get("from_acquisition", False) + if acquisition_in_progress and not is_from_acquisition: + print(f"Ignoring pump command during acquisition (use from_acquisition flag): {payload}") + return + action = payload.get("action") if action is not None: await handle_action(action, payload) diff --git a/segmenter/planktoscope/segmenter/live.py b/segmenter/planktoscope/segmenter/live.py new file mode 100644 index 000000000..062835ddb --- /dev/null +++ b/segmenter/planktoscope/segmenter/live.py @@ -0,0 +1,738 @@ +# Copyright (C) 2021 Romain Bazile +# +# This file is part of the PlanktoScope software. +# +# PlanktoScope is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# PlanktoScope is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with PlanktoScope. If not, see . + +"""Live segmentation module for acquisition overlay analysis. + +This module provides real-time segmentation during acquisition. When enabled, +it listens for image captures from the imager and segments each frame as it +is captured, publishing results for overlay display. + +Features: +- Real-time segmentation overlay for preview +- Saves object crops to /home/pi/data/objects for visualization +- Writes EcoTaxa-compatible TSV incrementally +- Publishes MQTT updates for live dashboard refresh +""" + +import base64 +import io +import json +import math +import multiprocessing +import os +import time +from datetime import datetime +from pathlib import Path + +import cv2 +import numpy as np +import PIL.Image +import skimage.measure +from loguru import logger + +import planktoscope.mqtt +import planktoscope.segmenter.operations +import planktoscope.segmenter.encoder + +logger.info("planktoscope.segmenter.live is loaded") + +# Hardware config path (same as used by controller) +HARDWARE_CONFIG_PATH = "/home/pi/PlanktoScope/hardware.json" + +# Paths for visualization output +IMG_BASE = "/home/pi/data/img" +OBJECTS_BASE = "/home/pi/data/objects" +LIVE_STATS_FILE = "/tmp/live_seg_stats.json" + +# EcoTaxa TSV column headers +ECOTAXA_COLUMNS = [ + "object_id", "object_date", "object_time", + "object_x", "object_y", "object_width", "object_height", + "object_area", "object_perim.", "object_major", "object_minor", + "object_circ.", "object_elongation", "object_solidity", + "object_equivalent_diameter", + "object_MeanHue", "object_MeanSaturation", "object_MeanValue", + "object_blur_laplacian", + "sample_id", "acq_id", "img_file_name" +] + + +class LiveSegmenterProcess(multiprocessing.Process): + """Live segmentation worker that analyzes frames during acquisition. + + This process listens for image captures from the imager during acquisition + and performs real-time segmentation on each captured frame. Results are + published via MQTT for overlay display on the frontend. + """ + + @logger.catch + def __init__(self, event, data_path): + """Initialize the LiveSegmenter class. + + Args: + event (multiprocessing.Event): shutdown event + data_path (str): base data path + """ + super(LiveSegmenterProcess, self).__init__(name="live_segmenter") + + logger.info("planktoscope.segmenter.live is initialising") + + self.stop_event = event + self.live_client = None + self.imager_client = None + self.__data_path = data_path + self.__enabled = False # Whether live segmentation overlay is enabled + self.__overlay_mode = "bbox" # bbox, mask, or both + self.__min_area = 100 # Minimum area in pixels for detected objects + self.__pixel_size_um = self._load_pixel_size() # Load from hardware config + self.__remove_static = False # Remove objects that appear in same position across frames + self.__static_tracker = {} # Track objects by position: {(cx, cy): frame_count} + self.__static_threshold = 2 # FIX: Reduced from 3 to 2 for faster debris detection + + # Visualization state + self.__save_crops = True # Save object crops for visualization gallery + self.__current_acq_folder = None + self.__object_counter = 0 + self.__frame_counter = 0 + + logger.success("planktoscope.segmenter.live is initialised and ready to go!") + + def _load_pixel_size(self): + """Load pixel size from hardware config file. + + Reads process_pixel_fixed from /home/pi/PlanktoScope/hardware.json. + This ensures consistency with the calibration value set in the dashboard. + + Returns: + float: Pixel size in micrometers per pixel. Defaults to 0.75 if not found. + """ + default_pixel_size = 0.75 + try: + with open(HARDWARE_CONFIG_PATH, "r") as f: + config = json.load(f) + pixel_size = config.get("process_pixel_fixed", default_pixel_size) + logger.info(f"Loaded pixel size from hardware config: {pixel_size} µm/pixel") + return float(pixel_size) + except FileNotFoundError: + logger.warning( + f"Hardware config not found at {HARDWARE_CONFIG_PATH}, " + f"using default pixel size: {default_pixel_size} µm/pixel" + ) + return default_pixel_size + except (json.JSONDecodeError, ValueError) as e: + logger.error( + f"Error reading hardware config: {e}, " + f"using default pixel size: {default_pixel_size} µm/pixel" + ) + return default_pixel_size + + def _get_acquisition_info(self, image_path): + """Extract acquisition info from image path. + + Path format: /home/pi/data/img/DATE/SAMPLE_ID/ACQ_ID/image.jpg + """ + try: + parts = image_path.split("/") + if "img" in parts: + idx = parts.index("img") + date_folder = parts[idx + 1] if len(parts) > idx + 1 else "" + sample_folder = parts[idx + 2] if len(parts) > idx + 2 else "" + acq_folder = parts[idx + 3] if len(parts) > idx + 3 else "" + + return { + "date": date_folder, + "sample_id": sample_folder, + "acq_id": acq_folder, + "acq_folder": acq_folder, + } + except Exception: + pass + + return { + "date": datetime.now().strftime("%Y-%m-%d"), + "sample_id": "unknown", + "acq_id": "A_0", + "acq_folder": "unknown", + } + + def _derive_output_dir(self, image_path): + """Get output directory for object crops, mirroring img structure.""" + abs_path = os.path.abspath(image_path) + img_dir = os.path.dirname(abs_path) + + if img_dir.startswith(IMG_BASE): + rel_path = os.path.relpath(img_dir, IMG_BASE) + return os.path.join(OBJECTS_BASE, rel_path) + return os.path.join(img_dir, "objects") + + def _write_tsv_header(self, tsv_path): + """Write EcoTaxa TSV header.""" + try: + with open(tsv_path, "w") as f: + f.write("\t".join(ECOTAXA_COLUMNS) + "\n") + types = ["[t]"] * len(ECOTAXA_COLUMNS) + f.write("\t".join(types) + "\n") + return True + except Exception as e: + logger.error(f"Failed to write TSV header: {e}") + return False + + def _append_tsv_row(self, tsv_path, row_data): + """Append a single row to the TSV file.""" + try: + with open(tsv_path, "a") as f: + values = [] + for col in ECOTAXA_COLUMNS: + val = row_data.get(col, "") + if isinstance(val, float): + values.append(f"{val:.4f}") + else: + values.append(str(val)) + f.write("\t".join(values) + "\n") + return True + except Exception as e: + logger.error(f"Failed to append TSV row: {e}") + return False + + def _extract_object_features(self, img, region, bbox): + """Extract morphological features from an object for TSV.""" + x, y, w, h = bbox + + # Extract ROI + roi_img = img[y:y+h, x:x+w] + + # Area and perimeter from region + area = int(region.area) + perimeter = float(region.perimeter) + + # Major/minor axes + major = float(region.major_axis_length) if region.major_axis_length else max(w, h) + minor = float(region.minor_axis_length) if region.minor_axis_length else min(w, h) + + # Derived metrics + circularity = (4 * math.pi * area / (perimeter ** 2)) if perimeter > 0 else 0 + elongation = major / minor if minor > 0 else 1.0 + solidity = float(region.solidity) if region.solidity else 1.0 + equivalent_diameter = float(region.equivalent_diameter) if region.equivalent_diameter else (4 * area / math.pi) ** 0.5 + + # HSV color statistics + mean_hue, mean_sat, mean_val = 0, 0, 0 + try: + roi_hsv = cv2.cvtColor(roi_img, cv2.COLOR_BGR2HSV) + mean_hue = float(np.mean(roi_hsv[:, :, 0])) + mean_sat = float(np.mean(roi_hsv[:, :, 1])) + mean_val = float(np.mean(roi_hsv[:, :, 2])) + except Exception: + pass + + # Blur metric + blur_laplacian = planktoscope.segmenter.operations.calculate_blur(roi_img) + + return { + "object_x": x + w / 2, + "object_y": y + h / 2, + "object_width": w, + "object_height": h, + "object_area": area, + "object_perim.": perimeter, + "object_major": major, + "object_minor": minor, + "object_circ.": circularity, + "object_elongation": elongation, + "object_solidity": solidity, + "object_equivalent_diameter": equivalent_diameter, + "object_MeanHue": mean_hue, + "object_MeanSaturation": mean_sat, + "object_MeanValue": mean_val, + "object_blur_laplacian": blur_laplacian, + } + + def _publish_visualization_update(self, output_dir, total_objects, total_frames): + """Publish MQTT update for visualization dashboard refresh.""" + try: + message = { + "status": "segmenting", + "total_objects": total_objects, + "total_images": total_frames, + "output_dir": output_dir, + "timestamp": time.time(), + } + self.live_client.client.publish( + "status/segmentation", + json.dumps(message), + ) + except Exception as e: + logger.debug(f"Failed to publish visualization update: {e}") + + def _esd_um_to_min_area(self, esd_um): + """Convert ESD in micrometers to minimum area in pixels. + + Args: + esd_um (float): Equivalent spherical diameter in micrometers + + Returns: + int: Minimum area in pixels + """ + # Convert ESD from micrometers to pixels + esd_pixels = esd_um / self.__pixel_size_um + # Calculate area of a circle with this diameter + area = math.pi * (esd_pixels / 2) ** 2 + return int(area) + + def _create_simple_mask(self, img): + """Create a mask using simple thresholding. + + Args: + img (np.array): BGR image + + Returns: + np.array: binary mask + """ + mask = planktoscope.segmenter.operations.simple_threshold(img) + mask = planktoscope.segmenter.operations.erode(mask) + mask = planktoscope.segmenter.operations.dilate(mask) + return mask + + def _get_bbox_key(self, bbox): + """Get a grid key for a bounding box center for tracking. + + Uses 100px grid cells - large enough to tolerate detection variation + in elongated objects while still distinguishing separate small objects. + + Args: + bbox: [x, y, w, h] bounding box + + Returns: + tuple: (grid_x, grid_y) key + """ + cx = bbox[0] + bbox[2] / 2 + cy = bbox[1] + bbox[3] / 2 + grid_size = 100 # FIX: Larger grid (was 60) tolerates detection jitter for stuck objects + return (int(cx / grid_size), int(cy / grid_size)) + + def _update_static_tracker(self, current_bboxes): + """Update the static object tracker with current frame's objects. + + Objects that appear in the same grid cell across multiple frames + get their count incremented. Objects not seen are removed. + + Args: + current_bboxes: list of [x, y, w, h] bounding boxes from current frame + """ + # Get all current grid positions + current_keys = set() + for bbox in current_bboxes: + key = self._get_bbox_key(bbox) + current_keys.add(key) + + # Update tracker: increment seen, remove unseen + new_tracker = {} + for key in current_keys: + if key in self.__static_tracker: + new_tracker[key] = self.__static_tracker[key] + 1 + else: + new_tracker[key] = 1 + + self.__static_tracker = new_tracker + + def _is_static_object(self, bbox): + """Check if an object has been static for multiple frames. + + Args: + bbox: [x, y, w, h] bounding box + + Returns: + bool: True if object is static (appeared in same position for N+ frames) + """ + key = self._get_bbox_key(bbox) + count = self.__static_tracker.get(key, 0) + return count >= self.__static_threshold + + def _encode_mask_png(self, mask): + """Encode a binary mask as base64 PNG with alpha transparency. + + Args: + mask (np.array): binary mask + + Returns: + str: base64 encoded PNG string with alpha channel + """ + # Convert binary mask to RGBA with alpha transparency + # Object pixels = white with full opacity, background = transparent + height, width = mask.shape + rgba = np.zeros((height, width, 4), dtype=np.uint8) + rgba[mask, :3] = 255 # White RGB for object pixels + rgba[mask, 3] = 255 # Full opacity for object pixels + # Background pixels remain (0,0,0,0) = transparent + + img = PIL.Image.fromarray(rgba, mode="RGBA") + buffer = io.BytesIO() + img.save(buffer, format="PNG") + return base64.b64encode(buffer.getvalue()).decode("utf-8") + + def segment_single_frame(self, img): + """Segment a single frame and return object data. + + Args: + img (np.array): BGR image + + Returns: + dict: segmentation results with objects, frame_blur, and image dimensions + """ + # Get image dimensions for frontend scaling + img_height, img_width = img.shape[:2] + + # Calculate frame-level blur + frame_blur = planktoscope.segmenter.operations.calculate_blur(img) + + # Calculate regional blur for heatmap visualization (4x4 grid) + blur_grid = planktoscope.segmenter.operations.calculate_regional_blur(img, 4, 4) + + # Create mask + mask = self._create_simple_mask(img) + + # Find objects + labels, nlabels = skimage.measure.label(mask, return_num=True) + regionprops = skimage.measure.regionprops(labels) + + # Filter by minimum area and sort by area (largest first) + regionprops_filtered = [ + region for region in regionprops if region.area >= self.__min_area + ] + regionprops_filtered.sort(key=lambda r: r.area, reverse=True) + + # Build list of all bboxes and regions for this frame + all_bboxes = [] + bbox_region_pairs = [] + for region in regionprops_filtered: + bbox = [ + int(region.bbox[1]), # x + int(region.bbox[0]), # y + int(region.bbox[3] - region.bbox[1]), # width + int(region.bbox[2] - region.bbox[0]), # height + ] + all_bboxes.append(bbox) + bbox_region_pairs.append((bbox, region)) + + # Update static tracker with all detected objects BEFORE filtering + if self.__remove_static: + self._update_static_tracker(all_bboxes) + + # Build output objects, filtering static ones if enabled + objects = [] + max_masks = 100 + + for bbox, region in bbox_region_pairs: + # Skip static objects (only if they've been seen for N+ consecutive frames) + if self.__remove_static and self._is_static_object(bbox): + continue + + obj_data = { + "bbox": bbox, + } + + # Include mask for objects + if self.__overlay_mode in ("mask", "both") and len(objects) < max_masks: + obj_data["mask"] = self._encode_mask_png(region.filled_image) + + objects.append(obj_data) + + # Limit total objects for performance + if len(objects) >= 300: + break + + return { + "objects": objects, + "frame_blur": float(frame_blur), + "blur_grid": blur_grid, # 4x4 regional blur heatmap + "object_count": len(objects), # Count after static filtering + "image_width": img_width, + "image_height": img_height, + } + + def _process_captured_image(self, img_path): + """Process a captured image from acquisition. + + Segments the image, saves object crops for visualization, + writes TSV data, and publishes results via MQTT. + + Args: + img_path (str): path to the captured image file + """ + if not self.__enabled: + return + + try: + if not os.path.exists(img_path): + logger.warning(f"Image file not found: {img_path}") + return + + # FIX: Wait for file to be fully written before reading + # The imager uses fsync, but we add a stability check as defense-in-depth + prev_size = -1 + for _ in range(10): # Max 500ms wait (10 * 50ms) + try: + curr_size = os.path.getsize(img_path) + if curr_size > 0 and curr_size == prev_size: + break # File size stable, safe to read + prev_size = curr_size + time.sleep(0.05) + except OSError: + time.sleep(0.05) + + # Load the captured image + frame = cv2.imread(img_path) + if frame is None: + logger.warning(f"Failed to load image: {img_path}") + return + + logger.debug(f"Processing captured image: {img_path}") + + # Get acquisition info + acq_info = self._get_acquisition_info(img_path) + acq_folder = acq_info.get("acq_folder", "") + + # Reset counters if new acquisition + if acq_folder != self.__current_acq_folder: + self.__current_acq_folder = acq_folder + self.__object_counter = 0 + self.__frame_counter = 0 + self.__static_tracker = {} + + self.__frame_counter += 1 + + # Segment the frame (returns objects with bbox, mask data) + result = self.segment_single_frame(frame) + + # Setup output directory for crops + output_dir = self._derive_output_dir(img_path) + if self.__save_crops: + Path(output_dir).mkdir(parents=True, exist_ok=True) + + # Setup TSV file + tsv_path = os.path.join(output_dir, f"ecotaxa_{acq_info['acq_id']}.tsv") + if self.__save_crops and not os.path.exists(tsv_path): + self._write_tsv_header(tsv_path) + + # Get base name for crops + base_name = os.path.splitext(os.path.basename(img_path))[0] + img_date = acq_info.get("date", "") + img_time = "00:00:00" + if "_" in base_name: + time_part = base_name.split("_")[1] if len(base_name.split("_")) > 1 else "" + if time_part: + img_time = time_part.replace("-", ":")[:8] + + # Re-segment to get regions for feature extraction and crop saving + mask = self._create_simple_mask(frame) + labels, _ = skimage.measure.label(mask, return_num=True) + regionprops = skimage.measure.regionprops(labels) + + # Process each object in the result + saved_crops = 0 + for obj in result.get("objects", []): + bbox = obj.get("bbox") + if not bbox: + continue + + x, y, w, h = bbox + + # Find matching region for this bbox + matching_region = None + for region in regionprops: + rx = int(region.bbox[1]) + ry = int(region.bbox[0]) + if abs(rx - x) < 5 and abs(ry - y) < 5: + matching_region = region + break + + if not matching_region: + continue + + self.__object_counter += 1 + obj_id = self.__object_counter + + # Save crop with padding + pad = max(5, int(max(w, h) * 0.1)) + x1 = max(0, x - pad) + y1 = max(0, y - pad) + x2 = min(frame.shape[1], x + w + pad) + y2 = min(frame.shape[0], y + h + pad) + crop = frame[y1:y2, x1:x2] + + if self.__save_crops and crop.size > 0: + crop_filename = f"{base_name}_{obj_id}.jpg" + crop_path = os.path.join(output_dir, crop_filename) + cv2.imwrite(crop_path, crop) + saved_crops += 1 + + # Extract features and write TSV row + features = self._extract_object_features(frame, matching_region, bbox) + row_data = { + "object_id": f"{acq_info['sample_id']}_{acq_info['acq_id']}_{obj_id}", + "object_date": img_date, + "object_time": img_time, + "sample_id": acq_info["sample_id"], + "acq_id": acq_info["acq_id"], + "img_file_name": crop_filename, + **features, + } + self._append_tsv_row(tsv_path, row_data) + + # Encode the image as base64 JPEG for frontend display + _, jpeg_buffer = cv2.imencode('.jpg', frame, [cv2.IMWRITE_JPEG_QUALITY, 80]) + result["image"] = base64.b64encode(jpeg_buffer).decode("utf-8") + + # Publish results for overlay display + self.live_client.client.publish( + "status/segmenter/live", + json.dumps(result, cls=planktoscope.segmenter.encoder.NpEncoder), + ) + + # Publish visualization update + if self.__save_crops: + self._publish_visualization_update( + output_dir, + self.__object_counter, + self.__frame_counter + ) + + logger.debug(f"Published segmentation: {result['object_count']} objects, {saved_crops} crops saved") + + except Exception as e: + logger.error(f"Error processing captured image: {e}") + import traceback + logger.error(traceback.format_exc()) + + def _check_imager_messages(self): + """Check for new messages from the imager. + + Polls the imager MQTT client for progress events during acquisition + and triggers segmentation when live segmentation is enabled. + """ + if not self.imager_client.new_message_received(): + return + + try: + message = self.imager_client.msg["payload"] + logger.debug(f"Imager message received: {message}") + self.imager_client.read_message() + + # Check if this is a progress event during acquisition + # The imager publishes {"type": "progress", "path": "/path/to/image.jpeg", ...} + if message.get("type") == "progress" and "path" in message: + self._process_captured_image(message["path"]) + + except Exception as e: + logger.error(f"Error processing imager message: {e}") + + @logger.catch + def treat_message(self): + """Process incoming MQTT messages for live segmentation control.""" + if self.live_client.new_message_received(): + logger.info("Live segmenter received a new message") + last_message = self.live_client.msg["payload"] + logger.debug(last_message) + self.live_client.read_message() + + if "action" in last_message: + if last_message["action"] == "start": + logger.info("Enabling live segmentation overlay") + self.__overlay_mode = last_message.get("overlay", "bbox") + + # Handle min_esd_um (micrometers) or fall back to min_area (pixels) + if "min_esd_um" in last_message: + min_esd = last_message.get("min_esd_um", 20) + self.__min_area = self._esd_um_to_min_area(min_esd) + logger.info(f"Minimum ESD: {min_esd} µm = {self.__min_area} pixels²") + else: + self.__min_area = last_message.get("min_area", 100) + + # Handle remove_static option (subtract objects in same position across frames) + self.__remove_static = last_message.get("remove_static", True) + self.__static_tracker = {} # Reset tracker on start + if self.__remove_static: + logger.info("Static object removal enabled (filtering after 3+ consecutive frames)") + + self.__enabled = True + + # Publish status + self.live_client.client.publish( + "status/segmenter/live", + json.dumps({ + "status": "Enabled", + "overlay": self.__overlay_mode, + "min_area": self.__min_area, + "remove_static": self.__remove_static + }), + ) + + elif last_message["action"] == "stop": + logger.info("Disabling live segmentation overlay") + self.__enabled = False + self.__static_tracker = {} # Clear static tracker + + # Clear the overlay by publishing empty objects + self.live_client.client.publish( + "status/segmenter/live", + json.dumps({ + "status": "Disabled", + "objects": [], + "object_count": 0 + }), + ) + + @logger.catch + def run(self): + """Main process loop.""" + logger.info( + f"The live segmenter control thread has been started in process {os.getpid()}" + ) + + # MQTT Client for receiving commands + self.live_client = planktoscope.mqtt.MQTT_Client( + topic="segmenter/live", name="live_segmenter_client" + ) + + # MQTT Client for imager status - listen for capture events + self.imager_client = planktoscope.mqtt.MQTT_Client( + topic="status/imager", name="live_imager_client" + ) + + # Publish ready status + self.live_client.client.publish( + "status/segmenter/live", '{"status":"Ready"}' + ) + + logger.success("Live Segmenter is READY!") + + # Main loop - process control messages and imager events + while not self.stop_event.is_set(): + self.treat_message() + self._check_imager_messages() + time.sleep(0.05) + + logger.info("Shutting down the live segmenter process") + self.live_client.client.publish("status/segmenter/live", '{"status":"Dead"}') + self.live_client.shutdown() + self.imager_client.shutdown() + logger.success("Live segmenter process shut down! See you!") + + +# This guy is called if this script is launched directly +if __name__ == "__main__": + pass