diff --git a/flows.json b/flows.json index 3385e87..c1b9e90 100644 --- a/flows.json +++ b/flows.json @@ -1,7823 +1,7944 @@ [ - { - "id": "b2bf18a0f40a5d72", - "type": "tab", - "label": "Setup", - "disabled": false, - "info": "", - "env": [] - }, - { - "id": "f90406ba2da5932f", - "type": "tab", - "label": "Home", - "disabled": false, - "info": "", - "env": [] - }, - { - "id": "6d6a011bf1913637", - "type": "tab", - "label": "Preview", - "disabled": false, - "info": "", - "env": [] - }, - { - "id": "f7ff9c0e54da4b8e", - "type": "tab", - "label": "Metadata", - "disabled": false, - "info": "", - "env": [] - }, - { - "id": "71ede8b7dd88d90e", - "type": "tab", - "label": "Acquisition", - "disabled": false, - "info": "", - "env": [] - }, - { - "id": "524b7620431210e8", - "type": "tab", - "label": "Segmentation", - "disabled": false, - "info": "", - "env": [] - }, - { - "id": "4fdbd7bacb797c5a", - "type": "tab", - "label": "Visualization", - "disabled": true, - "info": "", - "env": [] - }, - { - "id": "a7825c4c81ad20a0", - "type": "tab", - "label": "Visualization", - "disabled": false, - "info": "", - "env": [] - }, - { - "id": "6fac9fa6a894b293", - "type": "tab", - "label": "Calibration", - "disabled": false, - "info": "", - "env": [] - }, - { - "id": "d70bc885c4bf46d4", - "type": "tab", - "label": "Calibration - Saturation Level", - "disabled": false, - "info": "", - "env": [] - }, - { - "id": "9dfa8db7ce31208f", - "type": "tab", - "label": "Calibration - Lightness", - "disabled": false, - "info": "", - "env": [] - }, - { - "id": "e597cdc71d5a9d33", - "type": "tab", - "label": "Calibration - Pixel size", - "disabled": false, - "info": "", - "env": [] - }, - { - "id": "b8e6b9dc69aa3f56", - "type": "tab", - "label": "Calibration - Pump", - "disabled": false, - "info": "", - "env": [] - }, - { - "id": "f8d7305edb3dce2c", - "type": "tab", - "label": "[TEST] EcoTaxa", - "disabled": false, - "info": "", - "env": [] - }, - { - "id": "e6d820ba0f4f184e", - "type": "tab", - "label": "[TEST] PlanktoScope nodes", - "disabled": false, - "info": "", - "env": [] - }, - { - "id": "b7861ce703215a01", - "type": "subflow", - "name": "Load hardware config", - "info": "", - "category": "", - "in": [ - { - "x": 40, - "y": 40, - "wires": [ - { - "id": "0f16258953fae292" - } - ] - } - ], - "out": [ - { - "x": 900, - "y": 40, - "wires": [ - { - "id": "d0fbcd200cd09981", - "port": 0 - } - ] - } - ], - "env": [], - "meta": {}, - "color": "#DDAA99" - }, - { - "id": "63c85b96537b7355", - "type": "subflow", - "name": "Config", - "info": "An input to this subflow will get the configuration to be saved to disk.\n\nOn startup, this node outputs the loaded configuration", - "category": "", - "in": [ - { - "x": 220, - "y": 160, - "wires": [ - { - "id": "5248e5e225d854d1" - } - ] - } - ], - "out": [ - { - "x": 1040, - "y": 60, - "wires": [ - { - "id": "f1c0a882a9e3d927", - "port": 0 - } - ] - } - ], - "env": [], - "color": "#DDAA99" - }, - { - "id": "c398718e1269dc31", - "type": "group", - "z": "d70bc885c4bf46d4", - "name": "Step Bar", - "style": { - "label": true - }, - "nodes": [ - "f5ecfcfd42bb61f0", - "c1c7cc675878b0f3" - ], - "x": 994, - "y": 19, - "w": 372, - "h": 82 - }, - { - "id": "f2d9c0f9aabc0a63", - "type": "group", - "z": "e597cdc71d5a9d33", - "name": "Step Bar", - "style": { - "label": true - }, - "nodes": [ - "9d60b1bdff1a8058", - "48d61ea72d468010" - ], - "x": 994, - "y": 19, - "w": 372, - "h": 82 - }, - { - "id": "a75dd1f78f8f991a", - "type": "group", - "z": "b8e6b9dc69aa3f56", - "name": "Step Bar", - "style": { - "label": true - }, - "nodes": [ - "07e989594965ab7d", - "5e94860de36e0a1c" - ], - "x": 994, - "y": 19, - "w": 372, - "h": 82 - }, - { - "id": "3f734d83a7327043", - "type": "group", - "z": "9dfa8db7ce31208f", - "name": "Step Bar", - "style": { - "label": true - }, - "nodes": [ - "5331fa94d1b90654", - "5f823fb3b1c49c65" - ], - "x": 994, - "y": 19, - "w": 372, - "h": 82 - }, - { - "id": "14cb9ab4b149e2b5", - "type": "group", - "z": "e6d820ba0f4f184e", - "style": { - "stroke": "#999999", - "stroke-opacity": "1", - "fill": "none", - "fill-opacity": "1", - "label": true, - "label-position": "nw", - "color": "#a4a4a4" - }, - "nodes": [ - "9975b01cde700ae1", - "b72d81f258ab6ed1", - "8773c66a58ecab21", - "fe5a0dc78ca52e26", - "80b749ee1cbdf98e" - ], - "x": 14, - "y": 19, - "w": 752, - "h": 202 - }, - { - "id": "e25bae849bace7c6", - "type": "group", - "z": "e6d820ba0f4f184e", - "style": { - "stroke": "#999999", - "stroke-opacity": "1", - "fill": "none", - "fill-opacity": "1", - "label": true, - "label-position": "nw", - "color": "#a4a4a4" - }, - "nodes": [ - "655adc853a6956f6", - "fec9c2aca5437f85", - "c63068f9aa421f2c", - "5f77688682c97241", - "e49db28bb805280e" - ], - "x": 14, - "y": 239, - "w": 812, - "h": 202 - }, - { - "id": "034aca701dd2a256", - "type": "group", - "z": "e6d820ba0f4f184e", - "style": { - "stroke": "#999999", - "stroke-opacity": "1", - "fill": "none", - "fill-opacity": "1", - "label": true, - "label-position": "nw", - "color": "#a4a4a4" - }, - "nodes": [ - "4f9158d3ed8eadd3", - "287f7112bac18c51", - "0820acd0d3b628d4", - "327e3da2b7e9e50b", - "c80721a372673baf" - ], - "x": 14, - "y": 459, - "w": 932, - "h": 182 - }, - { - "id": "e5f0bc40b541e13f", - "type": "group", - "z": "e6d820ba0f4f184e", - "style": { - "stroke": "#999999", - "stroke-opacity": "1", - "fill": "none", - "fill-opacity": "1", - "label": true, - "label-position": "nw", - "color": "#a4a4a4" - }, - "nodes": [ - "ec2b56583e1703f0", - "078727b344f7e34a", - "3e333d969e41b545" - ], - "x": 14, - "y": 659, - "w": 372, - "h": 142 - }, - { - "id": "329d175baf2183b1", - "type": "group", - "z": "e6d820ba0f4f184e", - "style": { - "stroke": "#999999", - "stroke-opacity": "1", - "fill": "none", - "fill-opacity": "1", - "label": true, - "label-position": "nw", - "color": "#a4a4a4" - }, - "nodes": [ - "3058d95dae6eea1f", - "355d4990f8958d65", - "a6c33b2025837058" - ], - "x": 14, - "y": 819, - "w": 372, - "h": 142 - }, - { - "id": "62930a749d014d90", - "type": "group", - "z": "e6d820ba0f4f184e", - "style": { - "stroke": "#999999", - "stroke-opacity": "1", - "fill": "none", - "fill-opacity": "1", - "label": true, - "label-position": "nw", - "color": "#a4a4a4" - }, - "nodes": [ - "528f6ce911af8982", - "baac40d3e7a8fcb2", - "660f8462d674baea" - ], - "x": 414, - "y": 819, - "w": 352, - "h": 142 - }, - { - "id": "039d9046b10d6840", - "type": "group", - "z": "e6d820ba0f4f184e", - "style": { - "stroke": "#999999", - "stroke-opacity": "1", - "fill": "none", - "fill-opacity": "1", - "label": true, - "label-position": "nw", - "color": "#a4a4a4" - }, - "nodes": [ - "f941634f9acf1e65", - "8d538b74ac03964b", - "3cb007c279dceed0", - "05d4bd56d2b42da1", - "8972c5887d7de2dc", - "8283323ac1aca81a", - "13bdc8d028ab9063" - ], - "x": 934, - "y": 39, - "w": 352, - "h": 262 - }, - { - "id": "0a51b211ebbb9873", - "type": "group", - "z": "e6d820ba0f4f184e", - "style": { - "stroke": "#999999", - "stroke-opacity": "1", - "fill": "none", - "fill-opacity": "1", - "label": true, - "label-position": "nw", - "color": "#a4a4a4" - }, - "nodes": [ - "d5950445bf78c0c6", - "36a787c8463c3fab", - "d25760aa70fd1524" - ], - "x": 994, - "y": 499, - "w": 412, - "h": 142 - }, - { - "id": "ef4667eb73b18227", - "type": "group", - "z": "e6d820ba0f4f184e", - "style": { - "stroke": "#999999", - "stroke-opacity": "1", - "fill": "none", - "fill-opacity": "1", - "label": true, - "label-position": "nw", - "color": "#a4a4a4" - }, - "nodes": [ - "9203a825fc1c63e4", - "110801015a0ec067", - "9d2be08e52de0394" - ], - "x": 994, - "y": 659, - "w": 412, - "h": 142 - }, - { - "id": "693296edc15d81c2", - "type": "group", - "z": "e6d820ba0f4f184e", - "style": { - "stroke": "#999999", - "stroke-opacity": "1", - "fill": "none", - "fill-opacity": "1", - "label": true, - "label-position": "nw", - "color": "#a4a4a4" - }, - "nodes": [ - "3973105e8c349a38", - "b8a021de6b15a728", - "e2c7db0aef7f64ef", - "d96066c662742119" - ], - "x": 14, - "y": 979, - "w": 562, - "h": 162 - }, - { - "id": "68e954becfafbf7b", - "type": "group", - "z": "e6d820ba0f4f184e", - "style": { - "stroke": "#999999", - "stroke-opacity": "1", - "fill": "none", - "fill-opacity": "1", - "label": true, - "label-position": "nw", - "color": "#a4a4a4" - }, - "nodes": [ - "47dce78fa2e5973b", - "9f0db647f3595ef9", - "e14a82906a749dc5" - ], - "x": 994, - "y": 819, - "w": 412, - "h": 142 - }, - { - "id": "e6ae26617c24c3ea", - "type": "ui-base", - "name": "PlanktoScope Dashboard", - "path": "/dashboard", - "appIcon": "", - "includeClientData": true, - "acceptsClientConfig": [ - "ui-notification", - "ui-control" - ], - "showPathInSidebar": true, - "headerContent": "dashboard", - "navigationStyle": "none", - "titleBarStyle": "fixed", - "showReconnectNotification": true, - "notificationDisplayTime": "1", - "showDisconnectNotification": true, - "allowInstall": true - }, - { - "id": "8dc3722c.06efa8", - "type": "mqtt-broker", - "name": "", - "broker": "0.0.0.0", - "port": "1883", - "clientid": "Client_node", - "autoConnect": true, - "usetls": false, - "compatmode": false, - "protocolVersion": 4, - "keepalive": "60", - "cleansession": true, - "autoUnsubscribe": true, - "birthTopic": "", - "birthQos": "0", - "birthPayload": "", - "closeTopic": "", - "closeQos": "0", - "closePayload": "", - "willTopic": "", - "willQos": "0", - "willPayload": "" - }, - { - "id": "81a80e32347ff991", - "type": "ui_base", - "theme": { - "name": "theme-light", - "lightTheme": { - "default": "#0094CE", - "baseColor": "#5900ce", - "baseFont": "-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif", - "edited": true, - "reset": false - }, - "darkTheme": { - "default": "#097479", - "baseColor": "#097479", - "baseFont": "-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif", - "edited": true, - "reset": false - }, - "customTheme": { - "name": "Untitled Theme 1", - "default": "#4B7930", - "baseColor": "#4B7930", - "baseFont": "-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif" - }, - "themeState": { - "base-color": { - "default": "#0094CE", - "value": "#0094CE", - "edited": true + { + "id": "b2bf18a0f40a5d72", + "type": "tab", + "label": "Setup", + "disabled": false, + "info": "", + "env": [] + }, + { + "id": "f90406ba2da5932f", + "type": "tab", + "label": "Home", + "disabled": false, + "info": "", + "env": [] + }, + { + "id": "6d6a011bf1913637", + "type": "tab", + "label": "Preview", + "disabled": false, + "info": "", + "env": [] + }, + { + "id": "f7ff9c0e54da4b8e", + "type": "tab", + "label": "Metadata", + "disabled": false, + "info": "", + "env": [] + }, + { + "id": "71ede8b7dd88d90e", + "type": "tab", + "label": "Acquisition", + "disabled": false, + "info": "", + "env": [] + }, + { + "id": "524b7620431210e8", + "type": "tab", + "label": "Segmentation", + "disabled": false, + "info": "", + "env": [] + }, + { + "id": "4fdbd7bacb797c5a", + "type": "tab", + "label": "Visualization", + "disabled": true, + "info": "", + "env": [] + }, + { + "id": "a7825c4c81ad20a0", + "type": "tab", + "label": "Visualization", + "disabled": false, + "info": "", + "env": [] + }, + { + "id": "6fac9fa6a894b293", + "type": "tab", + "label": "Calibration", + "disabled": false, + "info": "", + "env": [] + }, + { + "id": "d70bc885c4bf46d4", + "type": "tab", + "label": "Calibration - Saturation Level", + "disabled": false, + "info": "", + "env": [] + }, + { + "id": "9dfa8db7ce31208f", + "type": "tab", + "label": "Calibration - Lightness", + "disabled": false, + "info": "", + "env": [] + }, + { + "id": "e597cdc71d5a9d33", + "type": "tab", + "label": "Calibration - Pixel size", + "disabled": false, + "info": "", + "env": [] + }, + { + "id": "b8e6b9dc69aa3f56", + "type": "tab", + "label": "Calibration - Pump", + "disabled": false, + "info": "", + "env": [] + }, + { + "id": "f8d7305edb3dce2c", + "type": "tab", + "label": "[TEST] EcoTaxa", + "disabled": false, + "info": "", + "env": [] + }, + { + "id": "e6d820ba0f4f184e", + "type": "tab", + "label": "[TEST] PlanktoScope nodes", + "disabled": false, + "info": "", + "env": [] + }, + { + "id": "b7861ce703215a01", + "type": "subflow", + "name": "Load hardware config", + "info": "", + "category": "", + "in": [ + { + "x": 40, + "y": 40, + "wires": [ + { + "id": "0f16258953fae292" + } + ] + } + ], + "out": [ + { + "x": 900, + "y": 40, + "wires": [ + { + "id": "d0fbcd200cd09981", + "port": 0 + } + ] + } + ], + "env": [], + "meta": {}, + "color": "#DDAA99" + }, + { + "id": "63c85b96537b7355", + "type": "subflow", + "name": "Config", + "info": "An input to this subflow will get the configuration to be saved to disk.\n\nOn startup, this node outputs the loaded configuration", + "category": "", + "in": [ + { + "x": 220, + "y": 160, + "wires": [ + { + "id": "5248e5e225d854d1" + } + ] + } + ], + "out": [ + { + "x": 1040, + "y": 60, + "wires": [ + { + "id": "f1c0a882a9e3d927", + "port": 0 + } + ] + } + ], + "env": [], + "color": "#DDAA99" + }, + { + "id": "c398718e1269dc31", + "type": "group", + "z": "d70bc885c4bf46d4", + "name": "Step Bar", + "style": { + "label": true + }, + "nodes": [ + "f5ecfcfd42bb61f0", + "c1c7cc675878b0f3" + ], + "x": 994, + "y": 19, + "w": 372, + "h": 82 + }, + { + "id": "f2d9c0f9aabc0a63", + "type": "group", + "z": "e597cdc71d5a9d33", + "name": "Step Bar", + "style": { + "label": true + }, + "nodes": [ + "9d60b1bdff1a8058", + "48d61ea72d468010" + ], + "x": 994, + "y": 19, + "w": 372, + "h": 82 + }, + { + "id": "a75dd1f78f8f991a", + "type": "group", + "z": "b8e6b9dc69aa3f56", + "name": "Step Bar", + "style": { + "label": true + }, + "nodes": [ + "07e989594965ab7d", + "5e94860de36e0a1c" + ], + "x": 994, + "y": 19, + "w": 372, + "h": 82 + }, + { + "id": "3f734d83a7327043", + "type": "group", + "z": "9dfa8db7ce31208f", + "name": "Step Bar", + "style": { + "label": true + }, + "nodes": [ + "5331fa94d1b90654", + "5f823fb3b1c49c65" + ], + "x": 994, + "y": 19, + "w": 372, + "h": 82 + }, + { + "id": "14cb9ab4b149e2b5", + "type": "group", + "z": "e6d820ba0f4f184e", + "style": { + "stroke": "#999999", + "stroke-opacity": "1", + "fill": "none", + "fill-opacity": "1", + "label": true, + "label-position": "nw", + "color": "#a4a4a4" + }, + "nodes": [ + "9975b01cde700ae1", + "b72d81f258ab6ed1", + "8773c66a58ecab21", + "fe5a0dc78ca52e26", + "80b749ee1cbdf98e" + ], + "x": 14, + "y": 19, + "w": 752, + "h": 202 + }, + { + "id": "e25bae849bace7c6", + "type": "group", + "z": "e6d820ba0f4f184e", + "style": { + "stroke": "#999999", + "stroke-opacity": "1", + "fill": "none", + "fill-opacity": "1", + "label": true, + "label-position": "nw", + "color": "#a4a4a4" + }, + "nodes": [ + "655adc853a6956f6", + "fec9c2aca5437f85", + "c63068f9aa421f2c", + "5f77688682c97241", + "e49db28bb805280e" + ], + "x": 14, + "y": 239, + "w": 812, + "h": 202 + }, + { + "id": "034aca701dd2a256", + "type": "group", + "z": "e6d820ba0f4f184e", + "style": { + "stroke": "#999999", + "stroke-opacity": "1", + "fill": "none", + "fill-opacity": "1", + "label": true, + "label-position": "nw", + "color": "#a4a4a4" + }, + "nodes": [ + "4f9158d3ed8eadd3", + "287f7112bac18c51", + "0820acd0d3b628d4", + "327e3da2b7e9e50b", + "c80721a372673baf" + ], + "x": 14, + "y": 459, + "w": 932, + "h": 182 + }, + { + "id": "e5f0bc40b541e13f", + "type": "group", + "z": "e6d820ba0f4f184e", + "style": { + "stroke": "#999999", + "stroke-opacity": "1", + "fill": "none", + "fill-opacity": "1", + "label": true, + "label-position": "nw", + "color": "#a4a4a4" }, - "page-titlebar-backgroundColor": { - "value": "#5900ce", - "edited": false + "nodes": [ + "ec2b56583e1703f0", + "078727b344f7e34a", + "3e333d969e41b545" + ], + "x": 14, + "y": 659, + "w": 372, + "h": 142 + }, + { + "id": "329d175baf2183b1", + "type": "group", + "z": "e6d820ba0f4f184e", + "style": { + "stroke": "#999999", + "stroke-opacity": "1", + "fill": "none", + "fill-opacity": "1", + "label": true, + "label-position": "nw", + "color": "#a4a4a4" }, - "page-backgroundColor": { - "value": "#fafafa", - "edited": false + "nodes": [ + "3058d95dae6eea1f", + "355d4990f8958d65", + "a6c33b2025837058" + ], + "x": 14, + "y": 819, + "w": 372, + "h": 142 + }, + { + "id": "62930a749d014d90", + "type": "group", + "z": "e6d820ba0f4f184e", + "style": { + "stroke": "#999999", + "stroke-opacity": "1", + "fill": "none", + "fill-opacity": "1", + "label": true, + "label-position": "nw", + "color": "#a4a4a4" }, - "page-sidebar-backgroundColor": { - "value": "#333333", - "edited": false + "nodes": [ + "528f6ce911af8982", + "baac40d3e7a8fcb2", + "660f8462d674baea" + ], + "x": 414, + "y": 819, + "w": 352, + "h": 142 + }, + { + "id": "039d9046b10d6840", + "type": "group", + "z": "e6d820ba0f4f184e", + "style": { + "stroke": "#999999", + "stroke-opacity": "1", + "fill": "none", + "fill-opacity": "1", + "label": true, + "label-position": "nw", + "color": "#a4a4a4" }, - "group-textColor": { - "value": "#7e1bff", - "edited": false + "nodes": [ + "f941634f9acf1e65", + "8d538b74ac03964b", + "3cb007c279dceed0", + "05d4bd56d2b42da1", + "8972c5887d7de2dc", + "8283323ac1aca81a", + "13bdc8d028ab9063" + ], + "x": 934, + "y": 39, + "w": 352, + "h": 262 + }, + { + "id": "0a51b211ebbb9873", + "type": "group", + "z": "e6d820ba0f4f184e", + "style": { + "stroke": "#999999", + "stroke-opacity": "1", + "fill": "none", + "fill-opacity": "1", + "label": true, + "label-position": "nw", + "color": "#a4a4a4" }, - "group-borderColor": { - "value": "#ffffff", - "edited": false + "nodes": [ + "d5950445bf78c0c6", + "36a787c8463c3fab", + "d25760aa70fd1524" + ], + "x": 994, + "y": 499, + "w": 412, + "h": 142 + }, + { + "id": "ef4667eb73b18227", + "type": "group", + "z": "e6d820ba0f4f184e", + "style": { + "stroke": "#999999", + "stroke-opacity": "1", + "fill": "none", + "fill-opacity": "1", + "label": true, + "label-position": "nw", + "color": "#a4a4a4" }, - "group-backgroundColor": { - "value": "#ffffff", - "edited": false + "nodes": [ + "9203a825fc1c63e4", + "110801015a0ec067", + "9d2be08e52de0394" + ], + "x": 994, + "y": 659, + "w": 412, + "h": 142 + }, + { + "id": "693296edc15d81c2", + "type": "group", + "z": "e6d820ba0f4f184e", + "style": { + "stroke": "#999999", + "stroke-opacity": "1", + "fill": "none", + "fill-opacity": "1", + "label": true, + "label-position": "nw", + "color": "#a4a4a4" }, - "widget-textColor": { - "value": "#111111", - "edited": false + "nodes": [ + "3973105e8c349a38", + "b8a021de6b15a728", + "e2c7db0aef7f64ef", + "d96066c662742119" + ], + "x": 14, + "y": 979, + "w": 562, + "h": 162 + }, + { + "id": "68e954becfafbf7b", + "type": "group", + "z": "e6d820ba0f4f184e", + "style": { + "stroke": "#999999", + "stroke-opacity": "1", + "fill": "none", + "fill-opacity": "1", + "label": true, + "label-position": "nw", + "color": "#a4a4a4" }, - "widget-backgroundColor": { - "value": "#5900ce", - "edited": false + "nodes": [ + "47dce78fa2e5973b", + "9f0db647f3595ef9", + "e14a82906a749dc5" + ], + "x": 994, + "y": 819, + "w": 412, + "h": 142 + }, + { + "id": "e6ae26617c24c3ea", + "type": "ui-base", + "name": "PlanktoScope Dashboard", + "path": "/dashboard", + "appIcon": "", + "includeClientData": true, + "acceptsClientConfig": [ + "ui-notification", + "ui-control" + ], + "showPathInSidebar": true, + "headerContent": "dashboard", + "navigationStyle": "none", + "titleBarStyle": "fixed", + "showReconnectNotification": true, + "notificationDisplayTime": "1", + "showDisconnectNotification": true, + "allowInstall": true + }, + { + "id": "8dc3722c.06efa8", + "type": "mqtt-broker", + "name": "", + "broker": "0.0.0.0", + "port": "1883", + "clientid": "Client_node", + "autoConnect": true, + "usetls": false, + "compatmode": false, + "protocolVersion": 4, + "keepalive": "60", + "cleansession": true, + "autoUnsubscribe": true, + "birthTopic": "", + "birthQos": "0", + "birthPayload": "", + "closeTopic": "", + "closeQos": "0", + "closePayload": "", + "willTopic": "", + "willQos": "0", + "willPayload": "" + }, + { + "id": "81a80e32347ff991", + "type": "ui_base", + "theme": { + "name": "theme-light", + "lightTheme": { + "default": "#0094CE", + "baseColor": "#5900ce", + "baseFont": "-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif", + "edited": true, + "reset": false + }, + "darkTheme": { + "default": "#097479", + "baseColor": "#097479", + "baseFont": "-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif", + "edited": true, + "reset": false + }, + "customTheme": { + "name": "Untitled Theme 1", + "default": "#4B7930", + "baseColor": "#4B7930", + "baseFont": "-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif" + }, + "themeState": { + "base-color": { + "default": "#0094CE", + "value": "#0094CE", + "edited": true + }, + "page-titlebar-backgroundColor": { + "value": "#5900ce", + "edited": false + }, + "page-backgroundColor": { + "value": "#fafafa", + "edited": false + }, + "page-sidebar-backgroundColor": { + "value": "#333333", + "edited": false + }, + "group-textColor": { + "value": "#7e1bff", + "edited": false + }, + "group-borderColor": { + "value": "#ffffff", + "edited": false + }, + "group-backgroundColor": { + "value": "#ffffff", + "edited": false + }, + "widget-textColor": { + "value": "#111111", + "edited": false + }, + "widget-backgroundColor": { + "value": "#5900ce", + "edited": false + }, + "widget-borderColor": { + "value": "#ffffff", + "edited": false + }, + "base-font": { + "value": "-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif" + } + }, + "angularTheme": { + "primary": "indigo", + "accents": "blue", + "warn": "red", + "background": "grey", + "palette": "light" + } }, - "widget-borderColor": { - "value": "#ffffff", - "edited": false + "site": { + "name": "PlanktoScope", + "hideToolbar": "false", + "allowSwipe": "false", + "lockMenu": "false", + "allowTempTheme": "true", + "dateFormat": "Y-MM-DD", + "sizes": { + "sx": 55, + "sy": 55, + "gx": 4, + "gy": 4, + "cx": 4, + "cy": 4, + "px": 4, + "py": 4 + } + } + }, + { + "id": "f7770f0b818c3a67", + "type": "ui-theme", + "name": "PlanktoScope GUI 2", + "colors": { + "surface": "#ffffff", + "primary": "#1976d2", + "bgPage": "#eef3ff", + "groupBg": "#ffffff", + "groupOutline": "#cccccc" }, - "base-font": { - "value": "-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif" + "sizes": { + "density": "comfortable", + "pagePadding": "1.5rem", + "groupGap": "1.5rem", + "groupBorderRadius": "8px", + "widgetGap": "0px" } - }, - "angularTheme": { - "primary": "indigo", - "accents": "blue", - "warn": "red", - "background": "grey", - "palette": "light" - } - }, - "site": { - "name": "PlanktoScope", - "hideToolbar": "false", - "allowSwipe": "false", - "lockMenu": "false", - "allowTempTheme": "true", - "dateFormat": "Y-MM-DD", - "sizes": { - "sx": 55, - "sy": 55, - "gx": 4, - "gy": 4, - "cx": 4, - "cy": 4, - "px": 4, - "py": 4 - } - } - }, - { - "id": "f7770f0b818c3a67", - "type": "ui-theme", - "name": "PlanktoScope GUI 2", - "colors": { - "surface": "#ffffff", - "primary": "#1976d2", - "bgPage": "#eef3ff", - "groupBg": "#ffffff", - "groupOutline": "#cccccc" - }, - "sizes": { - "density": "comfortable", - "pagePadding": "1.5rem", - "groupGap": "1.5rem", - "groupBorderRadius": "8px", - "widgetGap": "0px" + }, + { + "id": "73070e06474249b4", + "type": "ui-page", + "name": "Metadata", + "ui": "e6ae26617c24c3ea", + "path": "/metadata", + "icon": "information-outline", + "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": 4, + "className": "", + "visible": "true", + "disabled": "false" + }, + { + "id": "dd015fda7456b9b0", + "type": "ui-group", + "name": "Navigation Top", + "page": "73070e06474249b4", + "width": "12", + "height": 1, + "order": 1, + "showTitle": false, + "className": "", + "visible": "true", + "disabled": "false", + "groupType": "default" + }, + { + "id": "69c772603e9cec0d", + "type": "ui-group", + "name": "Sample Information", + "page": "73070e06474249b4", + "width": "12", + "height": 1, + "order": 2, + "showTitle": true, + "className": "", + "visible": "true", + "disabled": "false", + "groupType": "default" + }, + { + "id": "6fab47af451c6d95", + "type": "ui-group", + "name": "Starting point", + "page": "73070e06474249b4", + "width": "6", + "height": 1, + "order": 3, + "showTitle": true, + "className": "", + "visible": "true", + "disabled": "false", + "groupType": "default" + }, + { + "id": "1cfe3062a8353012", + "type": "ui-group", + "name": "Ending point", + "page": "73070e06474249b4", + "width": "6", + "height": 1, + "order": 4, + "showTitle": true, + "className": "", + "visible": "true", + "disabled": "false", + "groupType": "default" + }, + { + "id": "cb3521392eb4e9ed", + "type": "ui-group", + "name": "Net Specificity", + "page": "73070e06474249b4", + "width": "12", + "height": 1, + "order": 5, + "showTitle": true, + "className": "", + "visible": true, + "disabled": false, + "groupType": "default" + }, + { + "id": "dd4d9707051041c9", + "type": "ui-group", + "name": "Other informations", + "page": "73070e06474249b4", + "width": "12", + "height": 1, + "order": 6, + "showTitle": true, + "className": "", + "visible": true, + "disabled": false, + "groupType": "default" + }, + { + "id": "7a4e042a60b734a6", + "type": "ui-page", + "name": "Segmentation", + "ui": "e6ae26617c24c3ea", + "path": "/segmentation", + "icon": "crop", + "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": 6, + "className": "", + "visible": "true", + "disabled": "false" + }, + { + "id": "bfd4acb7b243514f", + "type": "ui-group", + "name": "List of Acquisitions", + "page": "7a4e042a60b734a6", + "width": "12", + "height": 1, + "order": 3, + "showTitle": true, + "className": "", + "visible": "true", + "disabled": "false", + "groupType": "default" + }, + { + "id": "f22f627015431032", + "type": "ui-page", + "name": "Calibration - Lightness", + "ui": "e6ae26617c24c3ea", + "path": "/calibration_lightness", + "icon": "target-variant", + "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": 10, + "className": "", + "visible": "false", + "disabled": "false" + }, + { + "id": "728c7ed3d63dfcee", + "type": "ui-group", + "name": "Settings", + "page": "f22f627015431032", + "width": "12", + "height": "1", + "order": 1, + "showTitle": false, + "className": "", + "visible": "true", + "disabled": "false", + "groupType": "default" + }, + { + "id": "aa9ba6cedf56d2cd", + "type": "ui-page", + "name": "Calibration - Pixel size", + "ui": "e6ae26617c24c3ea", + "path": "/calibration_pixel_size", + "icon": "target-variant", + "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": 11, + "className": "", + "visible": "false", + "disabled": "false" + }, + { + "id": "c966455a52d121c0", + "type": "ui-group", + "name": "Settings", + "page": "aa9ba6cedf56d2cd", + "width": "12", + "height": "1", + "order": 1, + "showTitle": false, + "className": "", + "visible": "true", + "disabled": "false", + "groupType": "default" + }, + { + "id": "48216bc1f7c53a75", + "type": "ui-page", + "name": "Calibration - Pump", + "ui": "e6ae26617c24c3ea", + "path": "/calibration_pump", + "icon": "target-variant", + "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": 12, + "className": "", + "visible": "false", + "disabled": "false" + }, + { + "id": "6039d653304537af", + "type": "ui-group", + "name": "Settings", + "page": "48216bc1f7c53a75", + "width": "12", + "height": "1", + "order": 1, + "showTitle": false, + "className": "", + "visible": "true", + "disabled": "false", + "groupType": "default" + }, + { + "id": "803aa402d5c66b73", + "type": "ui-page", + "name": "Calibration - Saturation Level", + "ui": "e6ae26617c24c3ea", + "path": "/calibration_saturation_level", + "icon": "target-variant", + "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": 9, + "className": "", + "visible": "false", + "disabled": "false" + }, + { + "id": "af8acdfe9afbad74", + "type": "ui-group", + "name": "Settings", + "page": "803aa402d5c66b73", + "width": "12", + "height": "1", + "order": 1, + "showTitle": false, + "className": "", + "visible": "true", + "disabled": "false", + "groupType": "default" + }, + { + "id": "f8dd620721c6d70b", + "type": "ui-page", + "name": "Calibration", + "ui": "e6ae26617c24c3ea", + "path": "/calibration", + "icon": "mdi-tune", + "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": "c29c835a70533b69", + "type": "ui-group", + "name": "header", + "page": "f8dd620721c6d70b", + "width": "12", + "height": 1, + "order": 1, + "showTitle": false, + "className": "", + "visible": "true", + "disabled": "false", + "groupType": "default" + }, + { + "id": "822cdc5b6ef13f39", + "type": "ui-group", + "name": "body", + "page": "f8dd620721c6d70b", + "width": "12", + "height": 1, + "order": 2, + "showTitle": false, + "className": "", + "visible": "true", + "disabled": "false", + "groupType": "default" + }, + { + "id": "bcc78241b99ba27f", + "type": "ui-page", + "name": "Preview", + "ui": "e6ae26617c24c3ea", + "path": "/preview", + "icon": "mdi-eye", + "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": 3, + "className": "", + "visible": true, + "disabled": false + }, + { + "id": "58ab4d5e3dd68192", + "type": "ui-group", + "name": "Streaming", + "page": "bcc78241b99ba27f", + "width": "6", + "height": 1, + "order": 2, + "showTitle": false, + "className": "", + "visible": "true", + "disabled": "false", + "groupType": "default" + }, + { + "id": "39cbd2658f16d608", + "type": "ui-group", + "name": "Settings", + "page": "bcc78241b99ba27f", + "width": "6", + "height": 1, + "order": 3, + "showTitle": false, + "className": "", + "visible": "true", + "disabled": "false", + "groupType": "default" + }, + { + "id": "5c3e73c675caac42", + "type": "ui-page", + "name": "Acquisition", + "ui": "e6ae26617c24c3ea", + "path": "/acquisition", + "icon": "video-image", + "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": 5, + "className": "", + "visible": "true", + "disabled": "false" + }, + { + "id": "b274327af3807b79", + "type": "ui-group", + "name": "Streaming", + "page": "5c3e73c675caac42", + "width": "4", + "height": 1, + "order": 2, + "showTitle": false, + "className": "", + "visible": "true", + "disabled": "false", + "groupType": "default" + }, + { + "id": "d2f77573ed4317e4", + "type": "ui-group", + "name": "Acquisition settings", + "page": "5c3e73c675caac42", + "width": "8", + "height": 1, + "order": 3, + "showTitle": false, + "className": "", + "visible": "true", + "disabled": "false", + "groupType": "default" + }, + { + "id": "632260133d581caa", + "type": "ui-page", + "name": "Home", + "ui": "e6ae26617c24c3ea", + "path": "/home", + "icon": "home", + "layout": "grid", + "theme": "f7770f0b818c3a67", + "breakpoints": [ + { + "name": "Default", + "px": "0", + "cols": "3" + }, + { + "name": "Tablet", + "px": "576", + "cols": "3" + }, + { + "name": "Small Desktop", + "px": "768", + "cols": "12" + }, + { + "name": "Desktop", + "px": "1024", + "cols": "12" + } + ], + "order": 1, + "className": "", + "visible": "true", + "disabled": "false" + }, + { + "id": "5d39a98563150f22", + "type": "ui-group", + "name": "body", + "page": "632260133d581caa", + "width": "12", + "height": 1, + "order": 1, + "showTitle": false, + "className": "", + "visible": "true", + "disabled": "false", + "groupType": "default" + }, + { + "id": "7572915171e440cd", + "type": "ui-group", + "name": "Navigation Top", + "page": "bcc78241b99ba27f", + "width": "12", + "height": 1, + "order": 1, + "showTitle": false, + "className": "", + "visible": "true", + "disabled": "false", + "groupType": "default" + }, + { + "id": "3ae252a2e5abca89", + "type": "ui-group", + "name": "Navigation Top", + "page": "5c3e73c675caac42", + "width": "12", + "height": 1, + "order": 1, + "showTitle": false, + "className": "", + "visible": "true", + "disabled": "false", + "groupType": "default" + }, + { + "id": "9b992ca6515ed058", + "type": "ui-group", + "name": "Navigation Top", + "page": "7a4e042a60b734a6", + "width": "12", + "height": 1, + "order": 1, + "showTitle": false, + "className": "", + "visible": "true", + "disabled": "false", + "groupType": "default" + }, + { + "id": "d129fac8e7742d5b", + "type": "ui-page", + "name": "Visualization", + "ui": "e6ae26617c24c3ea", + "path": "/visualization", + "icon": "chart-scatter-plot", + "layout": "grid", + "theme": "f7770f0b818c3a67", + "breakpoints": [ + { + "name": "Default", + "px": "0", + "cols": "4" + }, + { + "name": "Tablet", + "px": "576", + "cols": "4" + }, + { + "name": "Small Desktop", + "px": "768", + "cols": "12" + }, + { + "name": "Desktop", + "px": "1024", + "cols": "12" + } + ], + "order": 8, + "className": "", + "visible": "true", + "disabled": "false" + }, + { + "id": "b570f76ef526af45", + "type": "ui-group", + "name": "Navigation Top", + "page": "d129fac8e7742d5b", + "width": "12", + "height": 1, + "order": 1, + "showTitle": false, + "className": "", + "visible": "true", + "disabled": "false", + "groupType": "default" + }, + { + "id": "402b3d24c87ab0d2", + "type": "ui-group", + "name": "Informations", + "page": "7a4e042a60b734a6", + "width": "12", + "height": 1, + "order": 2, + "showTitle": true, + "className": "", + "visible": "true", + "disabled": "false", + "groupType": "default" + }, + { + "id": "fa6393a7d7e3b7d7", + "type": "ui-group", + "name": "List of Segmentation", + "page": "d129fac8e7742d5b", + "width": "12", + "height": 1, + "order": 2, + "showTitle": true, + "className": "", + "visible": "true", + "disabled": "false", + "groupType": "default" + }, + { + "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": 2, + "className": "", + "visible": "true", + "disabled": "false" + }, + { + "id": "de680effc3e27451", + "type": "ui-group", + "name": "body", + "page": "34112984a39c35bd", + "width": "12", + "height": 1, + "order": 1, + "showTitle": false, + "className": "", + "visible": "true", + "disabled": "false", + "groupType": "default" + }, + { + "id": "c5651e1a3e56f3f5", + "type": "ui-group", + "name": "Explorer", + "page": "d129fac8e7742d5b", + "width": "12", + "height": 1, + "order": 13, + "showTitle": true, + "className": "", + "visible": "true", + "disabled": "false", + "groupType": "default" + }, + { + "id": "0c1537ce71affc2b", + "type": "ui-group", + "name": "Gallery", + "page": "d129fac8e7742d5b", + "width": "12", + "height": 1, + "order": 14, + "showTitle": false, + "className": "", + "visible": "true", + "disabled": "false", + "groupType": "default" + }, + { + "id": "36931a9722892790", + "type": "ui-group", + "name": "Software Version", + "page": "632260133d581caa", + "width": "3", + "height": 1, + "order": 6, + "showTitle": false, + "className": "", + "visible": "true", + "disabled": "false", + "groupType": "default" + }, + { + "id": "2aa235120084abe4", + "type": "ui-group", + "name": "Images Acquired", + "page": "632260133d581caa", + "width": "3", + "height": 1, + "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": "dc304678d9a6b53b", + "type": "ui-group", + "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": false, + "className": "", + "visible": "true", + "disabled": "false", + "groupType": "default" + }, + { + "id": "82480e386ed6f8bd", + "type": "ui-group", + "name": "Explore your data", + "page": "632260133d581caa", + "width": "4", + "height": 1, + "order": 3, + "showTitle": false, + "className": "", + "visible": "true", + "disabled": "false", + "groupType": "default" + }, + { + "id": "9d3e8bdd535f0e0d", + "type": "ui-group", + "name": "Run the Calibration", + "page": "632260133d581caa", + "width": "4", + "height": 1, + "order": 4, + "showTitle": false, + "className": "", + "visible": "true", + "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": "0f16258953fae292", + "type": "file in", + "z": "b7861ce703215a01", + "name": "", + "filename": "/home/pi/PlanktoScope/hardware.json", + "filenameType": "str", + "format": "utf8", + "chunk": false, + "sendError": false, + "encoding": "none", + "allProps": false, + "x": 250, + "y": 40, + "wires": [ + [ + "81c516291ab19acd" + ] + ], + "info": "# PlanktoScope Help\nThis Node will read the content of the file named **config.txt** containing all the input placeholders.\n" + }, + { + "id": "81c516291ab19acd", + "type": "json", + "z": "b7861ce703215a01", + "name": "Parse JSON", + "property": "payload", + "action": "", + "pretty": false, + "x": 510, + "y": 40, + "wires": [ + [ + "d0fbcd200cd09981" + ] + ] + }, + { + "id": "d0fbcd200cd09981", + "type": "change", + "z": "b7861ce703215a01", + "name": "", + "rules": [ + { + "t": "set", + "p": "hardware_conf", + "pt": "global", + "to": "payload", + "tot": "msg" + } + ], + "action": "", + "property": "", + "from": "", + "to": "", + "reg": false, + "x": 730, + "y": 40, + "wires": [ + [] + ] + }, + { + "id": "730b2780ac215a52", + "type": "file in", + "z": "63c85b96537b7355", + "name": "", + "filename": "/home/pi/PlanktoScope/config.json", + "filenameType": "str", + "format": "utf8", + "chunk": false, + "sendError": false, + "encoding": "none", + "x": 560, + "y": 60, + "wires": [ + [ + "e0b7238a0c5d4ed0" + ] + ], + "info": "# PlanktoScope Help\nThis Node will read the content of the file named **config.txt** containing all the input placeholders.\n" + }, + { + "id": "e0b7238a0c5d4ed0", + "type": "json", + "z": "63c85b96537b7355", + "name": "config.json", + "property": "payload", + "action": "", + "pretty": false, + "x": 730, + "y": 60, + "wires": [ + [ + "f1c0a882a9e3d927" + ] + ] + }, + { + "id": "b8109badf5f39d35", + "type": "file", + "z": "63c85b96537b7355", + "name": "", + "filename": "/home/pi/PlanktoScope/config.json", + "appendNewline": true, + "createDir": true, + "overwriteFile": "true", + "encoding": "none", + "x": 990, + "y": 160, + "wires": [ + [] + ] + }, + { + "id": "31ae9b857627673c", + "type": "json", + "z": "63c85b96537b7355", + "name": "config.json", + "property": "payload", + "action": "str", + "pretty": true, + "x": 730, + "y": 160, + "wires": [ + [ + "b8109badf5f39d35" + ] + ] + }, + { + "id": "f1c0a882a9e3d927", + "type": "function", + "z": "63c85b96537b7355", + "name": "Global Set", + "func": "global.set(\"config_keys\", Object.keys(msg.payload));\n\nfor (const key in msg.payload) {\n global.set(key, msg.payload[key]);\n}\n\nreturn msg;", + "outputs": 1, + "timeout": "", + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 910, + "y": 60, + "wires": [ + [] + ] + }, + { + "id": "24ea99bb02eeffa2", + "type": "inject", + "z": "63c85b96537b7355", + "name": "Load config", + "props": [ + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": true, + "onceDelay": 0.1, + "topic": "", + "payloadType": "str", + "x": 230, + "y": 60, + "wires": [ + [ + "730b2780ac215a52" + ] + ] + }, + { + "id": "5248e5e225d854d1", + "type": "function", + "z": "63c85b96537b7355", + "name": "get config payload", + "func": "keys = global.get(\"config_keys\")\n\nvar payload = {}\n\nkeys.forEach(function(item, index, array) {\n payload[item] = global.get(item);\n})\n\nreturn {\"payload\": payload};", + "outputs": 1, + "timeout": "", + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 410, + "y": 160, + "wires": [ + [ + "31ae9b857627673c" + ] + ] + }, + { + "id": "97f8a94e71055782", + "type": "ui_ui_control", + "z": "63c85b96537b7355", + "name": "Connect Event", + "events": "connect", + "x": 220, + "y": 100, + "wires": [ + [ + "730b2780ac215a52" + ] + ] + }, + { + "id": "852522a716e4156e", + "type": "switch", + "z": "b2bf18a0f40a5d72", + "name": "msg.payload.page.path === \"/setup\"", + "property": "payload.page.path", + "propertyType": "msg", + "rules": [ + { + "t": "eq", + "v": "/setup", + "vt": "str" + } + ], + "checkall": "true", + "repair": false, + "outputs": 1, + "x": 310, + "y": 40, + "wires": [ + [ + "7af8cd00e8938727" + ] + ] + }, + { + "id": "49857517dd9e15f7", + "type": "ui-event", + "z": "b2bf18a0f40a5d72", + "ui": "e6ae26617c24c3ea", + "name": "UI Event", + "x": 80, + "y": 40, + "wires": [ + [ + "852522a716e4156e" + ] + ] + }, + { + "id": "fc5a4760cc3ea824", + "type": "ui-template", + "z": "b2bf18a0f40a5d72", + "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": 310, + "y": 180, + "wires": [ + [ + "c36ed08fac3fccc0", + "0b71d7e104148283" + ] + ] + }, + { + "id": "cf5275898273cd61", + "type": "ui-template", + "z": "b2bf18a0f40a5d72", + "group": "", + "page": "34112984a39c35bd", + "ui": "", + "name": "CSS (All Pages)", + "order": 0, + "width": 0, + "height": 0, + "head": "", + "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, + "templateScope": "page:style", + "className": "", + "x": 940, + "y": 60, + "wires": [ + [] + ] + }, + { + "id": "c36ed08fac3fccc0", + "type": "function", + "z": "b2bf18a0f40a5d72", + "name": "set general settings", + "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": 710, + "y": 140, + "wires": [ + [] + ] + }, + { + "id": "7af8cd00e8938727", + "type": "read setup", + "z": "b2bf18a0f40a5d72", + "name": "", + "x": 150, + "y": 180, + "wires": [ + [ + "fc5a4760cc3ea824" + ] + ] + }, + { + "id": "6c2762dad373862b", + "type": "write setup", + "z": "b2bf18a0f40a5d72", + "name": "", + "x": 670, + "y": 240, + "wires": [ + [] + ] + }, + { + "id": "0b71d7e104148283", + "type": "switch", + "z": "b2bf18a0f40a5d72", + "name": "", + "property": "topic", + "propertyType": "msg", + "rules": [ + { + "t": "eq", + "v": "setup/saved", + "vt": "str" + } + ], + "checkall": "true", + "repair": false, + "outputs": 1, + "x": 470, + "y": 240, + "wires": [ + [ + "6c2762dad373862b" + ] + ] + }, + { + "id": "b3754a3e5ae5e0ed", + "type": "mqtt in", + "z": "b2bf18a0f40a5d72", + "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": [ + [ + "fc5a4760cc3ea824" + ] + ] + }, + { + "id": "d525e948816e36b6", + "type": "ui-template", + "z": "f90406ba2da5932f", + "group": "", + "page": "", + "ui": "e6ae26617c24c3ea", + "name": "footer", + "order": 1, + "width": 0, + "height": 0, + "head": "", + "format": "\n\n\n", + "storeOutMessages": true, + "passthru": true, + "resendOnRefresh": true, + "templateScope": "widget:ui", + "className": "", + "x": 510, + "y": 700, + "wires": [ + [] + ] + }, + { + "id": "2a99cafa2c36383b", + "type": "ui-event", + "z": "f90406ba2da5932f", + "ui": "e6ae26617c24c3ea", + "name": "UI Event", + "x": 80, + "y": 40, + "wires": [ + [ + "9df0c4431f8b8f31", + "ba7e7f61401c14d5", + "9dc57d3fe40c1d43" + ] + ] + }, + { + "id": "d6311499f71b7440", + "type": "ui-template", + "z": "f90406ba2da5932f", + "group": "", + "page": "", + "ui": "e6ae26617c24c3ea", + "name": "CSS (All Pages)", + "order": 0, + "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.v-row {\n margin: 0;\n}", + "storeOutMessages": true, + "passthru": true, + "resendOnRefresh": true, + "templateScope": "site:style", + "className": "", + "x": 1300, + "y": 40, + "wires": [ + [] + ] + }, + { + "id": "ba7e7f61401c14d5", + "type": "get machine info", + "z": "f90406ba2da5932f", + "name": "", + "x": 850, + "y": 40, + "wires": [ + [ + "362b4428729ebcac" + ] + ] + }, + { + "id": "9dc57d3fe40c1d43", + "type": "storage info", + "z": "f90406ba2da5932f", + "name": "", + "x": 830, + "y": 80, + "wires": [ + [ + "751e338fd9e7b365" + ] + ] + }, + { + "id": "751e338fd9e7b365", + "type": "function", + "z": "f90406ba2da5932f", + "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": "362b4428729ebcac", + "type": "function", + "z": "f90406ba2da5932f", + "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": "9df0c4431f8b8f31", + "type": "delay", + "z": "f90406ba2da5932f", + "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": 140, + "y": 180, + "wires": [ + [ + "7326fb7c646235e5" + ] + ] + }, + { + "id": "7326fb7c646235e5", + "type": "function", + "z": "f90406ba2da5932f", + "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": 320, + "y": 180, + "wires": [ + [ + "17d6595dfa9d0cee", + "f4c3442da54320e3", + "d0eacab77771ef43", + "ed7e8648519251ad", + "bfd0d1b9f4c7141c" + ] + ] + }, + { + "id": "825ecb1183edc16b", + "type": "poweroff", + "z": "f90406ba2da5932f", + "name": "", + "x": 1160, + "y": 240, + "wires": [ + [] + ] + }, + { + "id": "f0a002fc31fbe9f9", + "type": "reboot", + "z": "f90406ba2da5932f", + "name": "", + "x": 1160, + "y": 200, + "wires": [ + [] + ] + }, + { + "id": "9cbcd71938e6162d", + "type": "switch", + "z": "f90406ba2da5932f", + "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": [ + [ + "f0a002fc31fbe9f9" + ], + [ + "825ecb1183edc16b" + ] + ] + }, + { + "id": "d5cd40c81ac86e7a", + "type": "ui-template", + "z": "f90406ba2da5932f", + "group": "5d39a98563150f22", + "page": "", + "ui": "", + "name": "empty-state", + "order": 1, + "width": "0", + "height": "0", + "head": "", + "format": "\n", + "storeOutMessages": true, + "passthru": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 530, + "y": 280, + "wires": [ + [] + ] + }, + { + "id": "17d6595dfa9d0cee", + "type": "ui-template", + "z": "f90406ba2da5932f", + "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": 800, + "y": 200, + "wires": [ + [ + "9cbcd71938e6162d" + ] + ] + }, + { + "id": "f4c3442da54320e3", + "type": "ui-template", + "z": "f90406ba2da5932f", + "group": "36931a9722892790", + "page": "", + "ui": "", + "name": "Software Version", + "order": 1, + "width": "0", + "height": "0", + "head": "", + "format": "", + "storeOutMessages": true, + "passthru": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 550, + "y": 480, + "wires": [ + [] + ] + }, + { + "id": "d0eacab77771ef43", + "type": "ui-template", + "z": "f90406ba2da5932f", + "group": "2aa235120084abe4", + "page": "", + "ui": "", + "name": "Images Acquired", + "order": 1, + "width": "0", + "height": "0", + "head": "", + "format": "", + "storeOutMessages": true, + "passthru": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 550, + "y": 520, + "wires": [ + [] + ] + }, + { + "id": "ed7e8648519251ad", + "type": "ui-template", + "z": "f90406ba2da5932f", + "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": "bfd0d1b9f4c7141c", + "type": "ui-template", + "z": "f90406ba2da5932f", + "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": "b8d1e9bc6daa3cd0", + "type": "ui-template", + "z": "f90406ba2da5932f", + "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": "b298c238c9fb0b58", + "type": "ui-template", + "z": "f90406ba2da5932f", + "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": "e7f40c244808dbd4", + "type": "ui-template", + "z": "f90406ba2da5932f", + "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": "4369716b2a45c769", + "type": "ui-template", + "z": "f90406ba2da5932f", + "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": "a3a7ac5089d5d8e6", + "type": "ui-template", + "z": "f90406ba2da5932f", + "group": "", + "page": "632260133d581caa", + "ui": "", + "name": "CSS (This page)", + "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": "5fedc522b345193c", + "type": "ui-template", + "z": "6d6a011bf1913637", + "group": "58ab4d5e3dd68192", + "page": "", + "ui": "", + "name": "Streaming", + "order": 1, + "width": "0", + "height": "0", + "head": "", + "format": "\n\n", + "storeOutMessages": true, + "passthru": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 1310, + "y": 120, + "wires": [ + [] + ] + }, + { + "id": "8452566c16d26f45", + "type": "function", + "z": "6d6a011bf1913637", + "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": [ + [ + "db1ef284071e60a1" + ] + ] + }, + { + "id": "67e05ec4078fd100", + "type": "ui-event", + "z": "6d6a011bf1913637", + "ui": "e6ae26617c24c3ea", + "name": "UI Event", + "x": 80, + "y": 40, + "wires": [ + [ + "b1e37de2e8a49f29" + ] + ] + }, + { + "id": "b1e37de2e8a49f29", + "type": "switch", + "z": "6d6a011bf1913637", + "name": "msg.payload.page.path === \"/preview\"", + "property": "payload.page.path", + "propertyType": "msg", + "rules": [ + { + "t": "eq", + "v": "/preview", + "vt": "str" + } + ], + "checkall": "true", + "repair": false, + "outputs": 1, + "x": 320, + "y": 40, + "wires": [ + [ + "8452566c16d26f45" + ] + ] + }, + { + "id": "d119a802cf4c3f2d", + "type": "function", + "z": "6d6a011bf1913637", + "name": "set pump settings", + "func": "if (msg.topic) {\n global.set(\"pump_flowrate\", msg.payload.pump_flowrate);\n global.set(\"pump_manual_volume\", msg.payload.pump_manual_volume);\n}\nreturn msg;", + "outputs": 1, + "timeout": "", + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 790, + "y": 80, + "wires": [ + [] + ] + }, + { + "id": "8dfac8c9adafc76f", + "type": "function", + "z": "6d6a011bf1913637", + "name": "set focus settings", + "func": "if (msg.topic) {\n global.set(\"focus_distance\", msg.payload.focus_distance);\n\n}\nreturn msg;", + "outputs": 1, + "timeout": "", + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 790, + "y": 180, + "wires": [ + [] + ] + }, + { + "id": "24077d432cd6db62", + "type": "switch", + "z": "6d6a011bf1913637", + "name": "", + "property": "topic", + "propertyType": "msg", + "rules": [ + { + "t": "eq", + "v": "set_global/pump", + "vt": "str" + }, + { + "t": "eq", + "v": "actuator/pump", + "vt": "str" + }, + { + "t": "eq", + "v": "set_global/focus", + "vt": "str" + }, + { + "t": "eq", + "v": "actuator/focus", + "vt": "str" + }, + { + "t": "eq", + "v": "set_global/light", + "vt": "str" + }, + { + "t": "eq", + "v": "light", + "vt": "str" + }, + { + "t": "eq", + "v": "set_global/bubbler", + "vt": "str" + }, + { + "t": "eq", + "v": "actuator/bubbler", + "vt": "str" + }, + { + "t": "eq", + "v": "imager/image", + "vt": "str" + } + ], + "checkall": "true", + "repair": false, + "outputs": 9, + "x": 550, + "y": 220, + "wires": [ + [ + "d119a802cf4c3f2d" + ], + [ + "7acd371a3e8d58c7" + ], + [ + "8dfac8c9adafc76f" + ], + [ + "ddbbc347dd314885" + ], + [ + "2655836d55c8c00e" + ], + [ + "f4e92faafd5a967d" + ], + [ + "85505c259d234540" + ], + [ + "69f0e0fd86f25833", + "8eecfc6543bf944c" + ], + [ + "a56a8820702f4610" + ] + ] + }, + { + "id": "db1ef284071e60a1", + "type": "ui-template", + "z": "6d6a011bf1913637", + "group": "39cbd2658f16d608", + "page": "", + "ui": "", + "name": "body", + "order": 1, + "width": "0", + "height": "0", + "head": "", + "format": "\n\n\n\n", + "storeOutMessages": true, + "passthru": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 370, + "y": 220, + "wires": [ + [ + "24077d432cd6db62" + ] + ] + }, + { + "id": "27a2b425848bce6f", + "type": "mqtt in", + "z": "6d6a011bf1913637", + "name": "", + "topic": "status/pump", + "qos": "0", + "datatype": "json", + "broker": "8dc3722c.06efa8", + "nl": false, + "rap": false, + "inputs": 0, + "x": 90, + "y": 200, + "wires": [ + [ + "db1ef284071e60a1" + ] + ] + }, + { + "id": "d53b5bacd6d4c3ac", + "type": "mqtt in", + "z": "6d6a011bf1913637", + "name": "", + "topic": "status/focus", + "qos": "0", + "datatype": "json", + "broker": "8dc3722c.06efa8", + "nl": false, + "rap": false, + "inputs": 0, + "x": 90, + "y": 260, + "wires": [ + [ + "db1ef284071e60a1" + ] + ] + }, + { + "id": "2655836d55c8c00e", + "type": "function", + "z": "6d6a011bf1913637", + "name": "set light settings", + "func": "// On vérifie si le message contient les propriétés attendues\n// avant de mettre à jour les variables globales.\n\nif (msg.payload) {\n \n // Si led_status est présent dans le payload, on stocke\n if (msg.payload.led_status !== undefined) {\n global.set(\"led_status\", msg.payload.led_status);\n }\n\n // Si calibration_led_intensity est présent dans le payload, on stocke\n if (msg.payload.calibration_led_intensity !== undefined) {\n global.set(\"calibration_led_intensity\", msg.payload.calibration_led_intensity);\n }\n}\n\nreturn msg;", + "outputs": 1, + "timeout": "", + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 780, + "y": 280, + "wires": [ + [] + ] + }, + { + "id": "20547adfe07c39d4", + "type": "mqtt in", + "z": "6d6a011bf1913637", + "name": "", + "topic": "status/light", + "qos": "0", + "datatype": "json", + "broker": "8dc3722c.06efa8", + "nl": false, + "rap": false, + "inputs": 0, + "x": 80, + "y": 320, + "wires": [ + [ + "db1ef284071e60a1" + ] + ] + }, + { + "id": "364762649f9df51a", + "type": "mqtt in", + "z": "6d6a011bf1913637", + "name": "", + "topic": "status/imager", + "qos": "0", + "datatype": "json", + "broker": "8dc3722c.06efa8", + "nl": false, + "rap": false, + "inputs": 0, + "x": 90, + "y": 380, + "wires": [ + [ + "db1ef284071e60a1" + ] + ] + }, + { + "id": "85505c259d234540", + "type": "function", + "z": "6d6a011bf1913637", + "name": "set bubbler settings", + "func": "if (msg.topic) {\n global.set(\"bubbler_status\", msg.payload.action);\n\n}\nreturn msg;", + "outputs": 1, + "timeout": "", + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 800, + "y": 380, + "wires": [ + [] + ] + }, + { + "id": "f49d224aa87cdc35", + "type": "mqtt in", + "z": "6d6a011bf1913637", + "name": "", + "topic": "status/bubbler", + "qos": "0", + "datatype": "json", + "broker": "8dc3722c.06efa8", + "nl": false, + "rap": false, + "inputs": 0, + "x": 100, + "y": 380, + "wires": [ + [ + "db1ef284071e60a1" + ] + ] + }, + { + "id": "daae7bb94e6bd36d", + "type": "ui-template", + "z": "6d6a011bf1913637", + "group": "7572915171e440cd", + "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": 80, + "wires": [ + [] + ] + }, + { + "id": "7acd371a3e8d58c7", + "type": "mqtt out", + "z": "6d6a011bf1913637", + "name": "MQTT - pump", + "topic": "", + "qos": "", + "retain": "", + "respTopic": "", + "contentType": "", + "userProps": "", + "correl": "", + "expiry": "", + "broker": "8dc3722c.06efa8", + "x": 780, + "y": 120, + "wires": [] + }, + { + "id": "ddbbc347dd314885", + "type": "mqtt out", + "z": "6d6a011bf1913637", + "name": "MQTT - focus", + "topic": "", + "qos": "", + "retain": "", + "respTopic": "", + "contentType": "", + "userProps": "", + "correl": "", + "expiry": "", + "broker": "8dc3722c.06efa8", + "x": 780, + "y": 220, + "wires": [] + }, + { + "id": "f4e92faafd5a967d", + "type": "mqtt out", + "z": "6d6a011bf1913637", + "name": "MQTT - light", + "topic": "", + "qos": "", + "retain": "", + "respTopic": "", + "contentType": "", + "userProps": "", + "correl": "", + "expiry": "", + "broker": "8dc3722c.06efa8", + "x": 770, + "y": 320, + "wires": [] + }, + { + "id": "a56a8820702f4610", + "type": "mqtt out", + "z": "6d6a011bf1913637", + "name": "MQTT - Imager", + "topic": "", + "qos": "", + "retain": "", + "respTopic": "", + "contentType": "", + "userProps": "", + "correl": "", + "expiry": "", + "broker": "8dc3722c.06efa8", + "x": 780, + "y": 480, + "wires": [] + }, + { + "id": "8eecfc6543bf944c", + "type": "debug", + "z": "6d6a011bf1913637", + "name": "debug 1", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "true", + "targetType": "full", + "statusVal": "", + "statusType": "auto", + "x": 480, + "y": 560, + "wires": [] + }, + { + "id": "69f0e0fd86f25833", + "type": "mqtt out", + "z": "6d6a011bf1913637", + "name": "MQTT - bubbler", + "topic": "", + "qos": "", + "retain": "", + "respTopic": "", + "contentType": "", + "userProps": "", + "correl": "", + "expiry": "", + "broker": "8dc3722c.06efa8", + "x": 780, + "y": 420, + "wires": [] + }, + { + "id": "29b0ee7d77b1b373", + "type": "ui-template", + "z": "f7ff9c0e54da4b8e", + "group": "dd015fda7456b9b0", + "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": "4893b2624925b10a", + "type": "ui-template", + "z": "f7ff9c0e54da4b8e", + "group": "69c772603e9cec0d", + "page": "", + "ui": "", + "name": "Sample Information", + "order": 1, + "width": 0, + "height": 0, + "head": "", + "format": "\n\n\n\n", + "storeOutMessages": true, + "passthru": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 410, + "y": 140, + "wires": [ + [ + "23f16f4437be7a5f", + "38f795adc7ce566d" + ] + ] + }, + { + "id": "e7f23192557851e0", + "type": "ui-template", + "z": "f7ff9c0e54da4b8e", + "group": "6fab47af451c6d95", + "page": "", + "ui": "", + "name": "Starting point", + "order": 1, + "width": 0, + "height": 0, + "head": "", + "format": "\n\n\n", + "storeOutMessages": true, + "passthru": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 400, + "y": 200, + "wires": [ + [ + "943a452f8c2720fe" + ] + ] + }, + { + "id": "ba461390e1455794", + "type": "ui-template", + "z": "f7ff9c0e54da4b8e", + "group": "1cfe3062a8353012", + "page": "", + "ui": "", + "name": "Ending point", + "order": 1, + "width": 0, + "height": 0, + "head": "", + "format": "\n\n", + "storeOutMessages": true, + "passthru": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 390, + "y": 300, + "wires": [ + [ + "8f1721c5616582cc" + ] + ] + }, + { + "id": "9ddd0fbe2670f626", + "type": "ui-template", + "z": "f7ff9c0e54da4b8e", + "group": "cb3521392eb4e9ed", + "page": "", + "ui": "", + "name": "Net Specificity", + "order": 1, + "width": 0, + "height": 0, + "head": "", + "format": "\n\n\n\n", + "storeOutMessages": true, + "passthru": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 400, + "y": 400, + "wires": [ + [ + "ad434c2ca4b52cf4" + ] + ] + }, + { + "id": "e4d9d690aa12d09c", + "type": "ui-template", + "z": "f7ff9c0e54da4b8e", + "group": "dd4d9707051041c9", + "page": "", + "ui": "", + "name": "Other Informations", + "order": 1, + "width": 0, + "height": 0, + "head": "", + "format": "\n\n\n\n", + "storeOutMessages": true, + "passthru": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 410, + "y": 460, + "wires": [ + [ + "b993c6289d67f56d" + ] + ] + }, + { + "id": "990f1d12e47028f3", + "type": "function", + "z": "f7ff9c0e54da4b8e", + "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": [ + [ + "4893b2624925b10a", + "e7f23192557851e0", + "ba461390e1455794", + "9ddd0fbe2670f626", + "e4d9d690aa12d09c" + ] + ] + }, + { + "id": "7eb3f9c16325dd59", + "type": "ui-event", + "z": "f7ff9c0e54da4b8e", + "ui": "e6ae26617c24c3ea", + "name": "UI Event", + "x": 80, + "y": 40, + "wires": [ + [ + "b729150cff31a0ff" + ] + ] + }, + { + "id": "b729150cff31a0ff", + "type": "switch", + "z": "f7ff9c0e54da4b8e", + "name": "msg.payload.page.path === \"/metadata\"", + "property": "payload.page.path", + "propertyType": "msg", + "rules": [ + { + "t": "eq", + "v": "/metadata", + "vt": "str" + } + ], + "checkall": "true", + "repair": false, + "outputs": 1, + "x": 320, + "y": 40, + "wires": [ + [ + "990f1d12e47028f3" + ] + ] + }, + { + "id": "943a452f8c2720fe", + "type": "switch", + "z": "f7ff9c0e54da4b8e", + "name": "", + "property": "topic", + "propertyType": "msg", + "rules": [ + { + "t": "eq", + "v": "object_datetime", + "vt": "str" + }, + { + "t": "eq", + "v": "object_latlon", + "vt": "str" + } + ], + "checkall": "true", + "repair": false, + "outputs": 2, + "x": 570, + "y": 200, + "wires": [ + [ + "e01c2d986929b8ed" + ], + [ + "ddd635a300595387" + ] + ] + }, + { + "id": "ddd635a300595387", + "type": "function", + "z": "f7ff9c0e54da4b8e", + "name": "set object_latlon", + "func": "if (msg.topic) {\n global.set(\"object_lat\", msg.payload.object_lat);\n global.set(\"object_lon\", msg.payload.object_lon);\n if (msg.payload.object_date !== undefined) {\n global.set(\"object_date\", msg.payload.object_date);\n }\n if (msg.payload.object_time !== undefined) {\n global.set(\"object_time\", msg.payload.object_time);\n }\n}\nreturn msg;", + "outputs": 1, + "timeout": "", + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 960, + "y": 240, + "wires": [ + [] + ] + }, + { + "id": "e01c2d986929b8ed", + "type": "function", + "z": "f7ff9c0e54da4b8e", + "name": "set object_datetime", + "func": "if (msg.topic) {\n global.set(\"object_date\", msg.payload.object_date);\n global.set(\"object_time\", msg.payload.object_time);\n if (msg.payload.object_lat !== undefined) {\n global.set(\"object_lat\", msg.payload.object_lat);\n }\n if (msg.payload.object_lon !== undefined) {\n global.set(\"object_lon\", msg.payload.object_lon);\n }\n}\nreturn msg;", + "outputs": 1, + "timeout": "", + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 970, + "y": 200, + "wires": [ + [] + ] + }, + { + "id": "8f1721c5616582cc", + "type": "switch", + "z": "f7ff9c0e54da4b8e", + "name": "", + "property": "topic", + "propertyType": "msg", + "rules": [ + { + "t": "eq", + "v": "object_datetime_end", + "vt": "str" + }, + { + "t": "eq", + "v": "object_latlon_end", + "vt": "str" + } + ], + "checkall": "true", + "repair": false, + "outputs": 2, + "x": 570, + "y": 300, + "wires": [ + [ + "e4efaec99ee5c172" + ], + [ + "4790ca87c9ebf492" + ] + ] + }, + { + "id": "4790ca87c9ebf492", + "type": "function", + "z": "f7ff9c0e54da4b8e", + "name": "set object_latlon_end", + "func": "if (msg.topic) {\n global.set(\"object_lat_end\", msg.payload.object_lat_end);\n global.set(\"object_lon_end\", msg.payload.object_lon_end);\n}\n\nreturn msg;\n", + "outputs": 1, + "timeout": "", + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 980, + "y": 340, + "wires": [ + [] + ] + }, + { + "id": "e4efaec99ee5c172", + "type": "function", + "z": "f7ff9c0e54da4b8e", + "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}\n\n\nreturn msg;\n", + "outputs": 1, + "timeout": "", + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 990, + "y": 300, + "wires": [ + [] + ] + }, + { + "id": "ad434c2ca4b52cf4", + "type": "switch", + "z": "f7ff9c0e54da4b8e", + "name": "", + "property": "topic", + "propertyType": "msg", + "rules": [ + { + "t": "eq", + "v": "sample_net_params", + "vt": "str" + } + ], + "checkall": "true", + "repair": false, + "outputs": 1, + "x": 570, + "y": 400, + "wires": [ + [ + "fb90be363fcaf2e5" + ] + ] + }, + { + "id": "fb90be363fcaf2e5", + "type": "function", + "z": "f7ff9c0e54da4b8e", + "name": "set sample_net_params", + "func": "if (msg.topic) {\n global.set(\"sample_net_mouth_diameter\", msg.payload.sample_net_mouth_diameter);\n global.set(\"sample_net_length\", msg.payload.sample_net_length);\n global.set(\"sample_net_mesh_size\", msg.payload.sample_net_mesh_size);\n global.set(\"sample_filtered_volume\", msg.payload.sample_filtered_volume);\n}\nreturn msg;\n", + "outputs": 1, + "timeout": "", + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 990, + "y": 400, + "wires": [ + [] + ] + }, + { + "id": "b993c6289d67f56d", + "type": "switch", + "z": "f7ff9c0e54da4b8e", + "name": "", + "property": "topic", + "propertyType": "msg", + "rules": [ + { + "t": "eq", + "v": "sample_water_params", + "vt": "str" + } + ], + "checkall": "true", + "repair": false, + "outputs": 1, + "x": 570, + "y": 460, + "wires": [ + [ + "01dbd7f8f0e8cc4b" + ] + ] + }, + { + "id": "01dbd7f8f0e8cc4b", + "type": "function", + "z": "f7ff9c0e54da4b8e", + "name": "set sample_water_params", + "func": "if (msg.topic) {\n global.set(\"sample_collected_volume\", msg.payload.sample_collected_volume);\n global.set(\"sample_concentration_factor\", msg.payload.sample_concentration_factor);\n global.set(\"sample_sieve_mesh_size\", msg.payload.sample_sieve_mesh_size);\n global.set(\"sample_depth\", msg.payload.sample_depth);\n global.set(\"sample_comment\", msg.payload.sample_comment);\n}\nreturn msg;\n", + "outputs": 1, + "timeout": "", + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 1000, + "y": 460, + "wires": [ + [] + ] + }, + { + "id": "23f16f4437be7a5f", + "type": "switch", + "z": "f7ff9c0e54da4b8e", + "name": "", + "property": "topic", + "propertyType": "msg", + "rules": [ + { + "t": "eq", + "v": "sample_project_meta", + "vt": "str" + } + ], + "checkall": "true", + "repair": false, + "outputs": 1, + "x": 570, + "y": 140, + "wires": [ + [ + "815b79480bdb6864", + "ba461390e1455794", + "9ddd0fbe2670f626", + "e7f23192557851e0", + "e4d9d690aa12d09c" + ] + ] + }, + { + "id": "815b79480bdb6864", + "type": "function", + "z": "f7ff9c0e54da4b8e", + "name": "set sample_project_meta", + "func": "if (msg.topic) {\n global.set(\"sample_project\", msg.payload.sample_project);\n global.set(\"sample_operator\", msg.payload.sample_operator);\n global.set(\"sample_id\", msg.payload.sample_id);\n global.set(\"sample_gear\", msg.payload.sample_gear);\n}\nreturn msg;\n", + "outputs": 1, + "timeout": "", + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 990, + "y": 140, + "wires": [ + [] + ] + }, + { + "id": "38f795adc7ce566d", + "type": "function", + "z": "f7ff9c0e54da4b8e", + "name": "set data", + "func": "// 1. Setup Base Variables\nconst gear = msg.payload.sample_gear;\nconst now = new Date();\nconst dateStr = now.toISOString().split('T')[0]; // YYYY-MM-DD\n// Time as HHMM (no colons, no seconds) — matches sendUpdate format\nconst hh = String(now.getUTCHours()).padStart(2, '0');\nconst mm = String(now.getUTCMinutes()).padStart(2, '0');\nconst timeStr = hh + mm;\n\n// Helper to get global or fallback\nconst getG = (key, fallback) => global.get(key) ?? fallback;\n\n// 2. Define the Full Schema Template (All keys)\nconst allKeys = [\n \"object_date\", \"object_time\", \"object_lat\", \"object_lon\",\n \"object_date_end\", \"object_time_end\", \"object_lat_end\", \"object_lon_end\",\n \"sample_net_mouth_diameter\", \"sample_net_length\", \"sample_net_mesh_size\",\n \"sample_filtered_volume\", \"sample_concentration_factor\", \"sample_sieve_mesh_size\",\n \"sample_depth\", \"sample_collected_volume\"\n];\n\nlet updates = {};\n\n// 3. Logic by Gear Type\n// All gears use getG() so user-entered values are preserved.\n// Defaults are only used when no value exists in globals.\nif (gear === \"Horizontal Net\") {\n updates = {\n object_date: getG(\"object_date\", dateStr),\n object_time: getG(\"object_time\", timeStr),\n object_lat: getG(\"object_lat\", 0),\n object_lon: getG(\"object_lon\", 0),\n object_date_end: getG(\"object_date_end\", getG(\"object_date\", dateStr)),\n object_time_end: getG(\"object_time_end\", getG(\"object_time\", timeStr)),\n object_lat_end: getG(\"object_lat_end\", getG(\"object_lat\", 0)),\n object_lon_end: getG(\"object_lon_end\", getG(\"object_lon\", 0)),\n sample_net_mouth_diameter: getG(\"sample_net_mouth_diameter\", 30),\n sample_net_length: getG(\"sample_net_length\", 120),\n sample_net_mesh_size: getG(\"sample_net_mesh_size\", 20),\n sample_filtered_volume: getG(\"sample_filtered_volume\", 2000),\n sample_concentration_factor: getG(\"sample_concentration_factor\", 1),\n sample_sieve_mesh_size: getG(\"sample_sieve_mesh_size\", 200),\n sample_depth: getG(\"sample_depth\", 1),\n sample_collected_volume: getG(\"sample_collected_volume\", 50)\n };\n}\nelse if (gear === \"Vertical Net\") {\n updates = {\n object_date: getG(\"object_date\", dateStr),\n object_time: getG(\"object_time\", timeStr),\n object_lat: getG(\"object_lat\", 0),\n object_lon: getG(\"object_lon\", 0),\n sample_net_mouth_diameter: getG(\"sample_net_mouth_diameter\", 30),\n sample_net_length: getG(\"sample_net_length\", 120),\n sample_net_mesh_size: getG(\"sample_net_mesh_size\", 20),\n sample_filtered_volume: getG(\"sample_filtered_volume\", 2000),\n sample_concentration_factor: getG(\"sample_concentration_factor\", 1),\n sample_sieve_mesh_size: getG(\"sample_sieve_mesh_size\", 200),\n sample_depth: getG(\"sample_depth\", 1),\n sample_collected_volume: getG(\"sample_collected_volume\", 50)\n };\n}\nelse if (gear === \"Niskin bottle\") {\n updates = {\n object_date: getG(\"object_date\", dateStr),\n object_time: getG(\"object_time\", timeStr),\n object_lat: getG(\"object_lat\", 0),\n object_lon: getG(\"object_lon\", 0),\n sample_concentration_factor: getG(\"sample_concentration_factor\", 1),\n sample_sieve_mesh_size: getG(\"sample_sieve_mesh_size\", 200),\n sample_depth: getG(\"sample_depth\", 1),\n sample_collected_volume: getG(\"sample_collected_volume\", 50)\n };\n}\nelse if (gear === \"Lab culture\") {\n updates = {\n object_date: getG(\"object_date\", dateStr),\n object_time: getG(\"object_time\", timeStr),\n object_lat: getG(\"object_lat\", 0),\n object_lon: getG(\"object_lon\", 0)\n };\n}\nelse if (gear === \"Demo / Test\") {\n updates = {\n object_date: getG(\"object_date\", dateStr),\n object_time: getG(\"object_time\", timeStr),\n object_lat: getG(\"object_lat\", 48.587440060782704),\n object_lon: getG(\"object_lon\", -3.838189224660975)\n };\n}\n\n// 4. Final Processing: Sync to Global & Build Payload\nlet finalPayload = {};\n\nallKeys.forEach(key => {\n if (updates.hasOwnProperty(key)) {\n // Key is relevant to current gear type - use value (from getG with fallback)\n finalPayload[key] = updates[key];\n global.set(key, updates[key]);\n } else {\n // Key is NOT relevant to current gear type.\n // Send null in payload (disabled fields show empty),\n // but preserve global so user values survive gear switches.\n finalPayload[key] = null;\n }\n});\n\nmsg.payload = finalPayload;\nreturn msg;", + "outputs": 1, + "timeout": "", + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 100, + "y": 340, + "wires": [ + [ + "e7f23192557851e0", + "ba461390e1455794", + "9ddd0fbe2670f626", + "e4d9d690aa12d09c" + ] + ] + }, + { + "id": "6a0b3b7b9f767135", + "type": "ui-template", + "z": "71ede8b7dd88d90e", + "group": "b274327af3807b79", + "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": 750, + "y": 340, + "wires": [ + [] + ] + }, + { + "id": "79bccc0355eb5d87", + "type": "ui-template", + "z": "71ede8b7dd88d90e", + "group": "d2f77573ed4317e4", + "page": "", + "ui": "", + "name": "body", + "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": [ + [ + "e44fd64f57f48140" + ] + ] + }, + { + "id": "6001550c3befb6ea", + "type": "ui-template", + "z": "71ede8b7dd88d90e", + "group": "3ae252a2e5abca89", + "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": "74aa092817adc6f0", + "type": "function", + "z": "71ede8b7dd88d90e", + "name": "set acq_params", + "func": "// On récupère le payload\nconst p = msg.payload;\n\n// On ne procède que si le payload est un objet\nif (p && typeof p === 'object') {\n\n // Liste des paramètres à surveiller et à stocker\n // On boucle sur les clés pour éviter de répéter 20 fois \"if(p.x !== undefined)\"\n const keys = [\n \"acq_id\", \"acq_nb_frame\", \"acq_interframe_volume\", \n \"acq_imaged_volume\", \"acq_pumped_volume\", \"acq_comment\",\n \"acq_status\", \"acq_magnification\", \"acq_tube_lens_reference\",\n \"acq_objective_lens_reference\", \"process_pixel_size\",\n \"calibration_pixel_size\", \"calibration_led_intensity\",\n \"sensor_width_um\", \"sensor_height_um\", \"acq_flowcell_thickness\",\n \"acq_interframe_flowrate\", \"acq_stabilization_delay\",\n \"acq_start_timestamp\"\n ];\n\n keys.forEach(key => {\n if (p[key] !== undefined) {\n global.set(key, p[key]);\n }\n });\n\n // Cas particuliers (noms de propriétés différents entre msg et global)\n if (p.progression !== undefined) global.set(\"acq_progression\", p.progression);\n if (p.duration_left !== undefined) global.set(\"acq_duration_left\", p.duration_left);\n}\n\nreturn msg;", + "outputs": 1, + "timeout": "", + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 760, + "y": 120, + "wires": [ + [] + ] + }, + { + "id": "ab83c951efd3e647", + "type": "ui-event", + "z": "71ede8b7dd88d90e", + "ui": "e6ae26617c24c3ea", + "name": "UI Event", + "x": 80, + "y": 40, + "wires": [ + [ + "5a65f683c152b736" + ] + ] + }, + { + "id": "5a65f683c152b736", + "type": "switch", + "z": "71ede8b7dd88d90e", + "name": "msg.payload.page.path === \"/acquisition\"", + "property": "payload.page.path", + "propertyType": "msg", + "rules": [ + { + "t": "eq", + "v": "/acquisition", + "vt": "str" + } + ], + "checkall": "true", + "repair": false, + "outputs": 1, + "x": 330, + "y": 40, + "wires": [ + [ + "31fab063b7078fe6" + ] + ] + }, + { + "id": "31fab063b7078fe6", + "type": "function", + "z": "71ede8b7dd88d90e", + "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": [ + [ + "79bccc0355eb5d87" + ] + ] + }, + { + "id": "740af1e4fccda6b3", + "type": "mqtt in", + "z": "71ede8b7dd88d90e", + "name": "", + "topic": "status/imager", + "qos": "0", + "datatype": "json", + "broker": "8dc3722c.06efa8", + "nl": false, + "rap": false, + "inputs": 0, + "x": 90, + "y": 200, + "wires": [ + [ + "79bccc0355eb5d87" + ] + ] + }, + { + "id": "ff1fcb16bcb6619e", + "type": "mqtt out", + "z": "71ede8b7dd88d90e", + "name": "MQTT", + "topic": "", + "qos": "", + "retain": "", + "respTopic": "", + "contentType": "", + "userProps": "", + "correl": "", + "expiry": "", + "broker": "8dc3722c.06efa8", + "x": 1110, + "y": 220, + "wires": [] + }, + { + "id": "4a02deeb69a27d00", + "type": "function", + "z": "71ede8b7dd88d90e", + "name": "update_config", + "func": "const keys = global.keys();\nlet config = {};\n\nkeys.forEach(key => {\n if (!key.startsWith('$')) {\n if (\n key.startsWith('sample_') ||\n key.startsWith('acq_') ||\n key.startsWith('object_') ||\n key.startsWith('process_') ||\n key.startsWith('img_')\n ) {\n config[key] = global.get(key);\n }\n }\n});\n\n// Compose fully-qualified IDs for unique EcoTaxa naming\nconst project = config.sample_project || \"\";\nconst sampleRaw = String(config.sample_id || \"\");\nconst acqRaw = String(config.acq_id || \"\");\n\nif (project && !sampleRaw.startsWith(project + \"_\")) {\n config.sample_id = project + \"_\" + sampleRaw;\n}\nconst finalSample = String(config.sample_id || \"\");\nif (finalSample && !acqRaw.startsWith(finalSample + \"_\")) {\n config.acq_id = finalSample + \"_\" + acqRaw;\n}\n\n// Sanitize spaces in IDs to match filesystem paths\n// (the Python imager replaces spaces with underscores in directory names)\nconfig.sample_id = String(config.sample_id || \"\").replace(/ /g, \"_\");\nconfig.acq_id = String(config.acq_id || \"\").replace(/ /g, \"_\");\n\nmsg.topic = \"imager/image\";\n\nmsg.payload = {\n action: \"update_config\",\n config: config\n};\n\nreturn msg;", + "outputs": 1, + "timeout": 0, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 760, + "y": 220, + "wires": [ + [ + "ff1fcb16bcb6619e" + ] + ] + }, + { + "id": "908239a5c01e9061", + "type": "function", + "z": "71ede8b7dd88d90e", + "name": "start acquisition", + "func": "const acq_interframe_volume = msg.payload.acq_interframe_volume\n || global.get(\"acq_interframe_volume\") || 0;\nconst acq_nb_frame = msg.payload.acq_nb_frame\n || global.get(\"acq_nb_frame\") || 0;\nconst acq_stabilization_delay = msg.payload.acq_stabilization_delay\n || global.get(\"acq_stabilization_delay\") || 0;\nconst acq_interframe_flowrate = msg.payload.acq_interframe_flowrate\n || global.get(\"acq_interframe_flowrate\") || 0;\n\nmsg.payload = {\n action: \"image\",\n pump_direction: \"FORWARD\",\n volume: acq_interframe_volume,\n nb_frame: acq_nb_frame,\n sleep: acq_stabilization_delay,\n flowrate: acq_interframe_flowrate,\n};\n\nmsg.topic = \"imager/image\";\n\nreturn msg;", + "outputs": 1, + "timeout": 0, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 760, + "y": 280, + "wires": [ + [ + "51e3db016f2cdf07" + ] + ] + }, + { + "id": "5eb0686bb01d4643", + "type": "mqtt out", + "z": "71ede8b7dd88d90e", + "name": "MQTT", + "topic": "", + "qos": "", + "retain": "", + "respTopic": "", + "contentType": "", + "userProps": "", + "correl": "", + "expiry": "", + "broker": "8dc3722c.06efa8", + "x": 1110, + "y": 280, + "wires": [] + }, + { + "id": "98dde1119867adf0", + "type": "switch", + "z": "71ede8b7dd88d90e", + "name": "", + "property": "payload.acq_status", + "propertyType": "msg", + "rules": [ + { + "t": "eq", + "v": "off", + "vt": "str" + }, + { + "t": "eq", + "v": "on", + "vt": "str" + } + ], + "checkall": "true", + "repair": false, + "outputs": 2, + "x": 550, + "y": 220, + "wires": [ + [ + "7c4e3258ef8f22c0" + ], + [ + "4a02deeb69a27d00", + "908239a5c01e9061" + ] + ] + }, + { + "id": "7c4e3258ef8f22c0", + "type": "function", + "z": "71ede8b7dd88d90e", + "name": "stop acquisition", + "func": "// Stop the imager acquisition\nvar stopImager = { topic: \"imager/image\", payload: { action: \"stop\" } };\n\n// Also send pump stop command as failsafe\nvar stopPump = { topic: \"actuator/pump\", payload: { action: \"stop\" } };\n\n// Return both messages to be sent\nreturn [[stopImager, stopPump]];\n", + "outputs": 1, + "timeout": 0, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 760, + "y": 160, + "wires": [ + [ + "687e8f43fbf72b84" + ] + ] + }, + { + "id": "687e8f43fbf72b84", + "type": "mqtt out", + "z": "71ede8b7dd88d90e", + "name": "MQTT", + "topic": "", + "qos": "", + "retain": "", + "respTopic": "", + "contentType": "", + "userProps": "", + "correl": "", + "expiry": "", + "broker": "8dc3722c.06efa8", + "x": 1110, + "y": 160, + "wires": [] + }, + { + "id": "7814ecbf379005bd", + "type": "switch", + "z": "71ede8b7dd88d90e", + "name": "", + "property": "topic", + "propertyType": "msg", + "rules": [ + { + "t": "neq", + "v": "$pageview", + "vt": "str" + } + ], + "checkall": "true", + "repair": false, + "outputs": 1, + "x": 310, + "y": 220, + "wires": [ + [ + "98dde1119867adf0", + "74aa092817adc6f0" + ] + ] + }, + { + "id": "51e3db016f2cdf07", + "type": "delay", + "z": "71ede8b7dd88d90e", + "name": "", + "pauseType": "delay", + "timeout": "200", + "timeoutUnits": "milliseconds", + "rate": "1", + "nbRateUnits": "1", + "rateUnits": "second", + "randomFirst": "1", + "randomLast": "5", + "randomUnits": "seconds", + "drop": false, + "allowrate": false, + "outputs": 1, + "x": 950, + "y": 280, + "wires": [ + [ + "5eb0686bb01d4643" + ] + ] + }, + { + "id": "e44fd64f57f48140", + "type": "switch", + "z": "71ede8b7dd88d90e", + "name": "", + "property": "topic", + "propertyType": "msg", + "rules": [ + { + "t": "neq", + "v": "$pageleave", + "vt": "str" + } + ], + "checkall": "true", + "repair": false, + "outputs": 1, + "x": 310, + "y": 180, + "wires": [ + [ + "7814ecbf379005bd" + ] + ] + }, + { + "id": "3841297f254854cf", + "type": "ui-event", + "z": "524b7620431210e8", + "ui": "e6ae26617c24c3ea", + "name": "UI Event", + "x": 80, + "y": 40, + "wires": [ + [ + "66f970bece31600d" + ] + ] + }, + { + "id": "66f970bece31600d", + "type": "switch", + "z": "524b7620431210e8", + "name": "msg.payload.page.path === \"/segmentation\"", + "property": "payload.page.path", + "propertyType": "msg", + "rules": [ + { + "t": "eq", + "v": "/segmentation", + "vt": "str" + } + ], + "checkall": "true", + "repair": false, + "outputs": 1, + "x": 330, + "y": 40, + "wires": [ + [ + "773960d6099cab47", + "aa1309fda7fde8f2", + "a1b2c3d4e5f60005" + ] + ] + }, + { + "id": "773960d6099cab47", + "type": "list acquisitions", + "z": "524b7620431210e8", + "name": "", + "x": 100, + "y": 140, + "wires": [ + [ + "8a22543633712279" + ] + ] + }, + { + "id": "3ad2613862513bb0", + "type": "ui-template", + "z": "524b7620431210e8", + "group": "9b992ca6515ed058", + "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": 1520, + "y": 40, + "wires": [ + [] + ] + }, + { + "id": "f9e776957dfa61b0", + "type": "mqtt out", + "z": "524b7620431210e8", + "name": "MQTT", + "topic": "", + "qos": "", + "retain": "", + "respTopic": "", + "contentType": "", + "userProps": "", + "correl": "", + "expiry": "", + "broker": "8dc3722c.06efa8", + "x": 1210, + "y": 140, + "wires": [] + }, + { + "id": "60ae065050fc2ad3", + "type": "switch", + "z": "524b7620431210e8", + "name": "", + "property": "topic", + "propertyType": "msg", + "rules": [ + { + "t": "eq", + "v": "segmenter/segment", + "vt": "str" + } + ], + "checkall": "true", + "repair": false, + "outputs": 1, + "x": 1030, + "y": 140, + "wires": [ + [ + "f9e776957dfa61b0", + "3314fac880c6a656" + ] + ] + }, + { + "id": "9a2e04f65e0432e1", + "type": "mqtt in", + "z": "524b7620431210e8", + "name": "", + "topic": "status/segmenter", + "qos": "0", + "datatype": "json", + "broker": "8dc3722c.06efa8", + "nl": false, + "rap": false, + "inputs": 0, + "x": 100, + "y": 260, + "wires": [ + [ + "e2db15bb9c3c254f", + "a1b2c3d4e5f60003" + ] + ] + }, + { + "id": "e2db15bb9c3c254f", + "type": "ui-template", + "z": "524b7620431210e8", + "group": "402b3d24c87ab0d2", + "page": "", + "ui": "", + "name": "Details of Segmentation", + "order": 1, + "width": "0", + "height": "0", + "head": "", + "format": "\n\n\n\n\n", + "storeOutMessages": true, + "passthru": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 370, + "y": 300, + "wires": [ + [] + ] + }, + { + "id": "312de75fd2144c81", + "type": "switch", + "z": "524b7620431210e8", + "name": "", + "property": "payload.is_segmented", + "propertyType": "msg", + "rules": [ + { + "t": "true" + }, + { + "t": "false" + } + ], + "checkall": "true", + "repair": false, + "outputs": 2, + "x": 510, + "y": 140, + "wires": [ + [], + [ + "6e43a35e8dcf2b3b" + ] + ] + }, + { + "id": "3314fac880c6a656", + "type": "function", + "z": "524b7620431210e8", + "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 global.set(\"process_min_ESD\", msg.payload.settings.process_min_ESD);\n}\nreturn msg;\n", + "outputs": 1, + "timeout": "", + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 1220, + "y": 60, + "wires": [ + [] + ] + }, + { + "id": "aa1309fda7fde8f2", + "type": "function", + "z": "524b7620431210e8", + "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": 200, + "wires": [ + [ + "e2db15bb9c3c254f" + ] + ] + }, + { + "id": "6e43a35e8dcf2b3b", + "type": "function", + "z": "524b7620431210e8", + "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;\nmsg.payload.open_dialog = true;\n\n// Return the modified message\nreturn msg;\n", + "outputs": 1, + "timeout": 0, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 700, + "y": 140, + "wires": [ + [ + "60bfa71a5e7fc2d5" + ] + ] + }, + { + "id": "60bfa71a5e7fc2d5", + "type": "ui-template", + "z": "524b7620431210e8", + "group": "bfd4acb7b243514f", + "page": "", + "ui": "", + "name": "Dialog", + "order": 2, + "width": 0, + "height": 0, + "head": "", + "format": "\n", + "storeOutMessages": true, + "passthru": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 870, + "y": 140, + "wires": [ + [ + "60ae065050fc2ad3", + "a1b2c3d4e5f60002" + ] + ] + }, + { + "id": "8a22543633712279", + "type": "ui-template", + "z": "524b7620431210e8", + "group": "bfd4acb7b243514f", + "page": "", + "ui": "", + "name": "List of Acquisitions", + "order": 1, + "width": 0, + "height": 0, + "head": "", + "format": "\n\n\n", + "storeOutMessages": true, + "passthru": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 310, + "y": 140, + "wires": [ + [ + "98b980828cac388b" + ] + ] + }, + { + "id": "98b980828cac388b", + "type": "switch", + "z": "524b7620431210e8", + "name": "", + "property": "topic", + "propertyType": "msg", + "rules": [ + { + "t": "eq", + "v": "segment_request", + "vt": "str" + }, + { + "t": "eq", + "v": "delete_request", + "vt": "str" + }, + { + "t": "regex", + "v": "^queue_", + "vt": "str" + } + ], + "checkall": "true", + "repair": false, + "outputs": 3, + "x": 430, + "y": 220, + "wires": [ + [ + "312de75fd2144c81" + ], + [ + "20e38293b7b4fca8" + ], + [ + "a1b2c3d4e5f60001" + ] + ] + }, + { + "id": "20e38293b7b4fca8", + "type": "exec", + "z": "524b7620431210e8", + "command": "rm -rf ", + "addpay": "payload.path", + "append": "", + "useSpawn": "false", + "timer": "", + "winHide": false, + "oldrc": false, + "name": "", + "x": 590, + "y": 240, + "wires": [ + [], + [], + [] + ] + }, + { + "id": "b3a9d14bbb081aa8", + "type": "ui-event", + "z": "4fdbd7bacb797c5a", + "ui": "e6ae26617c24c3ea", + "name": "UI Event", + "x": 80, + "y": 40, + "wires": [ + [ + "843d34f5d30962ab", + "69ce53c2079b63a0" + ] + ] + }, + { + "id": "843d34f5d30962ab", + "type": "switch", + "z": "4fdbd7bacb797c5a", + "name": "msg.payload.page.path === \"/visualization\"", + "property": "payload.page.path", + "propertyType": "msg", + "rules": [ + { + "t": "eq", + "v": "/visualization", + "vt": "str" + } + ], + "checkall": "true", + "repair": false, + "outputs": 1, + "x": 330, + "y": 40, + "wires": [ + [ + "2f62fc38be1bfe69" + ] + ] + }, + { + "id": "0425c18c1a9ca1ca", + "type": "ui-template", + "z": "4fdbd7bacb797c5a", + "group": "b570f76ef526af45", + "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": "2f62fc38be1bfe69", + "type": "list segmentations", + "z": "4fdbd7bacb797c5a", + "name": "", + "x": 170, + "y": 180, + "wires": [ + [ + "40a54312ad6d20c1" + ] + ] + }, + { + "id": "40a54312ad6d20c1", + "type": "function", + "z": "4fdbd7bacb797c5a", + "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});\nreturn msg;\n", + "outputs": 1, + "timeout": 0, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 440, + "y": 180, + "wires": [ + [ + "205e01594aafdf52" + ] + ] + }, + { + "id": "bb7e84695ec2b24c", + "type": "file in", + "z": "4fdbd7bacb797c5a", + "name": "", + "filename": "payload.path", + "filenameType": "msg", + "format": "utf8", + "chunk": false, + "sendError": false, + "encoding": "none", + "allProps": false, + "x": 180, + "y": 380, + "wires": [ + [ + "1e51a51e73c8f2a5", + "8f1bf6abf93d9587", + "a67c2c1c9523ec54", + "9f4a3d23a3c4ba43", + "836d93ca31e11fc8", + "03188ea81f8f748d", + "1db541fc5717964a", + "109aacc68888f0b1", + "27ebeb1895528a1d", + "2aa2c7cb2ba901a0", + "8fc92b69ced53cf2", + "88371b41526bbd18", + "93ac167a60a35472", + "72065cd82cb87df5" + ] + ] + }, + { + "id": "51fa556f38fcfaa4", + "type": "function", + "z": "4fdbd7bacb797c5a", + "name": "Get tsv path", + "func": "// 1. On s'assure que le path existe\nif (msg.payload.path) {\n\n const currentPath = msg.payload.path;\n const cleanPath = currentPath.endsWith('/') ? currentPath.slice(0, -1) : currentPath;\n const acqId = cleanPath.split('/').pop();\n\n // Try expected filename first\n const expectedTsv = `${cleanPath}/ecotaxa_${acqId}.tsv`;\n\n if (fs.existsSync(expectedTsv)) {\n msg.payload.path = expectedTsv;\n } else {\n // Fallback: find any ecotaxa_*.tsv in the directory\n try {\n const files = fs.readdirSync(cleanPath);\n const tsvFile = files.find(f => f.startsWith('ecotaxa_') && f.endsWith('.tsv'));\n if (tsvFile) {\n msg.payload.path = `${cleanPath}/${tsvFile}`;\n } else {\n node.warn('No ecotaxa TSV found in ' + cleanPath);\n return null;\n }\n } catch(e) {\n node.error('Cannot read directory: ' + e.message);\n return null;\n }\n }\n}\n\nreturn msg;", + "outputs": 1, + "timeout": 0, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [ + { + "var": "fs", + "module": "fs" + } + ], + "x": 930, + "y": 180, + "wires": [ + [ + "c4a624fe9c464cda" + ] + ] + }, + { + "id": "9b4aa58f8a37a9d9", + "type": "ui-template", + "z": "4fdbd7bacb797c5a", + "group": "0c1537ce71affc2b", + "page": "", + "ui": "", + "name": "Gallery", + "order": 1, + "width": 0, + "height": 0, + "head": "", + "format": "\n\n\n\n\n\n", + "storeOutMessages": true, + "passthru": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 920, + "y": 1000, + "wires": [ + [] + ] + }, + { + "id": "205e01594aafdf52", + "type": "ui-template", + "z": "4fdbd7bacb797c5a", + "group": "fa6393a7d7e3b7d7", + "page": "", + "ui": "", + "name": "List of Segmentation", + "order": 1, + "width": 0, + "height": 0, + "head": "", + "format": "\n\n\n\n", + "storeOutMessages": true, + "passthru": false, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 740, + "y": 180, + "wires": [ + [ + "82532431827450f2" + ] + ] + }, + { + "id": "82532431827450f2", + "type": "switch", + "z": "4fdbd7bacb797c5a", + "name": "Route Live Messages", + "property": "topic", + "propertyType": "msg", + "rules": [ + { + "t": "eq", + "v": "refresh_list", + "vt": "str" + }, + { + "t": "eq", + "v": "selected_acquisition", + "vt": "str" + } + ], + "checkall": "true", + "repair": false, + "outputs": 2, + "x": 960, + "y": 180, + "wires": [ + [ + "2f62fc38be1bfe69" + ], + [ + "51fa556f38fcfaa4" + ] + ] + }, + { + "id": "0c7765fa696e0717", + "type": "ui-template", + "z": "4fdbd7bacb797c5a", + "group": "53490f9c39e2065d", + "page": "", + "ui": "", + "name": "Infos", + "order": 1, + "width": 0, + "height": 0, + "head": "", + "format": "\n\n\n\n\n", + "storeOutMessages": true, + "passthru": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 910, + "y": 480, + "wires": [ + [] + ] + }, + { + "id": "1e51a51e73c8f2a5", + "type": "function", + "z": "4fdbd7bacb797c5a", + "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, + "timeout": 0, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 510, + "y": 520, + "wires": [ + [ + "d49dc2cc9f8eeaab" + ] + ] + }, + { + "id": "8f1bf6abf93d9587", + "type": "function", + "z": "4fdbd7bacb797c5a", + "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": 560, + "wires": [ + [ + "1631167b67b37d25" + ] + ] + }, + { + "id": "a67c2c1c9523ec54", + "type": "function", + "z": "4fdbd7bacb797c5a", + "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": 600, + "wires": [ + [ + "9bb69f94e4855a16" + ] + ] + }, + { + "id": "9f4a3d23a3c4ba43", + "type": "function", + "z": "4fdbd7bacb797c5a", + "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": 640, + "wires": [ + [ + "731608304eeaa5f3" + ] + ] + }, + { + "id": "836d93ca31e11fc8", + "type": "function", + "z": "4fdbd7bacb797c5a", + "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": 680, + "wires": [ + [ + "a6c62b4b3da492e9" + ] + ] + }, + { + "id": "03188ea81f8f748d", + "type": "function", + "z": "4fdbd7bacb797c5a", + "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": 720, + "wires": [ + [ + "fc22ad681d25523b" + ] + ] + }, + { + "id": "1db541fc5717964a", + "type": "function", + "z": "4fdbd7bacb797c5a", + "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": 760, + "wires": [ + [ + "2905bc12edeef503" + ] + ] + }, + { + "id": "109aacc68888f0b1", + "type": "function", + "z": "4fdbd7bacb797c5a", + "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": 800, + "wires": [ + [ + "5fe97b4a564498ea" + ] + ] + }, + { + "id": "27ebeb1895528a1d", + "type": "function", + "z": "4fdbd7bacb797c5a", + "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": 840, + "wires": [ + [ + "565c6d0f098e5be0" + ] + ] + }, + { + "id": "d49dc2cc9f8eeaab", + "type": "ui-template", + "z": "4fdbd7bacb797c5a", + "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": 920, + "y": 520, + "wires": [ + [] + ] + }, + { + "id": "2aa2c7cb2ba901a0", + "type": "function", + "z": "4fdbd7bacb797c5a", + "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": 480, + "wires": [ + [ + "0c7765fa696e0717" + ] + ] + }, + { + "id": "1631167b67b37d25", + "type": "ui-template", + "z": "4fdbd7bacb797c5a", + "group": "47800358cf7cee25", + "page": "", + "ui": "", + "name": "ESD Histogram", + "order": 1, + "width": 0, + "height": 0, + "head": "", + "format": "\n\n\n", + "storeOutMessages": true, + "passthru": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 940, + "y": 560, + "wires": [ + [] + ] + }, + { + "id": "9bb69f94e4855a16", + "type": "ui-template", + "z": "4fdbd7bacb797c5a", + "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": 920, + "y": 600, + "wires": [ + [] + ] + }, + { + "id": "731608304eeaa5f3", + "type": "ui-template", + "z": "4fdbd7bacb797c5a", + "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": 990, + "y": 640, + "wires": [ + [] + ] + }, + { + "id": "a6c62b4b3da492e9", + "type": "ui-template", + "z": "4fdbd7bacb797c5a", + "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": 970, + "y": 680, + "wires": [ + [] + ] + }, + { + "id": "fc22ad681d25523b", + "type": "ui-template", + "z": "4fdbd7bacb797c5a", + "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": 930, + "y": 720, + "wires": [ + [] + ] + }, + { + "id": "2905bc12edeef503", + "type": "ui-template", + "z": "4fdbd7bacb797c5a", + "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": 990, + "y": 760, + "wires": [ + [] + ] + }, + { + "id": "5fe97b4a564498ea", + "type": "ui-template", + "z": "4fdbd7bacb797c5a", + "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": 960, + "y": 800, + "wires": [ + [] + ] + }, + { + "id": "565c6d0f098e5be0", + "type": "ui-template", + "z": "4fdbd7bacb797c5a", + "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": 950, + "y": 840, + "wires": [ + [] + ] + }, + { + "id": "8fc92b69ced53cf2", + "type": "function", + "z": "4fdbd7bacb797c5a", + "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 // Extract pixel size (microns per pixel) - always needed for scale bar rendering\n const px = parseFloat(val(firstRow, \"process_pixel\"));\n if (!isNaN(px) && px > 0) {\n meta.resolution = px;\n }\n // Flag: if true, measurement values (width, height, area, etc.) are already in microns\n const applied = val(firstRow, \"process_pixel_applied\");\n meta.process_pixel_applied = (applied && applied.toString().toLowerCase() === \"true\");\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\", \"object_blur_laplacian\"\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: `/api/files/objects/${(val(row,\"object_date\")||\"\").replace(/ /g,\"_\")}/${(val(row,\"sample_id\")||\"\").replace(/ /g,\"_\")}/${(val(row,\"acq_id\")||\"\").replace(/ /g,\"_\")}/${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": 920, + "wires": [ + [ + "131aae14607d4830" + ] + ] + }, + { + "id": "131aae14607d4830", + "type": "ui-template", + "z": "4fdbd7bacb797c5a", + "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": 920, + "y": 920, + "wires": [ + [] + ] + }, + { + "id": "69ce53c2079b63a0", + "type": "function", + "z": "4fdbd7bacb797c5a", + "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": 460, + "y": 440, + "wires": [ + [ + "d49dc2cc9f8eeaab", + "1631167b67b37d25", + "9bb69f94e4855a16", + "731608304eeaa5f3", + "a6c62b4b3da492e9", + "fc22ad681d25523b", + "2905bc12edeef503", + "5fe97b4a564498ea", + "565c6d0f098e5be0", + "0c7765fa696e0717", + "131aae14607d4830", + "9b4aa58f8a37a9d9" + ] + ] + }, + { + "id": "88371b41526bbd18", + "type": "function", + "z": "4fdbd7bacb797c5a", + "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 // Extract pixel size (microns per pixel) - always needed for scale bar rendering\n const px = parseFloat(get(firstRow, 'process_pixel'));\n if (!isNaN(px) && px > 0) {\n meta.resolution = px;\n }\n // Flag: if true, measurement values (width, height, area, etc.) are already in microns\n const applied = get(firstRow, 'process_pixel_applied');\n meta.process_pixel_applied = (applied && applied.toString().toLowerCase() === \"true\");\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\", \"object_blur_laplacian\"\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')||'').replace(/ /g,'_')}/${(get(row, 'sample_id')||'').replace(/ /g,'_')}/${(get(row, 'acq_id')||'').replace(/ /g,'_')}/${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": 1000, + "wires": [ + [ + "9b4aa58f8a37a9d9" + ] + ] + }, + { + "id": "e16e3dace2bb507f", + "type": "delay", + "z": "4fdbd7bacb797c5a", + "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": 340, + "wires": [ + [ + "bb7e84695ec2b24c" + ] + ] + }, + { + "id": "ebb2567bdaf69c8e", + "type": "ui-template", + "z": "4fdbd7bacb797c5a", + "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": "c4a624fe9c464cda", + "type": "switch", + "z": "4fdbd7bacb797c5a", + "name": "", + "property": "topic", + "propertyType": "msg", + "rules": [ + { + "t": "neq", + "v": "$pageview", + "vt": "str" + } + ], + "checkall": "true", + "repair": false, + "outputs": 1, + "x": 170, + "y": 300, + "wires": [ + [ + "e16e3dace2bb507f" + ] + ] + }, + { + "id": "d365c80d0854bab7", + "type": "debug", + "z": "4fdbd7bacb797c5a", + "name": "debug 5", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "true", + "targetType": "full", + "statusVal": "", + "statusType": "auto", + "x": 940, + "y": 400, + "wires": [] + }, + { + "id": "93ac167a60a35472", + "type": "function", + "z": "4fdbd7bacb797c5a", + "name": "Sample Identity Metadata Only", + "func": "// FUNCTION: Extract all data and metadata\nconst lines = msg.payload.toString().trim().split(/\\r?\\n/);\nif (lines.length < 2) return null;\n\nconst headers = lines[0].split(\"\\t\");\n\n// Fonction utilitaire pour mapper les colonnes\nconst rowToObject = (rowCells) => {\n const obj = {};\n headers.forEach((header, index) => {\n obj[header] = rowCells[index] || \"\";\n });\n return obj;\n};\n\nlet allRows = [];\nlet firstDataRow = null;\n\n// --- 1. Parser toutes les lignes ---\nfor (let i = 1; i < lines.length; i++) {\n const cells = lines[i].split(\"\\t\");\n const rowObj = rowToObject(cells);\n\n allRows.push(rowObj);\n\n // Identifier la première vraie ligne de données pour les métadonnées globales\n if (!firstDataRow && !lines[i].trim().startsWith(\"[\")) {\n firstDataRow = rowObj;\n }\n}\n\n// --- 2. Extraire les métadonnées (basées sur la première ligne valide) ---\nconst meta = {\n sample_id: firstDataRow ? firstDataRow[\"sample_id\"] : \"\",\n project: firstDataRow ? firstDataRow[\"sample_project\"] : \"\",\n acq_id: firstDataRow ? firstDataRow[\"acq_id\"] : \"\"\n};\n\n// --- 3. Output : Métadonnées + l'intégralité des données ---\nmsg.payload = {\n meta: meta,\n data: allRows // Contient maintenant tous les enregistrements\n};\n\nreturn msg;", + "outputs": 1, + "timeout": 0, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 510, + "y": 400, + "wires": [ + [ + "d365c80d0854bab7" + ] + ] + }, + { + "id": "72065cd82cb87df5", + "type": "debug", + "z": "4fdbd7bacb797c5a", + "name": "debug 2", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "false", + "statusVal": "", + "statusType": "auto", + "x": 440, + "y": 340, + "wires": [] + }, + { + "id": "b2958e4a07a87cad", + "type": "ui-event", + "z": "a7825c4c81ad20a0", + "ui": "e6ae26617c24c3ea", + "name": "UI Event", + "x": 80, + "y": 40, + "wires": [ + [ + "b4515f99c5e5e2da", + "26ec7d57f8231752" + ] + ] + }, + { + "id": "b4515f99c5e5e2da", + "type": "switch", + "z": "a7825c4c81ad20a0", + "name": "msg.payload.page.path === \"/visualization\"", + "property": "payload.page.path", + "propertyType": "msg", + "rules": [ + { + "t": "eq", + "v": "/visualization", + "vt": "str" + } + ], + "checkall": "true", + "repair": false, + "outputs": 1, + "x": 330, + "y": 40, + "wires": [ + [ + "a15937db31847a11", + "99d07bf34db5693f" + ] + ] + }, + { + "id": "82cee7ccf168f572", + "type": "ui-template", + "z": "a7825c4c81ad20a0", + "group": "b570f76ef526af45", + "page": "", + "ui": "", + "name": "Navigation Top", + "order": 2, + "width": 0, + "height": 0, + "head": "", + "format": "\n\n", + "storeOutMessages": true, + "passthru": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 1300, + "y": 40, + "wires": [ + [] + ] + }, + { + "id": "a15937db31847a11", + "type": "list segmentations", + "z": "a7825c4c81ad20a0", + "name": "", + "x": 170, + "y": 180, + "wires": [ + [ + "4f4d57c070b89857", + "adc7e075bda50785" + ] + ] + }, + { + "id": "4f4d57c070b89857", + "type": "function", + "z": "a7825c4c81ad20a0", + "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});\nreturn msg;\n", + "outputs": 1, + "timeout": 0, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 440, + "y": 180, + "wires": [ + [ + "eb62a19058171fa8" + ] + ] + }, + { + "id": "1802e96dd833363b", + "type": "function", + "z": "a7825c4c81ad20a0", + "name": "Size-safe file read", + "func": "const MAX_LINES = 5001;\nconst MAX_SIZE = 10 * 1024 * 1024;\n\nconst filePath = msg.payload.path || msg.filename;\nif (!filePath) { return null; }\n\ntry {\n const stats = fs.statSync(filePath);\n if (stats.size > MAX_SIZE) {\n const fd = fs.openSync(filePath, \"r\");\n const buf = Buffer.alloc(Math.min(stats.size, 2 * 1024 * 1024));\n const bytesRead = fs.readSync(fd, buf, 0, buf.length, 0);\n fs.closeSync(fd);\n const partial = buf.toString(\"utf8\", 0, bytesRead);\n const lines = partial.split(\"\\n\");\n msg.payload = lines.slice(0, MAX_LINES).join(\"\\n\");\n node.warn(\"Large TSV (\" + Math.round(stats.size/1024/1024) + \"MB) - limited to \" + MAX_LINES + \" lines\");\n } else {\n msg.payload = fs.readFileSync(filePath, \"utf8\");\n }\n return msg;\n} catch(e) {\n node.error(\"File read error: \" + e.message, msg);\n return null;\n}", + "outputs": 1, + "timeout": 0, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [ + { + "var": "fs", + "module": "fs" + } + ], + "x": 180, + "y": 380, + "wires": [ + [ + "82d199f05a6f0af2", + "248999e928a14aa9", + "09b02605b198b2f4", + "897140ab8ebde04d", + "ea4aa3af0f18be63", + "42b7f7bec1d2da57", + "09203894702cf830", + "db14c860ad4ab4ec", + "eb793dfbf316c8d9", + "63e7888198376e77", + "da58d147efe428ea", + "f8b996c6c3c8e77a", + "a351f0483ac3a747" + ] + ] + }, + { + "id": "63121349be3135fd", + "type": "function", + "z": "a7825c4c81ad20a0", + "name": "Get tsv path", + "func": "// 1. On s'assure que le path existe\nif (msg.payload.path) {\n\n const currentPath = msg.payload.path;\n const cleanPath = currentPath.endsWith('/') ? currentPath.slice(0, -1) : currentPath;\n const acqId = cleanPath.split('/').pop();\n\n // Save the actual directory path relative to objects root for image URL construction\n // e.g. \"/home/pi/data/objects/2023-10-12/SomeName_A1\" -> \"2023-10-12/SomeName_A1\"\n const objRoot = \"/home/pi/data/objects/\";\n msg.tsvDir = cleanPath.startsWith(objRoot) ? cleanPath.slice(objRoot.length) : cleanPath;\n\n // Try expected filename first\n const expectedTsv = `${cleanPath}/ecotaxa_${acqId}.tsv`;\n\n if (fs.existsSync(expectedTsv)) {\n msg.payload.path = expectedTsv;\n } else {\n // Fallback: find any ecotaxa_*.tsv in the directory\n try {\n const files = fs.readdirSync(cleanPath);\n const tsvFile = files.find(f => f.startsWith('ecotaxa_') && f.endsWith('.tsv'));\n if (tsvFile) {\n msg.payload.path = `${cleanPath}/${tsvFile}`;\n } else {\n node.warn('No ecotaxa TSV found in ' + cleanPath);\n return null;\n }\n } catch(e) {\n node.error('Cannot read directory: ' + e.message);\n return null;\n }\n }\n}\n\nreturn msg;", + "outputs": 1, + "timeout": 0, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [ + { + "var": "fs", + "module": "fs" + } + ], + "x": 930, + "y": 180, + "wires": [ + [ + "aa474626711fa5b6" + ] + ] + }, + { + "id": "2133c934126655d8", + "type": "ui-template", + "z": "a7825c4c81ad20a0", + "group": "0c1537ce71affc2b", + "page": "", + "ui": "", + "name": "Gallery", + "order": 2, + "width": 0, + "height": 0, + "head": "", + "format": "\n\n\n\n\n\n", + "storeOutMessages": true, + "passthru": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 920, + "y": 1000, + "wires": [ + [] + ] + }, + { + "id": "eb62a19058171fa8", + "type": "ui-template", + "z": "a7825c4c81ad20a0", + "group": "fa6393a7d7e3b7d7", + "page": "", + "ui": "", + "name": "List of Segmentation", + "order": 2, + "width": 0, + "height": 0, + "head": "", + "format": "\n\n\n\n", + "storeOutMessages": true, + "passthru": false, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 700, + "y": 180, + "wires": [ + [ + "63121349be3135fd", + "68ec210262cb873a" + ] + ] + }, + { + "id": "ce136c26883f5d99", + "type": "ui-template", + "z": "a7825c4c81ad20a0", + "group": "53490f9c39e2065d", + "page": "", + "ui": "", + "name": "Infos", + "order": 2, + "width": 0, + "height": 0, + "head": "", + "format": "\n\n\n\n\n", + "storeOutMessages": true, + "passthru": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 910, + "y": 440, + "wires": [ + [] + ] + }, + { + "id": "82d199f05a6f0af2", + "type": "function", + "z": "a7825c4c81ad20a0", + "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, + "timeout": 0, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 510, + "y": 520, + "wires": [ + [ + "fa3836bcb90f19fd" + ] + ] + }, + { + "id": "248999e928a14aa9", + "type": "function", + "z": "a7825c4c81ad20a0", + "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": 560, + "wires": [ + [ + "884695dfb99afa9c" + ] + ] + }, + { + "id": "09b02605b198b2f4", + "type": "function", + "z": "a7825c4c81ad20a0", + "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": 600, + "wires": [ + [ + "6d881dec33245014" + ] + ] + }, + { + "id": "897140ab8ebde04d", + "type": "function", + "z": "a7825c4c81ad20a0", + "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": 640, + "wires": [ + [ + "d7416bf20b4dc8bc" + ] + ] + }, + { + "id": "ea4aa3af0f18be63", + "type": "function", + "z": "a7825c4c81ad20a0", + "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": 680, + "wires": [ + [ + "14cc9379f5c5c333" + ] + ] + }, + { + "id": "42b7f7bec1d2da57", + "type": "function", + "z": "a7825c4c81ad20a0", + "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": 720, + "wires": [ + [ + "cd8b19e450d40ff7" + ] + ] + }, + { + "id": "09203894702cf830", + "type": "function", + "z": "a7825c4c81ad20a0", + "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": 760, + "wires": [ + [ + "5267a7f80cdf6c6f" + ] + ] + }, + { + "id": "db14c860ad4ab4ec", + "type": "function", + "z": "a7825c4c81ad20a0", + "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": 800, + "wires": [ + [ + "a6df52a13d3d310f" + ] + ] + }, + { + "id": "eb793dfbf316c8d9", + "type": "function", + "z": "a7825c4c81ad20a0", + "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": 840, + "wires": [ + [ + "31ba1239ad82a60c" + ] + ] + }, + { + "id": "fa3836bcb90f19fd", + "type": "ui-template", + "z": "a7825c4c81ad20a0", + "group": "90b33e458dd29d04", + "page": "", + "ui": "", + "name": "Heatmap", + "order": 2, + "width": 0, + "height": 0, + "head": "", + "format": "\n\n\n\n\n", + "storeOutMessages": true, + "passthru": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 920, + "y": 520, + "wires": [ + [] + ] + }, + { + "id": "63e7888198376e77", + "type": "function", + "z": "a7825c4c81ad20a0", + "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": [ + [ + "ce136c26883f5d99" + ] + ] + }, + { + "id": "884695dfb99afa9c", + "type": "ui-template", + "z": "a7825c4c81ad20a0", + "group": "47800358cf7cee25", + "page": "", + "ui": "", + "name": "ESD Histogram", + "order": 2, + "width": 0, + "height": 0, + "head": "", + "format": "\n\n", + "storeOutMessages": true, + "passthru": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 940, + "y": 560, + "wires": [ + [] + ] + }, + { + "id": "6d881dec33245014", + "type": "ui-template", + "z": "a7825c4c81ad20a0", + "group": "1d9c6927a0e0a71b", + "page": "", + "ui": "", + "name": "Timeline", + "order": 2, + "width": 0, + "height": 0, + "head": "", + "format": "\n\n\n", + "storeOutMessages": true, + "passthru": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 920, + "y": 600, + "wires": [ + [] + ] + }, + { + "id": "d7416bf20b4dc8bc", + "type": "ui-template", + "z": "a7825c4c81ad20a0", + "group": "03ad1c8f517e8769", + "page": "", + "ui": "", + "name": "Colorspace (Saturation vs Value)", + "order": 2, + "width": 0, + "height": 0, + "head": "", + "format": "\n\n\n\n\n", + "storeOutMessages": true, + "passthru": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 990, + "y": 640, + "wires": [ + [] + ] + }, + { + "id": "14cc9379f5c5c333", + "type": "ui-template", + "z": "a7825c4c81ad20a0", + "group": "0be15f8190e6fd43", + "page": "", + "ui": "", + "name": "Aspect (Width vs Height)", + "order": 2, + "width": 0, + "height": 0, + "head": "", + "format": "\n\n\n\n\n", + "storeOutMessages": true, + "passthru": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 970, + "y": 680, + "wires": [ + [] + ] + }, + { + "id": "cd8b19e450d40ff7", + "type": "ui-template", + "z": "a7825c4c81ad20a0", + "group": "1edc962c44888abc", + "page": "", + "ui": "", + "name": "Greenness", + "order": 2, + "width": 0, + "height": 0, + "head": "", + "format": "\n\n\n\n\n", + "storeOutMessages": true, + "passthru": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 930, + "y": 720, + "wires": [ + [] + ] + }, + { + "id": "5267a7f80cdf6c6f", + "type": "ui-template", + "z": "a7825c4c81ad20a0", + "group": "67c63cc92c23c4b1", + "page": "", + "ui": "", + "name": "Complexity (Area vs Perimeter)", + "order": 2, + "width": 0, + "height": 0, + "head": "", + "format": "\n\n\n\n\n", + "storeOutMessages": true, + "passthru": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 990, + "y": 760, + "wires": [ + [] + ] + }, + { + "id": "a6df52a13d3d310f", + "type": "ui-template", + "z": "a7825c4c81ad20a0", + "group": "95a152a68b7ba779", + "page": "", + "ui": "", + "name": "Texture (Area vs Std)", + "order": 2, + "width": 0, + "height": 0, + "head": "", + "format": "\n\n\n\n\n", + "storeOutMessages": true, + "passthru": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 960, + "y": 800, + "wires": [ + [] + ] + }, + { + "id": "31ba1239ad82a60c", + "type": "ui-template", + "z": "a7825c4c81ad20a0", + "group": "1baa943e505febf1", + "page": "", + "ui": "", + "name": "Solidity Histogram", + "order": 2, + "width": 0, + "height": 0, + "head": "", + "format": "\n\n\n\n\n", + "storeOutMessages": true, + "passthru": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 950, + "y": 840, + "wires": [ + [] + ] + }, + { + "id": "da58d147efe428ea", + "type": "function", + "z": "a7825c4c81ad20a0", + "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 // Extract pixel size (microns per pixel) - always needed for scale bar rendering\n const px = parseFloat(val(firstRow, \"process_pixel\"));\n if (!isNaN(px) && px > 0) {\n meta.resolution = px;\n }\n // Flag: if true, measurement values (width, height, area, etc.) are already in microns\n const applied = val(firstRow, \"process_pixel_applied\");\n meta.process_pixel_applied = (applied && applied.toString().toLowerCase() === \"true\");\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\", \"object_blur_laplacian\"\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: msg.tsvDir ? `/api/files/objects/${msg.tsvDir}/${val(row,\"img_file_name\")}` : `/api/files/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\n// Cap rows to prevent browser crash on huge datasets\nconst MAX_ROWS = 5000;\nif (data.length > MAX_ROWS) data.length = MAX_ROWS;\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": 920, + "wires": [ + [ + "3d4c586bf5742617" + ] + ] + }, + { + "id": "3d4c586bf5742617", + "type": "ui-template", + "z": "a7825c4c81ad20a0", + "group": "c5651e1a3e56f3f5", + "page": "", + "ui": "", + "name": "Explorer", + "order": 2, + "width": 0, + "height": 0, + "head": "", + "format": "\n\n\n\n", + "storeOutMessages": true, + "passthru": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 920, + "y": 920, + "wires": [ + [] + ] + }, + { + "id": "26ec7d57f8231752", + "type": "function", + "z": "a7825c4c81ad20a0", + "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": [ + [ + "fa3836bcb90f19fd", + "884695dfb99afa9c", + "6d881dec33245014", + "d7416bf20b4dc8bc", + "14cc9379f5c5c333", + "cd8b19e450d40ff7", + "5267a7f80cdf6c6f", + "a6df52a13d3d310f", + "31ba1239ad82a60c", + "ce136c26883f5d99", + "3d4c586bf5742617", + "2133c934126655d8" + ] + ] + }, + { + "id": "f8b996c6c3c8e77a", + "type": "function", + "z": "a7825c4c81ad20a0", + "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 // Extract pixel size (microns per pixel) - always needed for scale bar rendering\n const px = parseFloat(get(firstRow, 'process_pixel'));\n if (!isNaN(px) && px > 0) {\n meta.resolution = px;\n }\n // Flag: if true, measurement values (width, height, area, etc.) are already in microns\n const applied = get(firstRow, 'process_pixel_applied');\n meta.process_pixel_applied = (applied && applied.toString().toLowerCase() === \"true\");\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\", \"object_blur_laplacian\"\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: msg.tsvDir ? `/api/files/objects/${msg.tsvDir}/${get(row, 'img_file_name')}` : `/api/files/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\n// Cap rows to prevent browser crash on huge datasets\nconst MAX_ROWS = 5000;\nif (data.length > MAX_ROWS) data.length = MAX_ROWS;\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": 1000, + "wires": [ + [ + "2133c934126655d8" + ] + ] + }, + { + "id": "59c2b02e7b50171f", + "type": "delay", + "z": "a7825c4c81ad20a0", + "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": 340, + "wires": [ + [ + "1802e96dd833363b" + ] + ] + }, + { + "id": "8c6d06345732ad31", + "type": "ui-template", + "z": "a7825c4c81ad20a0", + "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": "aa474626711fa5b6", + "type": "switch", + "z": "a7825c4c81ad20a0", + "name": "", + "property": "topic", + "propertyType": "msg", + "rules": [ + { + "t": "neq", + "v": "$pageview", + "vt": "str" + } + ], + "checkall": "true", + "repair": false, + "outputs": 1, + "x": 170, + "y": 300, + "wires": [ + [ + "59c2b02e7b50171f" + ] + ] + }, + { + "id": "a351f0483ac3a747", + "type": "function", + "z": "a7825c4c81ad20a0", + "name": "Sample Identity Metadata Only", + "func": "// FUNCTION: Extract all data and metadata\nconst lines = msg.payload.toString().trim().split(/\\r?\\n/);\nif (lines.length < 2) return null;\n\nconst headers = lines[0].split(\"\\t\");\n\n// Fonction utilitaire pour mapper les colonnes\nconst rowToObject = (rowCells) => {\n const obj = {};\n headers.forEach((header, index) => {\n obj[header] = rowCells[index] || \"\";\n });\n return obj;\n};\n\nlet allRows = [];\nlet firstDataRow = null;\n\n// --- 1. Parser toutes les lignes ---\nfor (let i = 1; i < lines.length; i++) {\n const cells = lines[i].split(\"\\t\");\n const rowObj = rowToObject(cells);\n\n allRows.push(rowObj);\n\n // Identifier la première vraie ligne de données pour les métadonnées globales\n if (!firstDataRow && !lines[i].trim().startsWith(\"[\")) {\n firstDataRow = rowObj;\n }\n}\n\n// --- 2. Extraire les métadonnées (basées sur la première ligne valide) ---\nconst meta = {\n sample_id: firstDataRow ? firstDataRow[\"sample_id\"] : \"\",\n project: firstDataRow ? firstDataRow[\"sample_project\"] : \"\",\n acq_id: firstDataRow ? firstDataRow[\"acq_id\"] : \"\"\n};\n\n// --- 3. Output : Métadonnées + l'intégralité des données ---\nmsg.payload = {\n meta: meta,\n data: allRows // Contient maintenant tous les enregistrements\n};\n\nreturn msg;", + "outputs": 1, + "timeout": 0, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 510, + "y": 340, + "wires": [ + [] + ] + }, + { + "id": "68ec210262cb873a", + "type": "debug", + "z": "a7825c4c81ad20a0", + "name": "debug 3", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "false", + "statusVal": "", + "statusType": "auto", + "x": 960, + "y": 140, + "wires": [] + }, + { + "id": "adc7e075bda50785", + "type": "debug", + "z": "a7825c4c81ad20a0", + "name": "debug 6", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "false", + "statusVal": "", + "statusType": "auto", + "x": 380, + "y": 140, + "wires": [] + }, + { + "id": "99d07bf34db5693f", + "type": "debug", + "z": "a7825c4c81ad20a0", + "name": "debug 7", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "false", + "statusVal": "", + "statusType": "auto", + "x": 660, + "y": 60, + "wires": [] + }, + { + "id": "314a079a9f687b5f", + "type": "ui-template", + "z": "6fac9fa6a894b293", + "group": "822cdc5b6ef13f39", + "page": "", + "ui": "", + "name": "body", + "order": 1, + "width": "0", + "height": "0", + "head": "", + "format": "\n", + "storeOutMessages": true, + "passthru": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 490, + "y": 320, + "wires": [ + [] + ] + }, + { + "id": "4f3bb4938e8a4db6", + "type": "ui-template", + "z": "6fac9fa6a894b293", + "group": "c29c835a70533b69", + "page": "", + "ui": "", + "name": "header", + "order": 1, + "width": "12", + "height": "6", + "head": "", + "format": "\n", + "storeOutMessages": true, + "passthru": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 490, + "y": 180, + "wires": [ + [] + ] + }, + { + "id": "aa79e9d1d4c21a2b", + "type": "mqtt out", + "z": "6fac9fa6a894b293", + "name": "", + "topic": "", + "qos": "", + "retain": "", + "broker": "8dc3722c.06efa8", + "x": 710, + "y": 320, + "wires": [] + }, + { + "id": "2ca7d039baf0651d", + "type": "inject", + "z": "6fac9fa6a894b293", + "name": "", + "props": [ + { + "p": "topic", + "vt": "str" + }, + { + "p": "payload" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "calibration", + "payload": "{ \"action\": \"start\" }", + "payloadType": "json", + "x": 290, + "y": 280, + "wires": [ + [ + "97634d7d17fefdeb" + ] + ] + }, + { + "id": "97634d7d17fefdeb", + "type": "ui-template", + "z": "6fac9fa6a894b293", + "group": "c29c835a70533b69", + "page": "", + "ui": "", + "name": "Calibration", + "order": 2, + "width": "0", + "height": "0", + "head": "", + "format": "\n\n\n\n", + "storeOutMessages": true, + "passthru": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 510, + "y": 280, + "wires": [ + [ + "aa79e9d1d4c21a2b", + "3ec78b1561245371" + ] + ] + }, + { + "id": "3ec78b1561245371", + "type": "function", + "z": "6fac9fa6a894b293", + "name": "set light settings", + "func": "// On vérifie si le message contient les propriétés attendues\n// avant de mettre à jour les variables globales.\n\nif (msg.payload) {\n\n // Si led_status est présent dans le payload, on stocke\n if (msg.payload.led_status !== undefined) {\n global.set(\"led_status\", msg.payload.led_status);\n }\n\n // Si calibration_led_intensity est présent dans le payload, on stocke\n if (msg.payload.calibration_led_intensity !== undefined) {\n global.set(\"calibration_led_intensity\", msg.payload.calibration_led_intensity);\n }\n}\n\nreturn msg;", + "outputs": 1, + "timeout": "", + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 740, + "y": 280, + "wires": [ + [] + ] + }, + { + "id": "f5ecfcfd42bb61f0", + "type": "ui-template", + "z": "d70bc885c4bf46d4", + "g": "c398718e1269dc31", + "group": "af8acdfe9afbad74", + "page": "", + "ui": "", + "name": "Step Bar", + "order": 1, + "width": 0, + "height": 0, + "head": "", + "format": "\n\n\n", + "storeOutMessages": true, + "passthru": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 1080, + "y": 60, + "wires": [ + [ + "c1c7cc675878b0f3" + ] + ] + }, + { + "id": "c1c7cc675878b0f3", + "type": "ui-control", + "z": "d70bc885c4bf46d4", + "g": "c398718e1269dc31", + "name": "", + "ui": "e6ae26617c24c3ea", + "events": "all", + "x": 1280, + "y": 60, + "wires": [ + [] + ] + }, + { + "id": "90e29d79c8074c17", + "type": "ui-event", + "z": "d70bc885c4bf46d4", + "ui": "e6ae26617c24c3ea", + "name": "UI Event", + "x": 80, + "y": 40, + "wires": [ + [ + "82a9fd78e0554323" + ] + ] + }, + { + "id": "82a9fd78e0554323", + "type": "switch", + "z": "d70bc885c4bf46d4", + "name": "msg.payload.page.path === \"/calibration_saturation_level\"", + "property": "payload.page.path", + "propertyType": "msg", + "rules": [ + { + "t": "eq", + "v": "/calibration_saturation_level", + "vt": "str" + } + ], + "checkall": "true", + "repair": false, + "outputs": 1, + "x": 380, + "y": 40, + "wires": [ + [ + "6219b9a464bc8109" + ] + ] + }, + { + "id": "6219b9a464bc8109", + "type": "function", + "z": "d70bc885c4bf46d4", + "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": [ + [ + "10c29a65226c0da4" + ] + ] + }, + { + "id": "10951f5abc907b1d", + "type": "mqtt out", + "z": "d70bc885c4bf46d4", + "name": "", + "topic": "", + "qos": "", + "retain": "", + "broker": "8dc3722c.06efa8", + "x": 850, + "y": 100, + "wires": [] + }, + { + "id": "d1b0704bd6aeb092", + "type": "function", + "z": "d70bc885c4bf46d4", + "name": "set wb 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;", + "outputs": 1, + "timeout": "", + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 980, + "y": 180, + "wires": [ + [] + ] + }, + { + "id": "2da968133f63a1fe", + "type": "switch", + "z": "d70bc885c4bf46d4", + "name": "", + "property": "topic", + "propertyType": "msg", + "rules": [ + { + "t": "eq", + "v": "imager/image", + "vt": "str" + } + ], + "checkall": "true", + "repair": false, + "outputs": 1, + "x": 590, + "y": 140, + "wires": [ + [ + "d1b0704bd6aeb092", + "10951f5abc907b1d" + ] + ] + }, + { + "id": "10c29a65226c0da4", + "type": "ui-template", + "z": "d70bc885c4bf46d4", + "group": "af8acdfe9afbad74", + "page": "", + "ui": "", + "name": "Calibration - WB", + "order": 2, + "width": "0", + "height": "0", + "head": "", + "format": "\n\n\n\n", + "storeOutMessages": true, + "passthru": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 380, + "y": 140, + "wires": [ + [ + "2da968133f63a1fe" + ] + ] + }, + { + "id": "5331fa94d1b90654", + "type": "ui-template", + "z": "9dfa8db7ce31208f", + "g": "3f734d83a7327043", + "group": "728c7ed3d63dfcee", + "page": "", + "ui": "", + "name": "Step Bar", + "order": 1, + "width": 0, + "height": 0, + "head": "", + "format": "\n\n\n", + "storeOutMessages": true, + "passthru": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 1080, + "y": 60, + "wires": [ + [ + "5f823fb3b1c49c65" + ] + ] + }, + { + "id": "5f823fb3b1c49c65", + "type": "ui-control", + "z": "9dfa8db7ce31208f", + "g": "3f734d83a7327043", + "name": "", + "ui": "e6ae26617c24c3ea", + "events": "all", + "x": 1280, + "y": 60, + "wires": [ + [] + ] + }, + { + "id": "bd3b1d9f854c367c", + "type": "ui-event", + "z": "9dfa8db7ce31208f", + "ui": "e6ae26617c24c3ea", + "name": "UI Event", + "x": 80, + "y": 40, + "wires": [ + [ + "bff2be213a3b76b2" + ] + ] + }, + { + "id": "bff2be213a3b76b2", + "type": "switch", + "z": "9dfa8db7ce31208f", + "name": "msg.payload.page.path === \"/calibration_lightness\"", + "property": "payload.page.path", + "propertyType": "msg", + "rules": [ + { + "t": "eq", + "v": "/calibration_lightness", + "vt": "str" + } + ], + "checkall": "true", + "repair": false, + "outputs": 1, + "x": 360, + "y": 40, + "wires": [ + [ + "43220ad00872b995" + ] + ] + }, + { + "id": "43220ad00872b995", + "type": "function", + "z": "9dfa8db7ce31208f", + "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": [ + [ + "c9a6d6f19ff18dda" + ] + ] + }, + { + "id": "c9a6d6f19ff18dda", + "type": "ui-template", + "z": "9dfa8db7ce31208f", + "group": "728c7ed3d63dfcee", + "page": "", + "ui": "", + "name": "Calibration - Lightness", + "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": [ + [ + "b067a2833aee498d" + ] + ] + }, + { + "id": "371de3becc1c4e80", + "type": "mqtt out", + "z": "9dfa8db7ce31208f", + "name": "", + "topic": "", + "qos": "", + "retain": "", + "broker": "8dc3722c.06efa8", + "x": 850, + "y": 100, + "wires": [] + }, + { + "id": "b067a2833aee498d", + "type": "switch", + "z": "9dfa8db7ce31208f", + "name": "", + "property": "topic", + "propertyType": "msg", + "rules": [ + { + "t": "eq", + "v": "light", + "vt": "str" + } + ], + "checkall": "true", + "repair": false, + "outputs": 1, + "x": 590, + "y": 140, + "wires": [ + [ + "371de3becc1c4e80" + ] + ] + }, + { + "id": "9d60b1bdff1a8058", + "type": "ui-template", + "z": "e597cdc71d5a9d33", + "g": "f2d9c0f9aabc0a63", + "group": "c966455a52d121c0", + "page": "", + "ui": "", + "name": "Step Bar", + "order": 1, + "width": 0, + "height": 0, + "head": "", + "format": "\n\n\n", + "storeOutMessages": true, + "passthru": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 1080, + "y": 60, + "wires": [ + [ + "48d61ea72d468010" + ] + ] + }, + { + "id": "48d61ea72d468010", + "type": "ui-control", + "z": "e597cdc71d5a9d33", + "g": "f2d9c0f9aabc0a63", + "name": "", + "ui": "e6ae26617c24c3ea", + "events": "all", + "x": 1280, + "y": 60, + "wires": [ + [] + ] + }, + { + "id": "f4884f6f1ea5cdb5", + "type": "ui-template", + "z": "e597cdc71d5a9d33", + "group": "c966455a52d121c0", + "page": "", + "ui": "", + "name": "Calibration - Pixel Size", + "order": 2, + "width": "0", + "height": "0", + "head": "", + "format": "\n\n\n\n\n\n", + "storeOutMessages": true, + "passthru": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 400, + "y": 140, + "wires": [ + [ + "0327248fda7c3f7e" + ] + ] + }, + { + "id": "472f4ba991a77e43", + "type": "ui-event", + "z": "e597cdc71d5a9d33", + "ui": "e6ae26617c24c3ea", + "name": "UI Event", + "x": 80, + "y": 40, + "wires": [ + [ + "60b4a20faf7a015e" + ] + ] + }, + { + "id": "60b4a20faf7a015e", + "type": "switch", + "z": "e597cdc71d5a9d33", + "name": "msg.payload.page.path === \"/calibration_pixel_size\"", + "property": "payload.page.path", + "propertyType": "msg", + "rules": [ + { + "t": "eq", + "v": "/calibration_pixel_size", + "vt": "str" + } + ], + "checkall": "true", + "repair": false, + "outputs": 1, + "x": 360, + "y": 40, + "wires": [ + [ + "3e2441107949295a" + ] + ] + }, + { + "id": "3e2441107949295a", + "type": "function", + "z": "e597cdc71d5a9d33", + "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": [ + [ + "f4884f6f1ea5cdb5" + ] + ] + }, + { + "id": "0327248fda7c3f7e", + "type": "switch", + "z": "e597cdc71d5a9d33", + "name": "", + "property": "topic", + "propertyType": "msg", + "rules": [ + { + "t": "eq", + "v": "calibration/save", + "vt": "str" + } + ], + "checkall": "true", + "repair": false, + "outputs": 1, + "x": 590, + "y": 140, + "wires": [ + [ + "133c27ef75317205" + ] + ] + }, + { + "id": "133c27ef75317205", + "type": "function", + "z": "e597cdc71d5a9d33", + "name": "set calibration_pixel_size", + "func": "if (msg.topic) {\n global.set(\"process_pixel\", msg.payload.calibration_pixel_size);\n global.set(\"process_pixel_size\", msg.payload.calibration_pixel_size);\n global.set(\"calibration_pixel_size\", msg.payload.calibration_pixel_size);\n global.set(\"calibration_scale_factor\", msg.payload.calibration_scale_factor);\n global.set(\"calibration_sensor_width\", msg.payload.calibration_sensor_width);\n global.set(\"calibration_stream_width\", msg.payload.calibration_stream_width);\n global.set(\"calibration_known_distance\", msg.payload.calibration_known_distance);\n global.set(\"calibration_measured_distance\", msg.payload.calibration_measured_distance);\n global.set(\"calibration_markerA_x\", msg.payload.calibration_markerA_x);\n global.set(\"calibration_markerA_y\", msg.payload.calibration_markerA_y);\n global.set(\"calibration_markerB_x\", msg.payload.calibration_markerB_x);\n global.set(\"calibration_markerB_y\", msg.payload.calibration_markerB_y);\n}\nreturn msg;", + "outputs": 1, + "timeout": "", + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 1010, + "y": 180, + "wires": [ + [] + ] + }, + { + "id": "07e989594965ab7d", + "type": "ui-template", + "z": "b8e6b9dc69aa3f56", + "g": "a75dd1f78f8f991a", + "group": "6039d653304537af", + "page": "", + "ui": "", + "name": "Step Bar", + "order": 1, + "width": 0, + "height": 0, + "head": "", + "format": "\n\n\n", + "storeOutMessages": true, + "passthru": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 1080, + "y": 60, + "wires": [ + [ + "5e94860de36e0a1c" + ] + ] + }, + { + "id": "5e94860de36e0a1c", + "type": "ui-control", + "z": "b8e6b9dc69aa3f56", + "g": "a75dd1f78f8f991a", + "name": "", + "ui": "e6ae26617c24c3ea", + "events": "all", + "x": 1280, + "y": 60, + "wires": [ + [] + ] + }, + { + "id": "3430afb16e1eccf0", + "type": "ui-event", + "z": "b8e6b9dc69aa3f56", + "ui": "e6ae26617c24c3ea", + "name": "UI Event", + "x": 80, + "y": 40, + "wires": [ + [ + "4d3f1761bfaea85b" + ] + ] + }, + { + "id": "4d3f1761bfaea85b", + "type": "switch", + "z": "b8e6b9dc69aa3f56", + "name": "msg.payload.page.path === \"/calibration_pump\"", + "property": "payload.page.path", + "propertyType": "msg", + "rules": [ + { + "t": "eq", + "v": "/calibration_pump", + "vt": "str" + } + ], + "checkall": "true", + "repair": false, + "outputs": 1, + "x": 350, + "y": 40, + "wires": [ + [ + "d578e28343d8286b" + ] + ] + }, + { + "id": "d578e28343d8286b", + "type": "function", + "z": "b8e6b9dc69aa3f56", + "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": [ + [ + "1c11c424e6c0c405", + "33f590e854486a56" + ] + ] + }, + { + "id": "33f590e854486a56", + "type": "ui-template", + "z": "b8e6b9dc69aa3f56", + "group": "6039d653304537af", + "page": "", + "ui": "", + "name": "Calibration - Pump", + "order": 2, + "width": "0", + "height": "0", + "head": "", + "format": "\n\n\n\n", + "storeOutMessages": true, + "passthru": true, + "resendOnRefresh": true, + "templateScope": "local", + "className": "", + "x": 390, + "y": 140, + "wires": [ + [ + "bc5e30bc7ed09916" + ] + ] + }, + { + "id": "27489e08148cc627", + "type": "mqtt out", + "z": "b8e6b9dc69aa3f56", + "name": "", + "topic": "", + "qos": "", + "retain": "", + "broker": "8dc3722c.06efa8", + "x": 850, + "y": 100, + "wires": [] + }, + { + "id": "bc5e30bc7ed09916", + "type": "switch", + "z": "b8e6b9dc69aa3f56", + "name": "", + "property": "topic", + "propertyType": "msg", + "rules": [ + { + "t": "eq", + "v": "actuator/pump", + "vt": "str" + }, + { + "t": "eq", + "v": "calibration/save", + "vt": "str" + } + ], + "checkall": "true", + "repair": false, + "outputs": 2, + "x": 590, + "y": 140, + "wires": [ + [ + "27489e08148cc627" + ], + [ + "6f3a85a2de771d90" + ] + ] + }, + { + "id": "495701d383cde0e8", + "type": "mqtt in", + "z": "b8e6b9dc69aa3f56", + "name": "", + "topic": "status/pump", + "qos": "0", + "datatype": "json", + "broker": "8dc3722c.06efa8", + "nl": false, + "rap": false, + "inputs": 0, + "x": 90, + "y": 340, + "wires": [ + [ + "33f590e854486a56" + ] + ] + }, + { + "id": "6f3a85a2de771d90", + "type": "function", + "z": "b8e6b9dc69aa3f56", + "name": "set calibration_pump", + "func": "if (msg.topic) {\n global.set(\"calibration_nb_step\", msg.payload.calibration_nb_step);\n}\nreturn msg;\n", + "outputs": 1, + "timeout": "", + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 860, + "y": 200, + "wires": [ + [] + ] + }, + { + "id": "1c11c424e6c0c405", + "type": "debug", + "z": "b8e6b9dc69aa3f56", + "name": "debug 4", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "false", + "statusVal": "", + "statusType": "auto", + "x": 360, + "y": 280, + "wires": [] + }, + { + "id": "aa4f580d98195efa", + "type": "ecotaxa", + "z": "f8d7305edb3dce2c", + "name": "Import to Ecotaxa Project", + "api_url": "https://ecotaxa.obs-vlfr.fr/api/", + "project_id": "9366", + "x": 830, + "y": 300, + "wires": [ + [ + "3f2db75b735b1cea" + ] + ] + }, + { + "id": "56563df41429a63b", + "type": "inject", + "z": "f8d7305edb3dce2c", + "name": "Lancer import", + "props": [], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": "0", + "topic": "", + "x": 310, + "y": 300, + "wires": [ + [ + "5deb75226b490f72" + ] + ] + }, + { + "id": "3f2db75b735b1cea", + "type": "debug", + "z": "f8d7305edb3dce2c", + "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": "5deb75226b490f72", + "type": "function", + "z": "f8d7305edb3dce2c", + "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": [ + [ + "aa4f580d98195efa", + "8f63d2d9ba29a929" + ] + ] + }, + { + "id": "8f63d2d9ba29a929", + "type": "debug", + "z": "f8d7305edb3dce2c", + "name": "debug 8", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "false", + "statusVal": "", + "statusType": "auto", + "x": 580, + "y": 560, + "wires": [] + }, + { + "id": "655adc853a6956f6", + "type": "inject", + "z": "e6d820ba0f4f184e", + "g": "e25bae849bace7c6", + "name": "inject", + "props": [ + { + "p": "payload.timezone", + "v": "Europe/Paris", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "", + "x": 110, + "y": 280, + "wires": [ + [ + "c63068f9aa421f2c" + ] + ] + }, + { + "id": "fec9c2aca5437f85", + "type": "set timezone", + "z": "e6d820ba0f4f184e", + "g": "e25bae849bace7c6", + "name": "", + "x": 340, + "y": 380, + "wires": [ + [ + "5f77688682c97241" + ] + ] + }, + { + "id": "c63068f9aa421f2c", + "type": "list timezones", + "z": "e6d820ba0f4f184e", + "g": "e25bae849bace7c6", + "name": "", + "x": 140, + "y": 360, + "wires": [ + [ + "fec9c2aca5437f85" + ] + ] + }, + { + "id": "5f77688682c97241", + "type": "get timezone", + "z": "e6d820ba0f4f184e", + "g": "e25bae849bace7c6", + "name": "", + "x": 540, + "y": 400, + "wires": [ + [] + ] + }, + { + "id": "9975b01cde700ae1", + "type": "inject", + "z": "e6d820ba0f4f184e", + "g": "14cb9ab4b149e2b5", + "name": "inject", + "props": [ + { + "p": "payload.country", + "v": "FR", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "", + "x": 110, + "y": 60, + "wires": [ + [ + "b72d81f258ab6ed1" + ] + ] + }, + { + "id": "b72d81f258ab6ed1", + "type": "list countries", + "z": "e6d820ba0f4f184e", + "g": "14cb9ab4b149e2b5", + "name": "", + "x": 140, + "y": 140, + "wires": [ + [ + "8773c66a58ecab21" + ] + ] + }, + { + "id": "8773c66a58ecab21", + "type": "set country", + "z": "e6d820ba0f4f184e", + "g": "14cb9ab4b149e2b5", + "name": "", + "x": 330, + "y": 160, + "wires": [ + [ + "fe5a0dc78ca52e26" + ] + ] + }, + { + "id": "fe5a0dc78ca52e26", + "type": "get country", + "z": "e6d820ba0f4f184e", + "g": "14cb9ab4b149e2b5", + "name": "", + "x": 510, + "y": 180, + "wires": [ + [] + ] + }, + { + "id": "ec2b56583e1703f0", + "type": "inject", + "z": "e6d820ba0f4f184e", + "g": "e5f0bc40b541e13f", + "name": "inject", + "props": [], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "", + "x": 110, + "y": 700, + "wires": [ + [ + "078727b344f7e34a" + ] + ] + }, + { + "id": "078727b344f7e34a", + "type": "get hostname", + "z": "e6d820ba0f4f184e", + "g": "e5f0bc40b541e13f", + "name": "", + "x": 140, + "y": 760, + "wires": [ + [] + ] + }, + { + "id": "355d4990f8958d65", + "type": "get name", + "z": "e6d820ba0f4f184e", + "g": "329d175baf2183b1", + "name": "", + "x": 130, + "y": 920, + "wires": [ + [] + ] + }, + { + "id": "3058d95dae6eea1f", + "type": "inject", + "z": "e6d820ba0f4f184e", + "g": "329d175baf2183b1", + "name": "", + "props": [], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "", + "x": 110, + "y": 860, + "wires": [ + [ + "355d4990f8958d65" + ] + ] + }, + { + "id": "528f6ce911af8982", + "type": "storage info", + "z": "e6d820ba0f4f184e", + "g": "62930a749d014d90", + "name": "", + "x": 530, + "y": 920, + "wires": [ + [] + ] + }, + { + "id": "baac40d3e7a8fcb2", + "type": "inject", + "z": "e6d820ba0f4f184e", + "g": "62930a749d014d90", + "name": "inject", + "props": [], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "", + "x": 510, + "y": 860, + "wires": [ + [ + "528f6ce911af8982" + ] + ] + }, + { + "id": "4f9158d3ed8eadd3", + "type": "list hardware versions", + "z": "e6d820ba0f4f184e", + "g": "034aca701dd2a256", + "name": "", + "x": 170, + "y": 560, + "wires": [ + [ + "0820acd0d3b628d4" + ] + ] + }, + { + "id": "287f7112bac18c51", + "type": "get hardware version", + "z": "e6d820ba0f4f184e", + "g": "034aca701dd2a256", + "name": "", + "x": 660, + "y": 600, + "wires": [ + [] + ] + }, + { + "id": "0820acd0d3b628d4", + "type": "set hardware version", + "z": "e6d820ba0f4f184e", + "g": "034aca701dd2a256", + "name": "", + "x": 420, + "y": 580, + "wires": [ + [ + "287f7112bac18c51" + ] + ] + }, + { + "id": "327e3da2b7e9e50b", + "type": "inject", + "z": "e6d820ba0f4f184e", + "g": "034aca701dd2a256", + "name": "inject", + "props": [ + { + "p": "payload.hardware_version", + "v": "v3.0", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "", + "x": 110, + "y": 500, + "wires": [ + [ + "4f9158d3ed8eadd3" + ] + ] + }, + { + "id": "80b749ee1cbdf98e", + "type": "comment", + "z": "e6d820ba0f4f184e", + "g": "14cb9ab4b149e2b5", + "name": "country", + "info": "", + "x": 690, + "y": 60, + "wires": [] + }, + { + "id": "e49db28bb805280e", + "type": "comment", + "z": "e6d820ba0f4f184e", + "g": "e25bae849bace7c6", + "name": "timezone", + "info": "", + "x": 740, + "y": 280, + "wires": [] + }, + { + "id": "c80721a372673baf", + "type": "comment", + "z": "e6d820ba0f4f184e", + "g": "034aca701dd2a256", + "name": "hardware version", + "info": "", + "x": 840, + "y": 500, + "wires": [] + }, + { + "id": "3e333d969e41b545", + "type": "comment", + "z": "e6d820ba0f4f184e", + "g": "e5f0bc40b541e13f", + "name": "hostname", + "info": "", + "x": 300, + "y": 700, + "wires": [] + }, + { + "id": "a6c33b2025837058", + "type": "comment", + "z": "e6d820ba0f4f184e", + "g": "329d175baf2183b1", + "name": "name", + "info": "", + "x": 310, + "y": 860, + "wires": [] + }, + { + "id": "660f8462d674baea", + "type": "comment", + "z": "e6d820ba0f4f184e", + "g": "62930a749d014d90", + "name": "storage", + "info": "", + "x": 690, + "y": 860, + "wires": [] + }, + { + "id": "f941634f9acf1e65", + "type": "poweroff", + "z": "e6d820ba0f4f184e", + "g": "039d9046b10d6840", + "name": "", + "x": 1180, + "y": 140, + "wires": [ + [] + ] + }, + { + "id": "8d538b74ac03964b", + "type": "reboot", + "z": "e6d820ba0f4f184e", + "g": "039d9046b10d6840", + "name": "", + "x": 1180, + "y": 200, + "wires": [ + [] + ] + }, + { + "id": "3cb007c279dceed0", + "type": "inject", + "z": "e6d820ba0f4f184e", + "g": "039d9046b10d6840", + "name": "inject", + "props": [], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "", + "x": 1030, + "y": 140, + "wires": [ + [ + "f941634f9acf1e65" + ] + ] + }, + { + "id": "05d4bd56d2b42da1", + "type": "inject", + "z": "e6d820ba0f4f184e", + "g": "039d9046b10d6840", + "name": "inject", + "props": [], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "", + "x": 1030, + "y": 200, + "wires": [ + [ + "8d538b74ac03964b" + ] + ] + }, + { + "id": "8972c5887d7de2dc", + "type": "comment", + "z": "e6d820ba0f4f184e", + "g": "039d9046b10d6840", + "name": "system ⚠️", + "info": "", + "x": 1200, + "y": 80, + "wires": [] + }, + { + "id": "13bdc8d028ab9063", + "type": "wake up", + "z": "e6d820ba0f4f184e", + "g": "039d9046b10d6840", + "name": "", + "minutes": "2", + "x": 1180, + "y": 260, + "wires": [ + [] + ] + }, + { + "id": "8283323ac1aca81a", + "type": "inject", + "z": "e6d820ba0f4f184e", + "g": "039d9046b10d6840", + "name": "inject", + "props": [], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "", + "x": 1030, + "y": 260, + "wires": [ + [ + "13bdc8d028ab9063" + ] + ] + }, + { + "id": "d25760aa70fd1524", + "type": "list acquisitions", + "z": "e6d820ba0f4f184e", + "g": "0a51b211ebbb9873", + "name": "", + "x": 1120, + "y": 600, + "wires": [ + [] + ] + }, + { + "id": "d5950445bf78c0c6", + "type": "inject", + "z": "e6d820ba0f4f184e", + "g": "0a51b211ebbb9873", + "name": "inject", + "props": [], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "", + "x": 1090, + "y": 540, + "wires": [ + [ + "d25760aa70fd1524" + ] + ] + }, + { + "id": "36a787c8463c3fab", + "type": "comment", + "z": "e6d820ba0f4f184e", + "g": "0a51b211ebbb9873", + "name": "acquisitions", + "info": "", + "x": 1310, + "y": 540, + "wires": [] + }, + { + "id": "9203a825fc1c63e4", + "type": "inject", + "z": "e6d820ba0f4f184e", + "g": "ef4667eb73b18227", + "name": "inject", + "props": [], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "", + "x": 1090, + "y": 700, + "wires": [ + [ + "9d2be08e52de0394" + ] + ] + }, + { + "id": "110801015a0ec067", + "type": "comment", + "z": "e6d820ba0f4f184e", + "g": "ef4667eb73b18227", + "name": "segmentations", + "info": "", + "x": 1300, + "y": 700, + "wires": [] + }, + { + "id": "9d2be08e52de0394", + "type": "list segmentations", + "z": "e6d820ba0f4f184e", + "g": "ef4667eb73b18227", + "name": "", + "x": 1130, + "y": 760, + "wires": [ + [] + ] + }, + { + "id": "3973105e8c349a38", + "type": "get machine info", + "z": "e6d820ba0f4f184e", + "g": "693296edc15d81c2", + "name": "", + "x": 150, + "y": 1080, + "wires": [ + [ + "d96066c662742119" + ] + ] + }, + { + "id": "b8a021de6b15a728", + "type": "inject", + "z": "e6d820ba0f4f184e", + "g": "693296edc15d81c2", + "name": "inject", + "props": [], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "", + "x": 110, + "y": 1020, + "wires": [ + [ + "3973105e8c349a38" + ] + ] + }, + { + "id": "e2c7db0aef7f64ef", + "type": "comment", + "z": "e6d820ba0f4f184e", + "g": "693296edc15d81c2", + "name": "machine info", + "info": "", + "x": 290, + "y": 1020, + "wires": [] + }, + { + "id": "47dce78fa2e5973b", + "type": "inject", + "z": "e6d820ba0f4f184e", + "g": "68e954becfafbf7b", + "name": "", + "props": [], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "", + "x": 1090, + "y": 860, + "wires": [ + [ + "9f0db647f3595ef9" + ] + ] + }, + { + "id": "9f0db647f3595ef9", + "type": "capture", + "z": "e6d820ba0f4f184e", + "g": "68e954becfafbf7b", + "name": "", + "x": 1100, + "y": 920, + "wires": [ + [] + ] + }, + { + "id": "e14a82906a749dc5", + "type": "comment", + "z": "e6d820ba0f4f184e", + "g": "68e954becfafbf7b", + "name": "camera", + "info": "", + "x": 1330, + "y": 860, + "wires": [] + }, + { + "id": "d96066c662742119", + "type": "debug", + "z": "e6d820ba0f4f184e", + "g": "693296edc15d81c2", + "name": "debug 10", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "false", + "statusVal": "", + "statusType": "auto", + "x": 470, + "y": 1100, + "wires": [] + }, + { + "id": "df6aac39f636d1d9", + "type": "ui-template", + "z": "f90406ba2da5932f", + "group": "", + "page": "", + "ui": "e6ae26617c24c3ea", + "name": "Favicon", + "order": 0, + "width": 0, + "height": 0, + "head": "", + "format": "", + "storeOutMessages": true, + "passthru": true, + "resend": true, + "templateScope": "widget:ui", + "className": "", + "x": 200, + "y": 200, + "wires": [ + [] + ] + }, + { + "id": "a1b2c3d4e5f60001", + "type": "switch", + "z": "524b7620431210e8", + "name": "Route queue topics", + "property": "topic", + "propertyType": "msg", + "rules": [ + { + "t": "eq", + "v": "queue_segment_request", + "vt": "str" + }, + { + "t": "eq", + "v": "queue_cancel", + "vt": "str" + }, + { + "t": "eq", + "v": "queue_cancel_all", + "vt": "str" + } + ], + "checkall": "true", + "repair": false, + "outputs": 3, + "x": 630, + "y": 340, + "wires": [ + [ + "a1b2c3d4e5f60002" + ], + [ + "a1b2c3d4e5f60002" + ], + [ + "a1b2c3d4e5f60002" + ] + ] + }, + { + "id": "a1b2c3d4e5f60002", + "type": "function", + "z": "524b7620431210e8", + "name": "Queue Processor", + "func": "// Queue Processor - manages the segmentation queue in flow context\n//\n// Inputs: queue_segment_request, queue_confirmed, queue_status_update,\n// queue_cancel, queue_cancel_all\n// Output 0: MQTT command -> existing segmenter/segment switch\n// Output 1: queue_status -> List of Acquisitions template\n// Output 2: dialog open -> add process_min_ESD -> Dialog\n\nconst topic = msg.topic;\nconst payload = msg.payload;\n\nlet queue = flow.get(\"seg_queue\") || [];\nlet current = flow.get(\"seg_queue_current\") || null;\nlet results = flow.get(\"seg_queue_results\") || [];\nlet active = flow.get(\"seg_queue_active\") || false;\nlet settings = flow.get(\"seg_queue_settings\") || null;\n\nfunction saveState() {\n flow.set(\"seg_queue\", queue);\n flow.set(\"seg_queue_current\", current);\n flow.set(\"seg_queue_results\", results);\n flow.set(\"seg_queue_active\", active);\n flow.set(\"seg_queue_settings\", settings);\n}\n\nfunction buildStatusMsg() {\n return {\n topic: \"queue_status\",\n payload: {\n active: active,\n current_path: current ? current.item.path : null,\n queued_paths: queue.map(q => q.item.path),\n results: results\n }\n };\n}\n\nfunction processNext() {\n if (queue.length === 0) {\n active = false;\n current = null;\n saveState();\n return [[null], [buildStatusMsg()], [null]];\n }\n\n current = queue.shift();\n saveState();\n\n const mqttMsg = {\n topic: \"segmenter/segment\",\n payload: {\n action: \"segment\",\n path: [current.item.path],\n settings: {\n force: false,\n recursive: true,\n ecotaxa: true,\n keep: true,\n process_id: 1,\n process_min_ESD: settings ? settings.process_min_ESD : 20,\n },\n dataset: {\n project_name: current.item.project_name,\n sample_id: current.item.sample_id,\n acquisition_id: current.item.acquisition_id,\n operator_name: current.item.operator_name,\n image_acquired_count: current.item.image_acquired_count,\n path: current.item.path,\n }\n }\n };\n\n return [[mqttMsg], [buildStatusMsg()], [null]];\n}\n\n// --- Handle incoming messages ---\n\nif (topic === \"queue_segment_request\") {\n // Multi-select request from table. Open batch dialog for ESD confirmation.\n flow.set(\"seg_queue_pending_items\", payload.items);\n\n const representative = payload.items[0];\n const dialogMsg = {\n topic: \"segment_request\",\n payload: {\n ...representative,\n is_segmented: false,\n open_dialog: true,\n batch_mode: true,\n batch_count: payload.items.length,\n batch_items: payload.items,\n process_min_ESD: global.get(\"process_min_ESD\") || 20\n }\n };\n return [[null], [null], [dialogMsg]];\n\n} else if (topic === \"queue_confirmed\") {\n // Dialog confirmed with settings - start the queue\n const items = payload.items;\n const confirmedSettings = { process_min_ESD: payload.process_min_ESD };\n\n const toQueue = items.filter(item => !item.is_segmented);\n\n if (toQueue.length === 0) {\n return [[null], [buildStatusMsg()], [null]];\n }\n\n queue = toQueue.map(item => ({ item: item }));\n results = [];\n active = true;\n settings = confirmedSettings;\n saveState();\n\n return processNext();\n\n} else if (topic === \"queue_status_update\") {\n const status = (payload.status || \"\").toLowerCase();\n\n if (!active || !current) {\n return [[null], [null], [null]];\n }\n\n // Handle retry after busy\n if (flow.get(\"seg_queue_retry_pending\") && (status.includes(\"done\") || status.includes(\"ready\"))) {\n flow.set(\"seg_queue_retry_pending\", false);\n return processNext();\n }\n\n if (status.includes(\"done\")) {\n results.push({ path: current.item.path, acquisition_id: current.item.acquisition_id, status: \"done\" });\n current = null;\n saveState();\n return processNext();\n\n } else if (status.includes(\"exception\") || status.includes(\"error\")) {\n results.push({ path: current.item.path, acquisition_id: current.item.acquisition_id, status: \"error\", error: payload.status });\n current = null;\n saveState();\n return processNext();\n\n } else if (status.includes(\"interrupted\") || status.includes(\"dead\")) {\n const errMsg = status.includes(\"dead\") ? \"Segmenter process died\" : \"Interrupted\";\n results.push({ path: current.item.path, acquisition_id: current.item.acquisition_id, status: \"error\", error: errMsg });\n active = false;\n current = null;\n queue = [];\n saveState();\n return [[null], [buildStatusMsg()], [null]];\n\n } else if (status.includes(\"busy\")) {\n // Segmenter busy with another task. Re-enqueue current and wait.\n queue.unshift(current);\n current = null;\n flow.set(\"seg_queue_retry_pending\", true);\n saveState();\n return [[null], [buildStatusMsg()], [null]];\n }\n\n // Progress messages - just update status display\n return [[null], [buildStatusMsg()], [null]];\n\n} else if (topic === \"queue_cancel\") {\n queue = queue.filter(q => q.item.path !== payload.path);\n saveState();\n return [[null], [buildStatusMsg()], [null]];\n\n} else if (topic === \"queue_cancel_all\") {\n queue = [];\n saveState();\n return [[null], [buildStatusMsg()], [null]];\n}\n\nreturn [[null], [null], [null]];", + "outputs": 3, + "timeout": 0, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 850, + "y": 340, + "wires": [ + [ + "60ae065050fc2ad3" + ], + [ + "8a22543633712279" + ], + [ + "6e43a35e8dcf2b3b" + ] + ] + }, + { + "id": "a1b2c3d4e5f60003", + "type": "function", + "z": "524b7620431210e8", + "name": "Queue Status Handler", + "func": "// Queue Status Handler\n// Receives status/segmenter MQTT messages, forwards to Queue Processor\n// when queue is active. Also sends progress to the table.\n\nconst active = flow.get(\"seg_queue_active\") || false;\nconst current = flow.get(\"seg_queue_current\") || null;\nconst retryPending = flow.get(\"seg_queue_retry_pending\") || false;\n\nif (!active && !current && !retryPending) {\n return null;\n}\n\nconst queueMsg = {\n topic: \"queue_status_update\",\n payload: msg.payload\n};\n\nconst tableMsg = {\n topic: \"queue_progress\",\n payload: {\n ...msg.payload,\n current_path: current ? current.item.path : null\n }\n};\n\nreturn [[queueMsg], [tableMsg]];", + "outputs": 2, + "timeout": 0, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 370, + "y": 400, + "wires": [ + [ + "a1b2c3d4e5f60002" + ], + [ + "8a22543633712279" + ] + ] + }, + { + "id": "a1b2c3d4e5f60005", + "type": "function", + "z": "524b7620431210e8", + "name": "Queue Status Refresh", + "func": "// Queue Status Refresh - sends current queue state to the table on page load\n\nconst queue = flow.get(\"seg_queue\") || [];\nconst current = flow.get(\"seg_queue_current\") || null;\nconst results = flow.get(\"seg_queue_results\") || [];\nconst active = flow.get(\"seg_queue_active\") || false;\n\nmsg = {\n topic: \"queue_status\",\n payload: {\n active: active,\n current_path: current ? current.item.path : null,\n queued_paths: queue.map(q => q.item.path),\n results: results\n }\n};\nreturn msg;", + "outputs": 1, + "timeout": 0, + "noerr": 0, + "initialize": "", + "finalize": "", + "libs": [], + "x": 370, + "y": 460, + "wires": [ + [ + "8a22543633712279" + ] + ] } - }, - { - "id": "73070e06474249b4", - "type": "ui-page", - "name": "Metadata", - "ui": "e6ae26617c24c3ea", - "path": "/metadata", - "icon": "information-outline", - "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": 4, - "className": "", - "visible": "true", - "disabled": "false" - }, - { - "id": "dd015fda7456b9b0", - "type": "ui-group", - "name": "Navigation Top", - "page": "73070e06474249b4", - "width": "12", - "height": 1, - "order": 1, - "showTitle": false, - "className": "", - "visible": "true", - "disabled": "false", - "groupType": "default" - }, - { - "id": "69c772603e9cec0d", - "type": "ui-group", - "name": "Sample Information", - "page": "73070e06474249b4", - "width": "12", - "height": 1, - "order": 2, - "showTitle": true, - "className": "", - "visible": "true", - "disabled": "false", - "groupType": "default" - }, - { - "id": "6fab47af451c6d95", - "type": "ui-group", - "name": "Starting point", - "page": "73070e06474249b4", - "width": "6", - "height": 1, - "order": 3, - "showTitle": true, - "className": "", - "visible": "true", - "disabled": "false", - "groupType": "default" - }, - { - "id": "1cfe3062a8353012", - "type": "ui-group", - "name": "Ending point", - "page": "73070e06474249b4", - "width": "6", - "height": 1, - "order": 4, - "showTitle": true, - "className": "", - "visible": "true", - "disabled": "false", - "groupType": "default" - }, - { - "id": "cb3521392eb4e9ed", - "type": "ui-group", - "name": "Net Specificity", - "page": "73070e06474249b4", - "width": "12", - "height": 1, - "order": 5, - "showTitle": true, - "className": "", - "visible": true, - "disabled": false, - "groupType": "default" - }, - { - "id": "dd4d9707051041c9", - "type": "ui-group", - "name": "Other informations", - "page": "73070e06474249b4", - "width": "12", - "height": 1, - "order": 6, - "showTitle": true, - "className": "", - "visible": true, - "disabled": false, - "groupType": "default" - }, - { - "id": "7a4e042a60b734a6", - "type": "ui-page", - "name": "Segmentation", - "ui": "e6ae26617c24c3ea", - "path": "/segmentation", - "icon": "crop", - "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": 6, - "className": "", - "visible": "true", - "disabled": "false" - }, - { - "id": "bfd4acb7b243514f", - "type": "ui-group", - "name": "List of Acquisitions", - "page": "7a4e042a60b734a6", - "width": "12", - "height": 1, - "order": 3, - "showTitle": true, - "className": "", - "visible": "true", - "disabled": "false", - "groupType": "default" - }, - { - "id": "f22f627015431032", - "type": "ui-page", - "name": "Calibration - Lightness", - "ui": "e6ae26617c24c3ea", - "path": "/calibration_lightness", - "icon": "target-variant", - "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": 10, - "className": "", - "visible": "false", - "disabled": "false" - }, - { - "id": "728c7ed3d63dfcee", - "type": "ui-group", - "name": "Settings", - "page": "f22f627015431032", - "width": "12", - "height": "1", - "order": 1, - "showTitle": false, - "className": "", - "visible": "true", - "disabled": "false", - "groupType": "default" - }, - { - "id": "aa9ba6cedf56d2cd", - "type": "ui-page", - "name": "Calibration - Pixel size", - "ui": "e6ae26617c24c3ea", - "path": "/calibration_pixel_size", - "icon": "target-variant", - "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": 11, - "className": "", - "visible": "false", - "disabled": "false" - }, - { - "id": "c966455a52d121c0", - "type": "ui-group", - "name": "Settings", - "page": "aa9ba6cedf56d2cd", - "width": "12", - "height": "1", - "order": 1, - "showTitle": false, - "className": "", - "visible": "true", - "disabled": "false", - "groupType": "default" - }, - { - "id": "48216bc1f7c53a75", - "type": "ui-page", - "name": "Calibration - Pump", - "ui": "e6ae26617c24c3ea", - "path": "/calibration_pump", - "icon": "target-variant", - "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": 12, - "className": "", - "visible": "false", - "disabled": "false" - }, - { - "id": "6039d653304537af", - "type": "ui-group", - "name": "Settings", - "page": "48216bc1f7c53a75", - "width": "12", - "height": "1", - "order": 1, - "showTitle": false, - "className": "", - "visible": "true", - "disabled": "false", - "groupType": "default" - }, - { - "id": "803aa402d5c66b73", - "type": "ui-page", - "name": "Calibration - Saturation Level", - "ui": "e6ae26617c24c3ea", - "path": "/calibration_saturation_level", - "icon": "target-variant", - "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": 9, - "className": "", - "visible": "false", - "disabled": "false" - }, - { - "id": "af8acdfe9afbad74", - "type": "ui-group", - "name": "Settings", - "page": "803aa402d5c66b73", - "width": "12", - "height": "1", - "order": 1, - "showTitle": false, - "className": "", - "visible": "true", - "disabled": "false", - "groupType": "default" - }, - { - "id": "f8dd620721c6d70b", - "type": "ui-page", - "name": "Calibration", - "ui": "e6ae26617c24c3ea", - "path": "/calibration", - "icon": "mdi-tune", - "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": "c29c835a70533b69", - "type": "ui-group", - "name": "header", - "page": "f8dd620721c6d70b", - "width": "12", - "height": 1, - "order": 1, - "showTitle": false, - "className": "", - "visible": "true", - "disabled": "false", - "groupType": "default" - }, - { - "id": "822cdc5b6ef13f39", - "type": "ui-group", - "name": "body", - "page": "f8dd620721c6d70b", - "width": "12", - "height": 1, - "order": 2, - "showTitle": false, - "className": "", - "visible": "true", - "disabled": "false", - "groupType": "default" - }, - { - "id": "bcc78241b99ba27f", - "type": "ui-page", - "name": "Preview", - "ui": "e6ae26617c24c3ea", - "path": "/preview", - "icon": "mdi-eye", - "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": 3, - "className": "", - "visible": true, - "disabled": false - }, - { - "id": "58ab4d5e3dd68192", - "type": "ui-group", - "name": "Streaming", - "page": "bcc78241b99ba27f", - "width": "6", - "height": 1, - "order": 2, - "showTitle": false, - "className": "", - "visible": "true", - "disabled": "false", - "groupType": "default" - }, - { - "id": "39cbd2658f16d608", - "type": "ui-group", - "name": "Settings", - "page": "bcc78241b99ba27f", - "width": "6", - "height": 1, - "order": 3, - "showTitle": false, - "className": "", - "visible": "true", - "disabled": "false", - "groupType": "default" - }, - { - "id": "5c3e73c675caac42", - "type": "ui-page", - "name": "Acquisition", - "ui": "e6ae26617c24c3ea", - "path": "/acquisition", - "icon": "video-image", - "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": 5, - "className": "", - "visible": "true", - "disabled": "false" - }, - { - "id": "b274327af3807b79", - "type": "ui-group", - "name": "Streaming", - "page": "5c3e73c675caac42", - "width": "4", - "height": 1, - "order": 2, - "showTitle": false, - "className": "", - "visible": "true", - "disabled": "false", - "groupType": "default" - }, - { - "id": "d2f77573ed4317e4", - "type": "ui-group", - "name": "Acquisition settings", - "page": "5c3e73c675caac42", - "width": "8", - "height": 1, - "order": 3, - "showTitle": false, - "className": "", - "visible": "true", - "disabled": "false", - "groupType": "default" - }, - { - "id": "632260133d581caa", - "type": "ui-page", - "name": "Home", - "ui": "e6ae26617c24c3ea", - "path": "/home", - "icon": "home", - "layout": "grid", - "theme": "f7770f0b818c3a67", - "breakpoints": [ - { - "name": "Default", - "px": "0", - "cols": "3" - }, - { - "name": "Tablet", - "px": "576", - "cols": "3" - }, - { - "name": "Small Desktop", - "px": "768", - "cols": "12" - }, - { - "name": "Desktop", - "px": "1024", - "cols": "12" - } - ], - "order": 1, - "className": "", - "visible": "true", - "disabled": "false" - }, - { - "id": "5d39a98563150f22", - "type": "ui-group", - "name": "body", - "page": "632260133d581caa", - "width": "12", - "height": 1, - "order": 1, - "showTitle": false, - "className": "", - "visible": "true", - "disabled": "false", - "groupType": "default" - }, - { - "id": "7572915171e440cd", - "type": "ui-group", - "name": "Navigation Top", - "page": "bcc78241b99ba27f", - "width": "12", - "height": 1, - "order": 1, - "showTitle": false, - "className": "", - "visible": "true", - "disabled": "false", - "groupType": "default" - }, - { - "id": "3ae252a2e5abca89", - "type": "ui-group", - "name": "Navigation Top", - "page": "5c3e73c675caac42", - "width": "12", - "height": 1, - "order": 1, - "showTitle": false, - "className": "", - "visible": "true", - "disabled": "false", - "groupType": "default" - }, - { - "id": "9b992ca6515ed058", - "type": "ui-group", - "name": "Navigation Top", - "page": "7a4e042a60b734a6", - "width": "12", - "height": 1, - "order": 1, - "showTitle": false, - "className": "", - "visible": "true", - "disabled": "false", - "groupType": "default" - }, - { - "id": "d129fac8e7742d5b", - "type": "ui-page", - "name": "Visualization", - "ui": "e6ae26617c24c3ea", - "path": "/visualization", - "icon": "chart-scatter-plot", - "layout": "grid", - "theme": "f7770f0b818c3a67", - "breakpoints": [ - { - "name": "Default", - "px": "0", - "cols": "4" - }, - { - "name": "Tablet", - "px": "576", - "cols": "4" - }, - { - "name": "Small Desktop", - "px": "768", - "cols": "12" - }, - { - "name": "Desktop", - "px": "1024", - "cols": "12" - } - ], - "order": 8, - "className": "", - "visible": "true", - "disabled": "false" - }, - { - "id": "b570f76ef526af45", - "type": "ui-group", - "name": "Navigation Top", - "page": "d129fac8e7742d5b", - "width": "12", - "height": 1, - "order": 1, - "showTitle": false, - "className": "", - "visible": "true", - "disabled": "false", - "groupType": "default" - }, - { - "id": "402b3d24c87ab0d2", - "type": "ui-group", - "name": "Informations", - "page": "7a4e042a60b734a6", - "width": "12", - "height": 1, - "order": 2, - "showTitle": true, - "className": "", - "visible": "true", - "disabled": "false", - "groupType": "default" - }, - { - "id": "fa6393a7d7e3b7d7", - "type": "ui-group", - "name": "List of Segmentation", - "page": "d129fac8e7742d5b", - "width": "12", - "height": 1, - "order": 2, - "showTitle": true, - "className": "", - "visible": "true", - "disabled": "false", - "groupType": "default" - }, - { - "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": 2, - "className": "", - "visible": "true", - "disabled": "false" - }, - { - "id": "de680effc3e27451", - "type": "ui-group", - "name": "body", - "page": "34112984a39c35bd", - "width": "12", - "height": 1, - "order": 1, - "showTitle": false, - "className": "", - "visible": "true", - "disabled": "false", - "groupType": "default" - }, - { - "id": "c5651e1a3e56f3f5", - "type": "ui-group", - "name": "Explorer", - "page": "d129fac8e7742d5b", - "width": "12", - "height": 1, - "order": 13, - "showTitle": true, - "className": "", - "visible": "true", - "disabled": "false", - "groupType": "default" - }, - { - "id": "0c1537ce71affc2b", - "type": "ui-group", - "name": "Gallery", - "page": "d129fac8e7742d5b", - "width": "12", - "height": 1, - "order": 14, - "showTitle": false, - "className": "", - "visible": "true", - "disabled": "false", - "groupType": "default" - }, - { - "id": "36931a9722892790", - "type": "ui-group", - "name": "Software Version", - "page": "632260133d581caa", - "width": "3", - "height": 1, - "order": 6, - "showTitle": false, - "className": "", - "visible": "true", - "disabled": "false", - "groupType": "default" - }, - { - "id": "2aa235120084abe4", - "type": "ui-group", - "name": "Images Acquired", - "page": "632260133d581caa", - "width": "3", - "height": 1, - "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": "dc304678d9a6b53b", - "type": "ui-group", - "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": false, - "className": "", - "visible": "true", - "disabled": "false", - "groupType": "default" - }, - { - "id": "82480e386ed6f8bd", - "type": "ui-group", - "name": "Explore your data", - "page": "632260133d581caa", - "width": "4", - "height": 1, - "order": 3, - "showTitle": false, - "className": "", - "visible": "true", - "disabled": "false", - "groupType": "default" - }, - { - "id": "9d3e8bdd535f0e0d", - "type": "ui-group", - "name": "Run the Calibration", - "page": "632260133d581caa", - "width": "4", - "height": 1, - "order": 4, - "showTitle": false, - "className": "", - "visible": "true", - "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": "0f16258953fae292", - "type": "file in", - "z": "b7861ce703215a01", - "name": "", - "filename": "/home/pi/PlanktoScope/hardware.json", - "filenameType": "str", - "format": "utf8", - "chunk": false, - "sendError": false, - "encoding": "none", - "allProps": false, - "x": 250, - "y": 40, - "wires": [ - [ - "81c516291ab19acd" - ] - ], - "info": "# PlanktoScope Help\nThis Node will read the content of the file named **config.txt** containing all the input placeholders.\n" - }, - { - "id": "81c516291ab19acd", - "type": "json", - "z": "b7861ce703215a01", - "name": "Parse JSON", - "property": "payload", - "action": "", - "pretty": false, - "x": 510, - "y": 40, - "wires": [ - [ - "d0fbcd200cd09981" - ] - ] - }, - { - "id": "d0fbcd200cd09981", - "type": "change", - "z": "b7861ce703215a01", - "name": "", - "rules": [ - { - "t": "set", - "p": "hardware_conf", - "pt": "global", - "to": "payload", - "tot": "msg" - } - ], - "action": "", - "property": "", - "from": "", - "to": "", - "reg": false, - "x": 730, - "y": 40, - "wires": [ - [] - ] - }, - { - "id": "730b2780ac215a52", - "type": "file in", - "z": "63c85b96537b7355", - "name": "", - "filename": "/home/pi/PlanktoScope/config.json", - "filenameType": "str", - "format": "utf8", - "chunk": false, - "sendError": false, - "encoding": "none", - "x": 560, - "y": 60, - "wires": [ - [ - "e0b7238a0c5d4ed0" - ] - ], - "info": "# PlanktoScope Help\nThis Node will read the content of the file named **config.txt** containing all the input placeholders.\n" - }, - { - "id": "e0b7238a0c5d4ed0", - "type": "json", - "z": "63c85b96537b7355", - "name": "config.json", - "property": "payload", - "action": "", - "pretty": false, - "x": 730, - "y": 60, - "wires": [ - [ - "f1c0a882a9e3d927" - ] - ] - }, - { - "id": "b8109badf5f39d35", - "type": "file", - "z": "63c85b96537b7355", - "name": "", - "filename": "/home/pi/PlanktoScope/config.json", - "appendNewline": true, - "createDir": true, - "overwriteFile": "true", - "encoding": "none", - "x": 990, - "y": 160, - "wires": [ - [] - ] - }, - { - "id": "31ae9b857627673c", - "type": "json", - "z": "63c85b96537b7355", - "name": "config.json", - "property": "payload", - "action": "str", - "pretty": true, - "x": 730, - "y": 160, - "wires": [ - [ - "b8109badf5f39d35" - ] - ] - }, - { - "id": "f1c0a882a9e3d927", - "type": "function", - "z": "63c85b96537b7355", - "name": "Global Set", - "func": "global.set(\"config_keys\", Object.keys(msg.payload));\n\nfor (const key in msg.payload) {\n global.set(key, msg.payload[key]);\n}\n\nreturn msg;", - "outputs": 1, - "timeout": "", - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 910, - "y": 60, - "wires": [ - [] - ] - }, - { - "id": "24ea99bb02eeffa2", - "type": "inject", - "z": "63c85b96537b7355", - "name": "Load config", - "props": [ - { - "p": "topic", - "vt": "str" - } - ], - "repeat": "", - "crontab": "", - "once": true, - "onceDelay": 0.1, - "topic": "", - "payloadType": "str", - "x": 230, - "y": 60, - "wires": [ - [ - "730b2780ac215a52" - ] - ] - }, - { - "id": "5248e5e225d854d1", - "type": "function", - "z": "63c85b96537b7355", - "name": "get config payload", - "func": "keys = global.get(\"config_keys\")\n\nvar payload = {}\n\nkeys.forEach(function(item, index, array) {\n payload[item] = global.get(item);\n})\n\nreturn {\"payload\": payload};", - "outputs": 1, - "timeout": "", - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 410, - "y": 160, - "wires": [ - [ - "31ae9b857627673c" - ] - ] - }, - { - "id": "97f8a94e71055782", - "type": "ui_ui_control", - "z": "63c85b96537b7355", - "name": "Connect Event", - "events": "connect", - "x": 220, - "y": 100, - "wires": [ - [ - "730b2780ac215a52" - ] - ] - }, - { - "id": "852522a716e4156e", - "type": "switch", - "z": "b2bf18a0f40a5d72", - "name": "msg.payload.page.path === \"/setup\"", - "property": "payload.page.path", - "propertyType": "msg", - "rules": [ - { - "t": "eq", - "v": "/setup", - "vt": "str" - } - ], - "checkall": "true", - "repair": false, - "outputs": 1, - "x": 310, - "y": 40, - "wires": [ - [ - "7af8cd00e8938727" - ] - ] - }, - { - "id": "49857517dd9e15f7", - "type": "ui-event", - "z": "b2bf18a0f40a5d72", - "ui": "e6ae26617c24c3ea", - "name": "UI Event", - "x": 80, - "y": 40, - "wires": [ - [ - "852522a716e4156e" - ] - ] - }, - { - "id": "fc5a4760cc3ea824", - "type": "ui-template", - "z": "b2bf18a0f40a5d72", - "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": 310, - "y": 180, - "wires": [ - [ - "c36ed08fac3fccc0", - "0b71d7e104148283" - ] - ] - }, - { - "id": "cf5275898273cd61", - "type": "ui-template", - "z": "b2bf18a0f40a5d72", - "group": "", - "page": "34112984a39c35bd", - "ui": "", - "name": "CSS (All Pages)", - "order": 0, - "width": 0, - "height": 0, - "head": "", - "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, - "templateScope": "page:style", - "className": "", - "x": 940, - "y": 60, - "wires": [ - [] - ] - }, - { - "id": "c36ed08fac3fccc0", - "type": "function", - "z": "b2bf18a0f40a5d72", - "name": "set general settings", - "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": 710, - "y": 140, - "wires": [ - [] - ] - }, - { - "id": "7af8cd00e8938727", - "type": "read setup", - "z": "b2bf18a0f40a5d72", - "name": "", - "x": 150, - "y": 180, - "wires": [ - [ - "fc5a4760cc3ea824" - ] - ] - }, - { - "id": "6c2762dad373862b", - "type": "write setup", - "z": "b2bf18a0f40a5d72", - "name": "", - "x": 670, - "y": 240, - "wires": [ - [] - ] - }, - { - "id": "0b71d7e104148283", - "type": "switch", - "z": "b2bf18a0f40a5d72", - "name": "", - "property": "topic", - "propertyType": "msg", - "rules": [ - { - "t": "eq", - "v": "setup/saved", - "vt": "str" - } - ], - "checkall": "true", - "repair": false, - "outputs": 1, - "x": 470, - "y": 240, - "wires": [ - [ - "6c2762dad373862b" - ] - ] - }, - { - "id": "b3754a3e5ae5e0ed", - "type": "mqtt in", - "z": "b2bf18a0f40a5d72", - "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": [ - [ - "fc5a4760cc3ea824" - ] - ] - }, - { - "id": "d525e948816e36b6", - "type": "ui-template", - "z": "f90406ba2da5932f", - "group": "", - "page": "", - "ui": "e6ae26617c24c3ea", - "name": "footer", - "order": 1, - "width": 0, - "height": 0, - "head": "", - "format": "\n\n\n", - "storeOutMessages": true, - "passthru": true, - "resendOnRefresh": true, - "templateScope": "widget:ui", - "className": "", - "x": 510, - "y": 700, - "wires": [ - [] - ] - }, - { - "id": "2a99cafa2c36383b", - "type": "ui-event", - "z": "f90406ba2da5932f", - "ui": "e6ae26617c24c3ea", - "name": "UI Event", - "x": 80, - "y": 40, - "wires": [ - [ - "9df0c4431f8b8f31", - "ba7e7f61401c14d5", - "9dc57d3fe40c1d43" - ] - ] - }, - { - "id": "d6311499f71b7440", - "type": "ui-template", - "z": "f90406ba2da5932f", - "group": "", - "page": "", - "ui": "e6ae26617c24c3ea", - "name": "CSS (All Pages)", - "order": 0, - "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.v-row {\n margin: 0;\n}", - "storeOutMessages": true, - "passthru": true, - "resendOnRefresh": true, - "templateScope": "site:style", - "className": "", - "x": 1300, - "y": 40, - "wires": [ - [] - ] - }, - { - "id": "ba7e7f61401c14d5", - "type": "get machine info", - "z": "f90406ba2da5932f", - "name": "", - "x": 850, - "y": 40, - "wires": [ - [ - "362b4428729ebcac" - ] - ] - }, - { - "id": "9dc57d3fe40c1d43", - "type": "storage info", - "z": "f90406ba2da5932f", - "name": "", - "x": 830, - "y": 80, - "wires": [ - [ - "751e338fd9e7b365" - ] - ] - }, - { - "id": "751e338fd9e7b365", - "type": "function", - "z": "f90406ba2da5932f", - "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": "362b4428729ebcac", - "type": "function", - "z": "f90406ba2da5932f", - "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": "9df0c4431f8b8f31", - "type": "delay", - "z": "f90406ba2da5932f", - "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": 140, - "y": 180, - "wires": [ - [ - "7326fb7c646235e5" - ] - ] - }, - { - "id": "7326fb7c646235e5", - "type": "function", - "z": "f90406ba2da5932f", - "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": 320, - "y": 180, - "wires": [ - [ - "17d6595dfa9d0cee", - "f4c3442da54320e3", - "d0eacab77771ef43", - "ed7e8648519251ad", - "bfd0d1b9f4c7141c" - ] - ] - }, - { - "id": "825ecb1183edc16b", - "type": "poweroff", - "z": "f90406ba2da5932f", - "name": "", - "x": 1160, - "y": 240, - "wires": [ - [] - ] - }, - { - "id": "f0a002fc31fbe9f9", - "type": "reboot", - "z": "f90406ba2da5932f", - "name": "", - "x": 1160, - "y": 200, - "wires": [ - [] - ] - }, - { - "id": "9cbcd71938e6162d", - "type": "switch", - "z": "f90406ba2da5932f", - "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": [ - [ - "f0a002fc31fbe9f9" - ], - [ - "825ecb1183edc16b" - ] - ] - }, - { - "id": "d5cd40c81ac86e7a", - "type": "ui-template", - "z": "f90406ba2da5932f", - "group": "5d39a98563150f22", - "page": "", - "ui": "", - "name": "empty-state", - "order": 1, - "width": "0", - "height": "0", - "head": "", - "format": "\n", - "storeOutMessages": true, - "passthru": true, - "resendOnRefresh": true, - "templateScope": "local", - "className": "", - "x": 530, - "y": 280, - "wires": [ - [] - ] - }, - { - "id": "17d6595dfa9d0cee", - "type": "ui-template", - "z": "f90406ba2da5932f", - "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": 800, - "y": 200, - "wires": [ - [ - "9cbcd71938e6162d" - ] - ] - }, - { - "id": "f4c3442da54320e3", - "type": "ui-template", - "z": "f90406ba2da5932f", - "group": "36931a9722892790", - "page": "", - "ui": "", - "name": "Software Version", - "order": 1, - "width": "0", - "height": "0", - "head": "", - "format": "", - "storeOutMessages": true, - "passthru": true, - "resendOnRefresh": true, - "templateScope": "local", - "className": "", - "x": 550, - "y": 480, - "wires": [ - [] - ] - }, - { - "id": "d0eacab77771ef43", - "type": "ui-template", - "z": "f90406ba2da5932f", - "group": "2aa235120084abe4", - "page": "", - "ui": "", - "name": "Images Acquired", - "order": 1, - "width": "0", - "height": "0", - "head": "", - "format": "", - "storeOutMessages": true, - "passthru": true, - "resendOnRefresh": true, - "templateScope": "local", - "className": "", - "x": 550, - "y": 520, - "wires": [ - [] - ] - }, - { - "id": "ed7e8648519251ad", - "type": "ui-template", - "z": "f90406ba2da5932f", - "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": "bfd0d1b9f4c7141c", - "type": "ui-template", - "z": "f90406ba2da5932f", - "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": "b8d1e9bc6daa3cd0", - "type": "ui-template", - "z": "f90406ba2da5932f", - "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": "b298c238c9fb0b58", - "type": "ui-template", - "z": "f90406ba2da5932f", - "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": "e7f40c244808dbd4", - "type": "ui-template", - "z": "f90406ba2da5932f", - "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": "4369716b2a45c769", - "type": "ui-template", - "z": "f90406ba2da5932f", - "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": "a3a7ac5089d5d8e6", - "type": "ui-template", - "z": "f90406ba2da5932f", - "group": "", - "page": "632260133d581caa", - "ui": "", - "name": "CSS (This page)", - "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": "5fedc522b345193c", - "type": "ui-template", - "z": "6d6a011bf1913637", - "group": "58ab4d5e3dd68192", - "page": "", - "ui": "", - "name": "Streaming", - "order": 1, - "width": "0", - "height": "0", - "head": "", - "format": "\n\n", - "storeOutMessages": true, - "passthru": true, - "resendOnRefresh": true, - "templateScope": "local", - "className": "", - "x": 1310, - "y": 120, - "wires": [ - [] - ] - }, - { - "id": "8452566c16d26f45", - "type": "function", - "z": "6d6a011bf1913637", - "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": [ - [ - "db1ef284071e60a1" - ] - ] - }, - { - "id": "67e05ec4078fd100", - "type": "ui-event", - "z": "6d6a011bf1913637", - "ui": "e6ae26617c24c3ea", - "name": "UI Event", - "x": 80, - "y": 40, - "wires": [ - [ - "b1e37de2e8a49f29" - ] - ] - }, - { - "id": "b1e37de2e8a49f29", - "type": "switch", - "z": "6d6a011bf1913637", - "name": "msg.payload.page.path === \"/preview\"", - "property": "payload.page.path", - "propertyType": "msg", - "rules": [ - { - "t": "eq", - "v": "/preview", - "vt": "str" - } - ], - "checkall": "true", - "repair": false, - "outputs": 1, - "x": 320, - "y": 40, - "wires": [ - [ - "8452566c16d26f45" - ] - ] - }, - { - "id": "d119a802cf4c3f2d", - "type": "function", - "z": "6d6a011bf1913637", - "name": "set pump settings", - "func": "if (msg.topic) {\n global.set(\"pump_flowrate\", msg.payload.pump_flowrate);\n global.set(\"pump_manual_volume\", msg.payload.pump_manual_volume);\n}\nreturn msg;", - "outputs": 1, - "timeout": "", - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 790, - "y": 80, - "wires": [ - [] - ] - }, - { - "id": "8dfac8c9adafc76f", - "type": "function", - "z": "6d6a011bf1913637", - "name": "set focus settings", - "func": "if (msg.topic) {\n global.set(\"focus_distance\", msg.payload.focus_distance);\n\n}\nreturn msg;", - "outputs": 1, - "timeout": "", - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 790, - "y": 180, - "wires": [ - [] - ] - }, - { - "id": "24077d432cd6db62", - "type": "switch", - "z": "6d6a011bf1913637", - "name": "", - "property": "topic", - "propertyType": "msg", - "rules": [ - { - "t": "eq", - "v": "set_global/pump", - "vt": "str" - }, - { - "t": "eq", - "v": "actuator/pump", - "vt": "str" - }, - { - "t": "eq", - "v": "set_global/focus", - "vt": "str" - }, - { - "t": "eq", - "v": "actuator/focus", - "vt": "str" - }, - { - "t": "eq", - "v": "set_global/light", - "vt": "str" - }, - { - "t": "eq", - "v": "light", - "vt": "str" - }, - { - "t": "eq", - "v": "set_global/bubbler", - "vt": "str" - }, - { - "t": "eq", - "v": "actuator/bubbler", - "vt": "str" - }, - { - "t": "eq", - "v": "imager/image", - "vt": "str" - } - ], - "checkall": "true", - "repair": false, - "outputs": 9, - "x": 550, - "y": 220, - "wires": [ - [ - "d119a802cf4c3f2d" - ], - [ - "7acd371a3e8d58c7" - ], - [ - "8dfac8c9adafc76f" - ], - [ - "ddbbc347dd314885" - ], - [ - "2655836d55c8c00e" - ], - [ - "f4e92faafd5a967d" - ], - [ - "85505c259d234540" - ], - [ - "69f0e0fd86f25833", - "8eecfc6543bf944c" - ], - [ - "a56a8820702f4610" - ] - ] - }, - { - "id": "db1ef284071e60a1", - "type": "ui-template", - "z": "6d6a011bf1913637", - "group": "39cbd2658f16d608", - "page": "", - "ui": "", - "name": "body", - "order": 1, - "width": "0", - "height": "0", - "head": "", - "format": "\n\n\n\n", - "storeOutMessages": true, - "passthru": true, - "resendOnRefresh": true, - "templateScope": "local", - "className": "", - "x": 370, - "y": 220, - "wires": [ - [ - "24077d432cd6db62" - ] - ] - }, - { - "id": "27a2b425848bce6f", - "type": "mqtt in", - "z": "6d6a011bf1913637", - "name": "", - "topic": "status/pump", - "qos": "0", - "datatype": "json", - "broker": "8dc3722c.06efa8", - "nl": false, - "rap": false, - "inputs": 0, - "x": 90, - "y": 200, - "wires": [ - [ - "db1ef284071e60a1" - ] - ] - }, - { - "id": "d53b5bacd6d4c3ac", - "type": "mqtt in", - "z": "6d6a011bf1913637", - "name": "", - "topic": "status/focus", - "qos": "0", - "datatype": "json", - "broker": "8dc3722c.06efa8", - "nl": false, - "rap": false, - "inputs": 0, - "x": 90, - "y": 260, - "wires": [ - [ - "db1ef284071e60a1" - ] - ] - }, - { - "id": "2655836d55c8c00e", - "type": "function", - "z": "6d6a011bf1913637", - "name": "set light settings", - "func": "// On v\u00e9rifie si le message contient les propri\u00e9t\u00e9s attendues\n// avant de mettre \u00e0 jour les variables globales.\n\nif (msg.payload) {\n \n // Si led_status est pr\u00e9sent dans le payload, on stocke\n if (msg.payload.led_status !== undefined) {\n global.set(\"led_status\", msg.payload.led_status);\n }\n\n // Si calibration_led_intensity est pr\u00e9sent dans le payload, on stocke\n if (msg.payload.calibration_led_intensity !== undefined) {\n global.set(\"calibration_led_intensity\", msg.payload.calibration_led_intensity);\n }\n}\n\nreturn msg;", - "outputs": 1, - "timeout": "", - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 780, - "y": 280, - "wires": [ - [] - ] - }, - { - "id": "20547adfe07c39d4", - "type": "mqtt in", - "z": "6d6a011bf1913637", - "name": "", - "topic": "status/light", - "qos": "0", - "datatype": "json", - "broker": "8dc3722c.06efa8", - "nl": false, - "rap": false, - "inputs": 0, - "x": 80, - "y": 320, - "wires": [ - [ - "db1ef284071e60a1" - ] - ] - }, - { - "id": "364762649f9df51a", - "type": "mqtt in", - "z": "6d6a011bf1913637", - "name": "", - "topic": "status/imager", - "qos": "0", - "datatype": "json", - "broker": "8dc3722c.06efa8", - "nl": false, - "rap": false, - "inputs": 0, - "x": 90, - "y": 380, - "wires": [ - [ - "db1ef284071e60a1" - ] - ] - }, - { - "id": "85505c259d234540", - "type": "function", - "z": "6d6a011bf1913637", - "name": "set bubbler settings", - "func": "if (msg.topic) {\n global.set(\"bubbler_status\", msg.payload.action);\n\n}\nreturn msg;", - "outputs": 1, - "timeout": "", - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 800, - "y": 380, - "wires": [ - [] - ] - }, - { - "id": "f49d224aa87cdc35", - "type": "mqtt in", - "z": "6d6a011bf1913637", - "name": "", - "topic": "status/bubbler", - "qos": "0", - "datatype": "json", - "broker": "8dc3722c.06efa8", - "nl": false, - "rap": false, - "inputs": 0, - "x": 100, - "y": 380, - "wires": [ - [ - "db1ef284071e60a1" - ] - ] - }, - { - "id": "daae7bb94e6bd36d", - "type": "ui-template", - "z": "6d6a011bf1913637", - "group": "7572915171e440cd", - "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": 80, - "wires": [ - [] - ] - }, - { - "id": "7acd371a3e8d58c7", - "type": "mqtt out", - "z": "6d6a011bf1913637", - "name": "MQTT - pump", - "topic": "", - "qos": "", - "retain": "", - "respTopic": "", - "contentType": "", - "userProps": "", - "correl": "", - "expiry": "", - "broker": "8dc3722c.06efa8", - "x": 780, - "y": 120, - "wires": [] - }, - { - "id": "ddbbc347dd314885", - "type": "mqtt out", - "z": "6d6a011bf1913637", - "name": "MQTT - focus", - "topic": "", - "qos": "", - "retain": "", - "respTopic": "", - "contentType": "", - "userProps": "", - "correl": "", - "expiry": "", - "broker": "8dc3722c.06efa8", - "x": 780, - "y": 220, - "wires": [] - }, - { - "id": "f4e92faafd5a967d", - "type": "mqtt out", - "z": "6d6a011bf1913637", - "name": "MQTT - light", - "topic": "", - "qos": "", - "retain": "", - "respTopic": "", - "contentType": "", - "userProps": "", - "correl": "", - "expiry": "", - "broker": "8dc3722c.06efa8", - "x": 770, - "y": 320, - "wires": [] - }, - { - "id": "a56a8820702f4610", - "type": "mqtt out", - "z": "6d6a011bf1913637", - "name": "MQTT - Imager", - "topic": "", - "qos": "", - "retain": "", - "respTopic": "", - "contentType": "", - "userProps": "", - "correl": "", - "expiry": "", - "broker": "8dc3722c.06efa8", - "x": 780, - "y": 480, - "wires": [] - }, - { - "id": "8eecfc6543bf944c", - "type": "debug", - "z": "6d6a011bf1913637", - "name": "debug 1", - "active": true, - "tosidebar": true, - "console": false, - "tostatus": false, - "complete": "true", - "targetType": "full", - "statusVal": "", - "statusType": "auto", - "x": 480, - "y": 560, - "wires": [] - }, - { - "id": "69f0e0fd86f25833", - "type": "mqtt out", - "z": "6d6a011bf1913637", - "name": "MQTT - bubbler", - "topic": "", - "qos": "", - "retain": "", - "respTopic": "", - "contentType": "", - "userProps": "", - "correl": "", - "expiry": "", - "broker": "8dc3722c.06efa8", - "x": 780, - "y": 420, - "wires": [] - }, - { - "id": "29b0ee7d77b1b373", - "type": "ui-template", - "z": "f7ff9c0e54da4b8e", - "group": "dd015fda7456b9b0", - "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": "4893b2624925b10a", - "type": "ui-template", - "z": "f7ff9c0e54da4b8e", - "group": "69c772603e9cec0d", - "page": "", - "ui": "", - "name": "Sample Information", - "order": 1, - "width": 0, - "height": 0, - "head": "", - "format": "\n\n\n\n", - "storeOutMessages": true, - "passthru": true, - "resendOnRefresh": true, - "templateScope": "local", - "className": "", - "x": 410, - "y": 140, - "wires": [ - [ - "23f16f4437be7a5f", - "38f795adc7ce566d" - ] - ] - }, - { - "id": "e7f23192557851e0", - "type": "ui-template", - "z": "f7ff9c0e54da4b8e", - "group": "6fab47af451c6d95", - "page": "", - "ui": "", - "name": "Starting point", - "order": 1, - "width": 0, - "height": 0, - "head": "", - "format": "\n\n\n", - "storeOutMessages": true, - "passthru": true, - "resendOnRefresh": true, - "templateScope": "local", - "className": "", - "x": 400, - "y": 200, - "wires": [ - [ - "943a452f8c2720fe" - ] - ] - }, - { - "id": "ba461390e1455794", - "type": "ui-template", - "z": "f7ff9c0e54da4b8e", - "group": "1cfe3062a8353012", - "page": "", - "ui": "", - "name": "Ending point", - "order": 1, - "width": 0, - "height": 0, - "head": "", - "format": "\n\n", - "storeOutMessages": true, - "passthru": true, - "resendOnRefresh": true, - "templateScope": "local", - "className": "", - "x": 390, - "y": 300, - "wires": [ - [ - "8f1721c5616582cc" - ] - ] - }, - { - "id": "9ddd0fbe2670f626", - "type": "ui-template", - "z": "f7ff9c0e54da4b8e", - "group": "cb3521392eb4e9ed", - "page": "", - "ui": "", - "name": "Net Specificity", - "order": 1, - "width": 0, - "height": 0, - "head": "", - "format": "\n\n\n\n", - "storeOutMessages": true, - "passthru": true, - "resendOnRefresh": true, - "templateScope": "local", - "className": "", - "x": 400, - "y": 400, - "wires": [ - [ - "ad434c2ca4b52cf4" - ] - ] - }, - { - "id": "e4d9d690aa12d09c", - "type": "ui-template", - "z": "f7ff9c0e54da4b8e", - "group": "dd4d9707051041c9", - "page": "", - "ui": "", - "name": "Other Informations", - "order": 1, - "width": 0, - "height": 0, - "head": "", - "format": "\n\n\n\n", - "storeOutMessages": true, - "passthru": true, - "resendOnRefresh": true, - "templateScope": "local", - "className": "", - "x": 410, - "y": 460, - "wires": [ - [ - "b993c6289d67f56d" - ] - ] - }, - { - "id": "990f1d12e47028f3", - "type": "function", - "z": "f7ff9c0e54da4b8e", - "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": [ - [ - "4893b2624925b10a", - "e7f23192557851e0", - "ba461390e1455794", - "9ddd0fbe2670f626", - "e4d9d690aa12d09c" - ] - ] - }, - { - "id": "7eb3f9c16325dd59", - "type": "ui-event", - "z": "f7ff9c0e54da4b8e", - "ui": "e6ae26617c24c3ea", - "name": "UI Event", - "x": 80, - "y": 40, - "wires": [ - [ - "b729150cff31a0ff" - ] - ] - }, - { - "id": "b729150cff31a0ff", - "type": "switch", - "z": "f7ff9c0e54da4b8e", - "name": "msg.payload.page.path === \"/metadata\"", - "property": "payload.page.path", - "propertyType": "msg", - "rules": [ - { - "t": "eq", - "v": "/metadata", - "vt": "str" - } - ], - "checkall": "true", - "repair": false, - "outputs": 1, - "x": 320, - "y": 40, - "wires": [ - [ - "990f1d12e47028f3" - ] - ] - }, - { - "id": "943a452f8c2720fe", - "type": "switch", - "z": "f7ff9c0e54da4b8e", - "name": "", - "property": "topic", - "propertyType": "msg", - "rules": [ - { - "t": "eq", - "v": "object_datetime", - "vt": "str" - }, - { - "t": "eq", - "v": "object_latlon", - "vt": "str" - } - ], - "checkall": "true", - "repair": false, - "outputs": 2, - "x": 570, - "y": 200, - "wires": [ - [ - "e01c2d986929b8ed" - ], - [ - "ddd635a300595387" - ] - ] - }, - { - "id": "ddd635a300595387", - "type": "function", - "z": "f7ff9c0e54da4b8e", - "name": "set object_latlon", - "func": "if (msg.topic) {\n global.set(\"object_lat\", msg.payload.object_lat);\n global.set(\"object_lon\", msg.payload.object_lon);\n if (msg.payload.object_date !== undefined) {\n global.set(\"object_date\", msg.payload.object_date);\n }\n if (msg.payload.object_time !== undefined) {\n global.set(\"object_time\", msg.payload.object_time);\n }\n}\nreturn msg;", - "outputs": 1, - "timeout": "", - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 960, - "y": 240, - "wires": [ - [] - ] - }, - { - "id": "e01c2d986929b8ed", - "type": "function", - "z": "f7ff9c0e54da4b8e", - "name": "set object_datetime", - "func": "if (msg.topic) {\n global.set(\"object_date\", msg.payload.object_date);\n global.set(\"object_time\", msg.payload.object_time);\n if (msg.payload.object_lat !== undefined) {\n global.set(\"object_lat\", msg.payload.object_lat);\n }\n if (msg.payload.object_lon !== undefined) {\n global.set(\"object_lon\", msg.payload.object_lon);\n }\n}\nreturn msg;", - "outputs": 1, - "timeout": "", - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 970, - "y": 200, - "wires": [ - [] - ] - }, - { - "id": "8f1721c5616582cc", - "type": "switch", - "z": "f7ff9c0e54da4b8e", - "name": "", - "property": "topic", - "propertyType": "msg", - "rules": [ - { - "t": "eq", - "v": "object_datetime_end", - "vt": "str" - }, - { - "t": "eq", - "v": "object_latlon_end", - "vt": "str" - } - ], - "checkall": "true", - "repair": false, - "outputs": 2, - "x": 570, - "y": 300, - "wires": [ - [ - "e4efaec99ee5c172" - ], - [ - "4790ca87c9ebf492" - ] - ] - }, - { - "id": "4790ca87c9ebf492", - "type": "function", - "z": "f7ff9c0e54da4b8e", - "name": "set object_latlon_end", - "func": "if (msg.topic) {\n global.set(\"object_lat_end\", msg.payload.object_lat_end);\n global.set(\"object_lon_end\", msg.payload.object_lon_end);\n}\n\nreturn msg;\n", - "outputs": 1, - "timeout": "", - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 980, - "y": 340, - "wires": [ - [] - ] - }, - { - "id": "e4efaec99ee5c172", - "type": "function", - "z": "f7ff9c0e54da4b8e", - "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}\n\n\nreturn msg;\n", - "outputs": 1, - "timeout": "", - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 990, - "y": 300, - "wires": [ - [] - ] - }, - { - "id": "ad434c2ca4b52cf4", - "type": "switch", - "z": "f7ff9c0e54da4b8e", - "name": "", - "property": "topic", - "propertyType": "msg", - "rules": [ - { - "t": "eq", - "v": "sample_net_params", - "vt": "str" - } - ], - "checkall": "true", - "repair": false, - "outputs": 1, - "x": 570, - "y": 400, - "wires": [ - [ - "fb90be363fcaf2e5" - ] - ] - }, - { - "id": "fb90be363fcaf2e5", - "type": "function", - "z": "f7ff9c0e54da4b8e", - "name": "set sample_net_params", - "func": "if (msg.topic) {\n global.set(\"sample_net_mouth_diameter\", msg.payload.sample_net_mouth_diameter);\n global.set(\"sample_net_length\", msg.payload.sample_net_length);\n global.set(\"sample_net_mesh_size\", msg.payload.sample_net_mesh_size);\n global.set(\"sample_filtered_volume\", msg.payload.sample_filtered_volume);\n}\nreturn msg;\n", - "outputs": 1, - "timeout": "", - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 990, - "y": 400, - "wires": [ - [] - ] - }, - { - "id": "b993c6289d67f56d", - "type": "switch", - "z": "f7ff9c0e54da4b8e", - "name": "", - "property": "topic", - "propertyType": "msg", - "rules": [ - { - "t": "eq", - "v": "sample_water_params", - "vt": "str" - } - ], - "checkall": "true", - "repair": false, - "outputs": 1, - "x": 570, - "y": 460, - "wires": [ - [ - "01dbd7f8f0e8cc4b" - ] - ] - }, - { - "id": "01dbd7f8f0e8cc4b", - "type": "function", - "z": "f7ff9c0e54da4b8e", - "name": "set sample_water_params", - "func": "if (msg.topic) {\n global.set(\"sample_collected_volume\", msg.payload.sample_collected_volume);\n global.set(\"sample_concentration_factor\", msg.payload.sample_concentration_factor);\n global.set(\"sample_sieve_mesh_size\", msg.payload.sample_sieve_mesh_size);\n global.set(\"sample_depth\", msg.payload.sample_depth);\n global.set(\"sample_comment\", msg.payload.sample_comment);\n}\nreturn msg;\n", - "outputs": 1, - "timeout": "", - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 1000, - "y": 460, - "wires": [ - [] - ] - }, - { - "id": "23f16f4437be7a5f", - "type": "switch", - "z": "f7ff9c0e54da4b8e", - "name": "", - "property": "topic", - "propertyType": "msg", - "rules": [ - { - "t": "eq", - "v": "sample_project_meta", - "vt": "str" - } - ], - "checkall": "true", - "repair": false, - "outputs": 1, - "x": 570, - "y": 140, - "wires": [ - [ - "815b79480bdb6864", - "ba461390e1455794", - "9ddd0fbe2670f626", - "e7f23192557851e0", - "e4d9d690aa12d09c" - ] - ] - }, - { - "id": "815b79480bdb6864", - "type": "function", - "z": "f7ff9c0e54da4b8e", - "name": "set sample_project_meta", - "func": "if (msg.topic) {\n global.set(\"sample_project\", msg.payload.sample_project);\n global.set(\"sample_operator\", msg.payload.sample_operator);\n global.set(\"sample_id\", msg.payload.sample_id);\n global.set(\"sample_gear\", msg.payload.sample_gear);\n}\nreturn msg;\n", - "outputs": 1, - "timeout": "", - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 990, - "y": 140, - "wires": [ - [] - ] - }, - { - "id": "38f795adc7ce566d", - "type": "function", - "z": "f7ff9c0e54da4b8e", - "name": "set data", - "func": "// 1. Setup Base Variables\nconst gear = msg.payload.sample_gear;\nconst now = new Date();\nconst dateStr = now.toISOString().split('T')[0]; // YYYY-MM-DD\n// Time as HHMM (no colons, no seconds) \u2014 matches sendUpdate format\nconst hh = String(now.getUTCHours()).padStart(2, '0');\nconst mm = String(now.getUTCMinutes()).padStart(2, '0');\nconst timeStr = hh + mm;\n\n// Helper to get global or fallback\nconst getG = (key, fallback) => global.get(key) ?? fallback;\n\n// 2. Define the Full Schema Template (All keys)\nconst allKeys = [\n \"object_date\", \"object_time\", \"object_lat\", \"object_lon\",\n \"object_date_end\", \"object_time_end\", \"object_lat_end\", \"object_lon_end\",\n \"sample_net_mouth_diameter\", \"sample_net_length\", \"sample_net_mesh_size\",\n \"sample_filtered_volume\", \"sample_concentration_factor\", \"sample_sieve_mesh_size\",\n \"sample_depth\", \"sample_collected_volume\"\n];\n\nlet updates = {};\n\n// 3. Logic by Gear Type\n// All gears use getG() so user-entered values are preserved.\n// Defaults are only used when no value exists in globals.\nif (gear === \"Horizontal Net\") {\n updates = {\n object_date: getG(\"object_date\", dateStr),\n object_time: getG(\"object_time\", timeStr),\n object_lat: getG(\"object_lat\", 0),\n object_lon: getG(\"object_lon\", 0),\n object_date_end: getG(\"object_date_end\", getG(\"object_date\", dateStr)),\n object_time_end: getG(\"object_time_end\", getG(\"object_time\", timeStr)),\n object_lat_end: getG(\"object_lat_end\", getG(\"object_lat\", 0)),\n object_lon_end: getG(\"object_lon_end\", getG(\"object_lon\", 0)),\n sample_net_mouth_diameter: getG(\"sample_net_mouth_diameter\", 30),\n sample_net_length: getG(\"sample_net_length\", 120),\n sample_net_mesh_size: getG(\"sample_net_mesh_size\", 20),\n sample_filtered_volume: getG(\"sample_filtered_volume\", 2000),\n sample_concentration_factor: getG(\"sample_concentration_factor\", 1),\n sample_sieve_mesh_size: getG(\"sample_sieve_mesh_size\", 200),\n sample_depth: getG(\"sample_depth\", 1),\n sample_collected_volume: getG(\"sample_collected_volume\", 50)\n };\n}\nelse if (gear === \"Vertical Net\") {\n updates = {\n object_date: getG(\"object_date\", dateStr),\n object_time: getG(\"object_time\", timeStr),\n object_lat: getG(\"object_lat\", 0),\n object_lon: getG(\"object_lon\", 0),\n sample_net_mouth_diameter: getG(\"sample_net_mouth_diameter\", 30),\n sample_net_length: getG(\"sample_net_length\", 120),\n sample_net_mesh_size: getG(\"sample_net_mesh_size\", 20),\n sample_filtered_volume: getG(\"sample_filtered_volume\", 2000),\n sample_concentration_factor: getG(\"sample_concentration_factor\", 1),\n sample_sieve_mesh_size: getG(\"sample_sieve_mesh_size\", 200),\n sample_depth: getG(\"sample_depth\", 1),\n sample_collected_volume: getG(\"sample_collected_volume\", 50)\n };\n}\nelse if (gear === \"Niskin bottle\") {\n updates = {\n object_date: getG(\"object_date\", dateStr),\n object_time: getG(\"object_time\", timeStr),\n object_lat: getG(\"object_lat\", 0),\n object_lon: getG(\"object_lon\", 0),\n sample_concentration_factor: getG(\"sample_concentration_factor\", 1),\n sample_sieve_mesh_size: getG(\"sample_sieve_mesh_size\", 200),\n sample_depth: getG(\"sample_depth\", 1),\n sample_collected_volume: getG(\"sample_collected_volume\", 50)\n };\n}\nelse if (gear === \"Lab culture\") {\n updates = {\n object_date: getG(\"object_date\", dateStr),\n object_time: getG(\"object_time\", timeStr),\n object_lat: getG(\"object_lat\", 0),\n object_lon: getG(\"object_lon\", 0)\n };\n}\nelse if (gear === \"Demo / Test\") {\n updates = {\n object_date: getG(\"object_date\", dateStr),\n object_time: getG(\"object_time\", timeStr),\n object_lat: getG(\"object_lat\", 48.587440060782704),\n object_lon: getG(\"object_lon\", -3.838189224660975)\n };\n}\n\n// 4. Final Processing: Sync to Global & Build Payload\nlet finalPayload = {};\n\nallKeys.forEach(key => {\n if (updates.hasOwnProperty(key)) {\n // Key is relevant to current gear type - use value (from getG with fallback)\n finalPayload[key] = updates[key];\n global.set(key, updates[key]);\n } else {\n // Key is NOT relevant to current gear type.\n // Send null in payload (disabled fields show empty),\n // but preserve global so user values survive gear switches.\n finalPayload[key] = null;\n }\n});\n\nmsg.payload = finalPayload;\nreturn msg;", - "outputs": 1, - "timeout": "", - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 100, - "y": 340, - "wires": [ - [ - "e7f23192557851e0", - "ba461390e1455794", - "9ddd0fbe2670f626", - "e4d9d690aa12d09c" - ] - ] - }, - { - "id": "6a0b3b7b9f767135", - "type": "ui-template", - "z": "71ede8b7dd88d90e", - "group": "b274327af3807b79", - "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": 750, - "y": 340, - "wires": [ - [] - ] - }, - { - "id": "79bccc0355eb5d87", - "type": "ui-template", - "z": "71ede8b7dd88d90e", - "group": "d2f77573ed4317e4", - "page": "", - "ui": "", - "name": "body", - "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": [ - [ - "e44fd64f57f48140" - ] - ] - }, - { - "id": "6001550c3befb6ea", - "type": "ui-template", - "z": "71ede8b7dd88d90e", - "group": "3ae252a2e5abca89", - "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": "74aa092817adc6f0", - "type": "function", - "z": "71ede8b7dd88d90e", - "name": "set acq_params", - "func": "// On r\u00e9cup\u00e8re le payload\nconst p = msg.payload;\n\n// On ne proc\u00e8de que si le payload est un objet\nif (p && typeof p === 'object') {\n\n // Liste des param\u00e8tres \u00e0 surveiller et \u00e0 stocker\n // On boucle sur les cl\u00e9s pour \u00e9viter de r\u00e9p\u00e9ter 20 fois \"if(p.x !== undefined)\"\n const keys = [\n \"acq_id\", \"acq_nb_frame\", \"acq_interframe_volume\", \n \"acq_imaged_volume\", \"acq_pumped_volume\", \"acq_comment\",\n \"acq_status\", \"acq_magnification\", \"acq_tube_lens_reference\",\n \"acq_objective_lens_reference\", \"process_pixel_size\",\n \"calibration_pixel_size\", \"calibration_led_intensity\",\n \"sensor_width_um\", \"sensor_height_um\", \"acq_flowcell_thickness\",\n \"acq_interframe_flowrate\", \"acq_stabilization_delay\",\n \"acq_start_timestamp\"\n ];\n\n keys.forEach(key => {\n if (p[key] !== undefined) {\n global.set(key, p[key]);\n }\n });\n\n // Cas particuliers (noms de propri\u00e9t\u00e9s diff\u00e9rents entre msg et global)\n if (p.progression !== undefined) global.set(\"acq_progression\", p.progression);\n if (p.duration_left !== undefined) global.set(\"acq_duration_left\", p.duration_left);\n}\n\nreturn msg;", - "outputs": 1, - "timeout": "", - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 760, - "y": 120, - "wires": [ - [] - ] - }, - { - "id": "ab83c951efd3e647", - "type": "ui-event", - "z": "71ede8b7dd88d90e", - "ui": "e6ae26617c24c3ea", - "name": "UI Event", - "x": 80, - "y": 40, - "wires": [ - [ - "5a65f683c152b736" - ] - ] - }, - { - "id": "5a65f683c152b736", - "type": "switch", - "z": "71ede8b7dd88d90e", - "name": "msg.payload.page.path === \"/acquisition\"", - "property": "payload.page.path", - "propertyType": "msg", - "rules": [ - { - "t": "eq", - "v": "/acquisition", - "vt": "str" - } - ], - "checkall": "true", - "repair": false, - "outputs": 1, - "x": 330, - "y": 40, - "wires": [ - [ - "31fab063b7078fe6" - ] - ] - }, - { - "id": "31fab063b7078fe6", - "type": "function", - "z": "71ede8b7dd88d90e", - "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": [ - [ - "79bccc0355eb5d87" - ] - ] - }, - { - "id": "740af1e4fccda6b3", - "type": "mqtt in", - "z": "71ede8b7dd88d90e", - "name": "", - "topic": "status/imager", - "qos": "0", - "datatype": "json", - "broker": "8dc3722c.06efa8", - "nl": false, - "rap": false, - "inputs": 0, - "x": 90, - "y": 200, - "wires": [ - [ - "79bccc0355eb5d87" - ] - ] - }, - { - "id": "ff1fcb16bcb6619e", - "type": "mqtt out", - "z": "71ede8b7dd88d90e", - "name": "MQTT", - "topic": "", - "qos": "", - "retain": "", - "respTopic": "", - "contentType": "", - "userProps": "", - "correl": "", - "expiry": "", - "broker": "8dc3722c.06efa8", - "x": 1110, - "y": 220, - "wires": [] - }, - { - "id": "4a02deeb69a27d00", - "type": "function", - "z": "71ede8b7dd88d90e", - "name": "update_config", - "func": "const keys = global.keys();\nlet config = {};\n\nkeys.forEach(key => {\n if (!key.startsWith('$')) {\n if (\n key.startsWith('sample_') ||\n key.startsWith('acq_') ||\n key.startsWith('object_') ||\n key.startsWith('process_') ||\n key.startsWith('img_')\n ) {\n config[key] = global.get(key);\n }\n }\n});\n\n// Compose fully-qualified IDs for unique EcoTaxa naming\nconst project = config.sample_project || \"\";\nconst sampleRaw = String(config.sample_id || \"\");\nconst acqRaw = String(config.acq_id || \"\");\n\nif (project && !sampleRaw.startsWith(project + \"_\")) {\n config.sample_id = project + \"_\" + sampleRaw;\n}\nconst finalSample = String(config.sample_id || \"\");\nif (finalSample && !acqRaw.startsWith(finalSample + \"_\")) {\n config.acq_id = finalSample + \"_\" + acqRaw;\n}\n\n// Sanitize spaces in IDs to match filesystem paths\n// (the Python imager replaces spaces with underscores in directory names)\nconfig.sample_id = String(config.sample_id || \"\").replace(/ /g, \"_\");\nconfig.acq_id = String(config.acq_id || \"\").replace(/ /g, \"_\");\n\nmsg.topic = \"imager/image\";\n\nmsg.payload = {\n action: \"update_config\",\n config: config\n};\n\nreturn msg;", - "outputs": 1, - "timeout": 0, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 760, - "y": 220, - "wires": [ - [ - "ff1fcb16bcb6619e" - ] - ] - }, - { - "id": "908239a5c01e9061", - "type": "function", - "z": "71ede8b7dd88d90e", - "name": "start acquisition", - "func": "const acq_interframe_volume = msg.payload.acq_interframe_volume\n || global.get(\"acq_interframe_volume\") || 0;\nconst acq_nb_frame = msg.payload.acq_nb_frame\n || global.get(\"acq_nb_frame\") || 0;\nconst acq_stabilization_delay = msg.payload.acq_stabilization_delay\n || global.get(\"acq_stabilization_delay\") || 0;\nconst acq_interframe_flowrate = msg.payload.acq_interframe_flowrate\n || global.get(\"acq_interframe_flowrate\") || 0;\n\nmsg.payload = {\n action: \"image\",\n pump_direction: \"FORWARD\",\n volume: acq_interframe_volume,\n nb_frame: acq_nb_frame,\n sleep: acq_stabilization_delay,\n flowrate: acq_interframe_flowrate,\n};\n\nmsg.topic = \"imager/image\";\n\nreturn msg;", - "outputs": 1, - "timeout": 0, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 760, - "y": 280, - "wires": [ - [ - "51e3db016f2cdf07" - ] - ] - }, - { - "id": "5eb0686bb01d4643", - "type": "mqtt out", - "z": "71ede8b7dd88d90e", - "name": "MQTT", - "topic": "", - "qos": "", - "retain": "", - "respTopic": "", - "contentType": "", - "userProps": "", - "correl": "", - "expiry": "", - "broker": "8dc3722c.06efa8", - "x": 1110, - "y": 280, - "wires": [] - }, - { - "id": "98dde1119867adf0", - "type": "switch", - "z": "71ede8b7dd88d90e", - "name": "", - "property": "payload.acq_status", - "propertyType": "msg", - "rules": [ - { - "t": "eq", - "v": "off", - "vt": "str" - }, - { - "t": "eq", - "v": "on", - "vt": "str" - } - ], - "checkall": "true", - "repair": false, - "outputs": 2, - "x": 550, - "y": 220, - "wires": [ - [ - "7c4e3258ef8f22c0" - ], - [ - "4a02deeb69a27d00", - "908239a5c01e9061" - ] - ] - }, - { - "id": "7c4e3258ef8f22c0", - "type": "function", - "z": "71ede8b7dd88d90e", - "name": "stop acquisition", - "func": "// Stop the imager acquisition\nvar stopImager = { topic: \"imager/image\", payload: { action: \"stop\" } };\n\n// Also send pump stop command as failsafe\nvar stopPump = { topic: \"actuator/pump\", payload: { action: \"stop\" } };\n\n// Return both messages to be sent\nreturn [[stopImager, stopPump]];\n", - "outputs": 1, - "timeout": 0, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 760, - "y": 160, - "wires": [ - [ - "687e8f43fbf72b84" - ] - ] - }, - { - "id": "687e8f43fbf72b84", - "type": "mqtt out", - "z": "71ede8b7dd88d90e", - "name": "MQTT", - "topic": "", - "qos": "", - "retain": "", - "respTopic": "", - "contentType": "", - "userProps": "", - "correl": "", - "expiry": "", - "broker": "8dc3722c.06efa8", - "x": 1110, - "y": 160, - "wires": [] - }, - { - "id": "7814ecbf379005bd", - "type": "switch", - "z": "71ede8b7dd88d90e", - "name": "", - "property": "topic", - "propertyType": "msg", - "rules": [ - { - "t": "neq", - "v": "$pageview", - "vt": "str" - } - ], - "checkall": "true", - "repair": false, - "outputs": 1, - "x": 310, - "y": 220, - "wires": [ - [ - "98dde1119867adf0", - "74aa092817adc6f0" - ] - ] - }, - { - "id": "51e3db016f2cdf07", - "type": "delay", - "z": "71ede8b7dd88d90e", - "name": "", - "pauseType": "delay", - "timeout": "200", - "timeoutUnits": "milliseconds", - "rate": "1", - "nbRateUnits": "1", - "rateUnits": "second", - "randomFirst": "1", - "randomLast": "5", - "randomUnits": "seconds", - "drop": false, - "allowrate": false, - "outputs": 1, - "x": 950, - "y": 280, - "wires": [ - [ - "5eb0686bb01d4643" - ] - ] - }, - { - "id": "e44fd64f57f48140", - "type": "switch", - "z": "71ede8b7dd88d90e", - "name": "", - "property": "topic", - "propertyType": "msg", - "rules": [ - { - "t": "neq", - "v": "$pageleave", - "vt": "str" - } - ], - "checkall": "true", - "repair": false, - "outputs": 1, - "x": 310, - "y": 180, - "wires": [ - [ - "7814ecbf379005bd" - ] - ] - }, - { - "id": "3841297f254854cf", - "type": "ui-event", - "z": "524b7620431210e8", - "ui": "e6ae26617c24c3ea", - "name": "UI Event", - "x": 80, - "y": 40, - "wires": [ - [ - "66f970bece31600d" - ] - ] - }, - { - "id": "66f970bece31600d", - "type": "switch", - "z": "524b7620431210e8", - "name": "msg.payload.page.path === \"/segmentation\"", - "property": "payload.page.path", - "propertyType": "msg", - "rules": [ - { - "t": "eq", - "v": "/segmentation", - "vt": "str" - } - ], - "checkall": "true", - "repair": false, - "outputs": 1, - "x": 330, - "y": 40, - "wires": [ - [ - "773960d6099cab47", - "aa1309fda7fde8f2" - ] - ] - }, - { - "id": "773960d6099cab47", - "type": "list acquisitions", - "z": "524b7620431210e8", - "name": "", - "x": 100, - "y": 140, - "wires": [ - [ - "8a22543633712279" - ] - ] - }, - { - "id": "3ad2613862513bb0", - "type": "ui-template", - "z": "524b7620431210e8", - "group": "9b992ca6515ed058", - "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": 1520, - "y": 40, - "wires": [ - [] - ] - }, - { - "id": "f9e776957dfa61b0", - "type": "mqtt out", - "z": "524b7620431210e8", - "name": "MQTT", - "topic": "", - "qos": "", - "retain": "", - "respTopic": "", - "contentType": "", - "userProps": "", - "correl": "", - "expiry": "", - "broker": "8dc3722c.06efa8", - "x": 1210, - "y": 140, - "wires": [] - }, - { - "id": "60ae065050fc2ad3", - "type": "switch", - "z": "524b7620431210e8", - "name": "", - "property": "topic", - "propertyType": "msg", - "rules": [ - { - "t": "eq", - "v": "segmenter/segment", - "vt": "str" - } - ], - "checkall": "true", - "repair": false, - "outputs": 1, - "x": 1030, - "y": 140, - "wires": [ - [ - "f9e776957dfa61b0", - "3314fac880c6a656" - ] - ] - }, - { - "id": "9a2e04f65e0432e1", - "type": "mqtt in", - "z": "524b7620431210e8", - "name": "", - "topic": "status/segmenter", - "qos": "0", - "datatype": "json", - "broker": "8dc3722c.06efa8", - "nl": false, - "rap": false, - "inputs": 0, - "x": 100, - "y": 260, - "wires": [ - [ - "e2db15bb9c3c254f" - ] - ] - }, - { - "id": "e2db15bb9c3c254f", - "type": "ui-template", - "z": "524b7620431210e8", - "group": "402b3d24c87ab0d2", - "page": "", - "ui": "", - "name": "Details of Segmentation", - "order": 1, - "width": "0", - "height": "0", - "head": "", - "format": "\n\n\n\n\n", - "storeOutMessages": true, - "passthru": true, - "resendOnRefresh": true, - "templateScope": "local", - "className": "", - "x": 370, - "y": 300, - "wires": [ - [] - ] - }, - { - "id": "312de75fd2144c81", - "type": "switch", - "z": "524b7620431210e8", - "name": "", - "property": "payload.is_segmented", - "propertyType": "msg", - "rules": [ - { - "t": "true" - }, - { - "t": "false" - } - ], - "checkall": "true", - "repair": false, - "outputs": 2, - "x": 510, - "y": 140, - "wires": [ - [], - [ - "6e43a35e8dcf2b3b" - ] - ] - }, - { - "id": "3314fac880c6a656", - "type": "function", - "z": "524b7620431210e8", - "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 global.set(\"process_min_ESD\", msg.payload.settings.process_min_ESD);\n}\nreturn msg;\n", - "outputs": 1, - "timeout": "", - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 1220, - "y": 60, - "wires": [ - [] - ] - }, - { - "id": "aa1309fda7fde8f2", - "type": "function", - "z": "524b7620431210e8", - "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": 200, - "wires": [ - [ - "e2db15bb9c3c254f" - ] - ] - }, - { - "id": "6e43a35e8dcf2b3b", - "type": "function", - "z": "524b7620431210e8", - "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;\nmsg.payload.open_dialog = true;\n\n// Return the modified message\nreturn msg;\n", - "outputs": 1, - "timeout": 0, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 700, - "y": 140, - "wires": [ - [ - "60bfa71a5e7fc2d5" - ] - ] - }, - { - "id": "60bfa71a5e7fc2d5", - "type": "ui-template", - "z": "524b7620431210e8", - "group": "bfd4acb7b243514f", - "page": "", - "ui": "", - "name": "Dialog", - "order": 2, - "width": 0, - "height": 0, - "head": "", - "format": "\n", - "storeOutMessages": true, - "passthru": true, - "resendOnRefresh": true, - "templateScope": "local", - "className": "", - "x": 870, - "y": 140, - "wires": [ - [ - "60ae065050fc2ad3" - ] - ] - }, - { - "id": "8a22543633712279", - "type": "ui-template", - "z": "524b7620431210e8", - "group": "bfd4acb7b243514f", - "page": "", - "ui": "", - "name": "List of Acquisitions", - "order": 1, - "width": 0, - "height": 0, - "head": "", - "format": "\n\n\n", - "storeOutMessages": true, - "passthru": true, - "resendOnRefresh": true, - "templateScope": "local", - "className": "", - "x": 310, - "y": 140, - "wires": [ - [ - "98b980828cac388b" - ] - ] - }, - { - "id": "98b980828cac388b", - "type": "switch", - "z": "524b7620431210e8", - "name": "", - "property": "topic", - "propertyType": "msg", - "rules": [ - { - "t": "eq", - "v": "segment_request", - "vt": "str" - }, - { - "t": "eq", - "v": "delete_request", - "vt": "str" - } - ], - "checkall": "true", - "repair": false, - "outputs": 2, - "x": 430, - "y": 220, - "wires": [ - [ - "312de75fd2144c81" - ], - [ - "20e38293b7b4fca8" - ] - ] - }, - { - "id": "20e38293b7b4fca8", - "type": "exec", - "z": "524b7620431210e8", - "command": "rm -rf ", - "addpay": "payload.path", - "append": "", - "useSpawn": "false", - "timer": "", - "winHide": false, - "oldrc": false, - "name": "", - "x": 590, - "y": 240, - "wires": [ - [], - [], - [] - ] - }, - { - "id": "b3a9d14bbb081aa8", - "type": "ui-event", - "z": "4fdbd7bacb797c5a", - "ui": "e6ae26617c24c3ea", - "name": "UI Event", - "x": 80, - "y": 40, - "wires": [ - [ - "843d34f5d30962ab", - "69ce53c2079b63a0" - ] - ] - }, - { - "id": "843d34f5d30962ab", - "type": "switch", - "z": "4fdbd7bacb797c5a", - "name": "msg.payload.page.path === \"/visualization\"", - "property": "payload.page.path", - "propertyType": "msg", - "rules": [ - { - "t": "eq", - "v": "/visualization", - "vt": "str" - } - ], - "checkall": "true", - "repair": false, - "outputs": 1, - "x": 330, - "y": 40, - "wires": [ - [ - "2f62fc38be1bfe69" - ] - ] - }, - { - "id": "0425c18c1a9ca1ca", - "type": "ui-template", - "z": "4fdbd7bacb797c5a", - "group": "b570f76ef526af45", - "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": "2f62fc38be1bfe69", - "type": "list segmentations", - "z": "4fdbd7bacb797c5a", - "name": "", - "x": 170, - "y": 180, - "wires": [ - [ - "40a54312ad6d20c1" - ] - ] - }, - { - "id": "40a54312ad6d20c1", - "type": "function", - "z": "4fdbd7bacb797c5a", - "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\u00e9ation 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, - "initialize": "", - "finalize": "", - "libs": [], - "x": 440, - "y": 180, - "wires": [ - [ - "205e01594aafdf52" - ] - ] - }, - { - "id": "bb7e84695ec2b24c", - "type": "file in", - "z": "4fdbd7bacb797c5a", - "name": "", - "filename": "payload.path", - "filenameType": "msg", - "format": "utf8", - "chunk": false, - "sendError": false, - "encoding": "none", - "allProps": false, - "x": 180, - "y": 380, - "wires": [ - [ - "1e51a51e73c8f2a5", - "8f1bf6abf93d9587", - "a67c2c1c9523ec54", - "9f4a3d23a3c4ba43", - "836d93ca31e11fc8", - "03188ea81f8f748d", - "1db541fc5717964a", - "109aacc68888f0b1", - "27ebeb1895528a1d", - "2aa2c7cb2ba901a0", - "8fc92b69ced53cf2", - "88371b41526bbd18", - "93ac167a60a35472", - "72065cd82cb87df5" - ] - ] - }, - { - "id": "51fa556f38fcfaa4", - "type": "function", - "z": "4fdbd7bacb797c5a", - "name": "Get tsv path", - "func": "// 1. On s'assure que le path existe\nif (msg.payload.path) {\n\n const currentPath = msg.payload.path;\n const cleanPath = currentPath.endsWith('/') ? currentPath.slice(0, -1) : currentPath;\n const acqId = cleanPath.split('/').pop();\n\n // Try expected filename first\n const expectedTsv = `${cleanPath}/ecotaxa_${acqId}.tsv`;\n\n if (fs.existsSync(expectedTsv)) {\n msg.payload.path = expectedTsv;\n } else {\n // Fallback: find any ecotaxa_*.tsv in the directory\n try {\n const files = fs.readdirSync(cleanPath);\n const tsvFile = files.find(f => f.startsWith('ecotaxa_') && f.endsWith('.tsv'));\n if (tsvFile) {\n msg.payload.path = `${cleanPath}/${tsvFile}`;\n } else {\n node.warn('No ecotaxa TSV found in ' + cleanPath);\n return null;\n }\n } catch(e) {\n node.error('Cannot read directory: ' + e.message);\n return null;\n }\n }\n}\n\nreturn msg;", - "outputs": 1, - "timeout": 0, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [ - { - "var": "fs", - "module": "fs" - } - ], - "x": 930, - "y": 180, - "wires": [ - [ - "c4a624fe9c464cda" - ] - ] - }, - { - "id": "9b4aa58f8a37a9d9", - "type": "ui-template", - "z": "4fdbd7bacb797c5a", - "group": "0c1537ce71affc2b", - "page": "", - "ui": "", - "name": "Gallery", - "order": 1, - "width": 0, - "height": 0, - "head": "", - "format": "\n\n\n\n\n\n", - "storeOutMessages": true, - "passthru": true, - "resendOnRefresh": true, - "templateScope": "local", - "className": "", - "x": 920, - "y": 1000, - "wires": [ - [] - ] - }, - { - "id": "205e01594aafdf52", - "type": "ui-template", - "z": "4fdbd7bacb797c5a", - "group": "fa6393a7d7e3b7d7", - "page": "", - "ui": "", - "name": "List of Segmentation", - "order": 1, - "width": 0, - "height": 0, - "head": "", - "format": "\n\n\n\n", - "storeOutMessages": true, - "passthru": false, - "resendOnRefresh": true, - "templateScope": "local", - "className": "", - "x": 740, - "y": 180, - "wires": [ - [ - "82532431827450f2" - ] - ] - }, - { - "id": "82532431827450f2", - "type": "switch", - "z": "4fdbd7bacb797c5a", - "name": "Route Live Messages", - "property": "topic", - "propertyType": "msg", - "rules": [ - { - "t": "eq", - "v": "refresh_list", - "vt": "str" - }, - { - "t": "eq", - "v": "selected_acquisition", - "vt": "str" - } - ], - "checkall": "true", - "repair": false, - "outputs": 2, - "x": 960, - "y": 180, - "wires": [ - [ - "2f62fc38be1bfe69" - ], - [ - "51fa556f38fcfaa4" - ] - ] - }, - { - "id": "0c7765fa696e0717", - "type": "ui-template", - "z": "4fdbd7bacb797c5a", - "group": "53490f9c39e2065d", - "page": "", - "ui": "", - "name": "Infos", - "order": 1, - "width": 0, - "height": 0, - "head": "", - "format": "\n\n\n\n\n", - "storeOutMessages": true, - "passthru": true, - "resendOnRefresh": true, - "templateScope": "local", - "className": "", - "x": 910, - "y": 480, - "wires": [ - [] - ] - }, - { - "id": "1e51a51e73c8f2a5", - "type": "function", - "z": "4fdbd7bacb797c5a", - "name": "HEATMAP (object_x, object_y)", - "func": "// Heatmap \u2013 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, - "timeout": 0, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 510, - "y": 520, - "wires": [ - [ - "d49dc2cc9f8eeaab" - ] - ] - }, - { - "id": "8f1bf6abf93d9587", - "type": "function", - "z": "4fdbd7bacb797c5a", - "name": "ESD Histogram (object_equivalent_diameter)", - "func": "// ESD Size Spectrum \u2013 TSV \u2192 {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": 560, - "wires": [ - [ - "1631167b67b37d25" - ] - ] - }, - { - "id": "a67c2c1c9523ec54", - "type": "function", - "z": "4fdbd7bacb797c5a", - "name": "TIMELINE (sequence index + area)", - "func": "// Timeline \u2013 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": 600, - "wires": [ - [ - "9bb69f94e4855a16" - ] - ] - }, - { - "id": "9f4a3d23a3c4ba43", - "type": "function", - "z": "4fdbd7bacb797c5a", - "name": "COLORSPACE (Saturation vs Value)", - "func": "// Colorspace \u2013 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": 640, - "wires": [ - [ - "731608304eeaa5f3" - ] - ] - }, - { - "id": "836d93ca31e11fc8", - "type": "function", - "z": "4fdbd7bacb797c5a", - "name": "ASPECT (Width vs Height)", - "func": "// Aspect \u2013 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": 680, - "wires": [ - [ - "a6c62b4b3da492e9" - ] - ] - }, - { - "id": "03188ea81f8f748d", - "type": "function", - "z": "4fdbd7bacb797c5a", - "name": "GREENNESS (custom index + circularity)", - "func": "// Greenness vs Circularity \u2013 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": 720, - "wires": [ - [ - "fc22ad681d25523b" - ] - ] - }, - { - "id": "1db541fc5717964a", - "type": "function", - "z": "4fdbd7bacb797c5a", - "name": "COMPLEXITY (Area vs Perimeter)", - "func": "// Complexity \u2013 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": 760, - "wires": [ - [ - "2905bc12edeef503" - ] - ] - }, - { - "id": "109aacc68888f0b1", - "type": "function", - "z": "4fdbd7bacb797c5a", - "name": "TEXTURE (Area vs StdValue)", - "func": "// Texture \u2013 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": 800, - "wires": [ - [ - "5fe97b4a564498ea" - ] - ] - }, - { - "id": "27ebeb1895528a1d", - "type": "function", - "z": "4fdbd7bacb797c5a", - "name": "SOLIDITY (Histogram)", - "func": "// Solidity \u2013 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": 840, - "wires": [ - [ - "565c6d0f098e5be0" - ] - ] - }, - { - "id": "d49dc2cc9f8eeaab", - "type": "ui-template", - "z": "4fdbd7bacb797c5a", - "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": 920, - "y": 520, - "wires": [ - [] - ] - }, - { - "id": "2aa2c7cb2ba901a0", - "type": "function", - "z": "4fdbd7bacb797c5a", - "name": "Sample Identity Metadata Only", - "func": "// FUNCTION: Extract minimal metadata for the \u201cSample Identity\u201d 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": 480, - "wires": [ - [ - "0c7765fa696e0717" - ] - ] - }, - { - "id": "1631167b67b37d25", - "type": "ui-template", - "z": "4fdbd7bacb797c5a", - "group": "47800358cf7cee25", - "page": "", - "ui": "", - "name": "ESD Histogram", - "order": 1, - "width": 0, - "height": 0, - "head": "", - "format": "\n\n\n", - "storeOutMessages": true, - "passthru": true, - "resendOnRefresh": true, - "templateScope": "local", - "className": "", - "x": 940, - "y": 560, - "wires": [ - [] - ] - }, - { - "id": "9bb69f94e4855a16", - "type": "ui-template", - "z": "4fdbd7bacb797c5a", - "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": 920, - "y": 600, - "wires": [ - [] - ] - }, - { - "id": "731608304eeaa5f3", - "type": "ui-template", - "z": "4fdbd7bacb797c5a", - "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": 990, - "y": 640, - "wires": [ - [] - ] - }, - { - "id": "a6c62b4b3da492e9", - "type": "ui-template", - "z": "4fdbd7bacb797c5a", - "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": 970, - "y": 680, - "wires": [ - [] - ] - }, - { - "id": "fc22ad681d25523b", - "type": "ui-template", - "z": "4fdbd7bacb797c5a", - "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": 930, - "y": 720, - "wires": [ - [] - ] - }, - { - "id": "2905bc12edeef503", - "type": "ui-template", - "z": "4fdbd7bacb797c5a", - "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": 990, - "y": 760, - "wires": [ - [] - ] - }, - { - "id": "5fe97b4a564498ea", - "type": "ui-template", - "z": "4fdbd7bacb797c5a", - "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": 960, - "y": 800, - "wires": [ - [] - ] - }, - { - "id": "565c6d0f098e5be0", - "type": "ui-template", - "z": "4fdbd7bacb797c5a", - "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": 950, - "y": 840, - "wires": [ - [] - ] - }, - { - "id": "8fc92b69ced53cf2", - "type": "function", - "z": "4fdbd7bacb797c5a", - "name": "EXPLORER", - "func": "// Explorer Function Node\n// TSV \u2192 {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 // Extract pixel size (microns per pixel) - always needed for scale bar rendering\n const px = parseFloat(val(firstRow, \"process_pixel\"));\n if (!isNaN(px) && px > 0) {\n meta.resolution = px;\n }\n // Flag: if true, measurement values (width, height, area, etc.) are already in microns\n const applied = val(firstRow, \"process_pixel_applied\");\n meta.process_pixel_applied = (applied && applied.toString().toLowerCase() === \"true\");\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\", \"object_blur_laplacian\"\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: `/api/files/objects/${(val(row,\"object_date\")||\"\").replace(/ /g,\"_\")}/${(val(row,\"sample_id\")||\"\").replace(/ /g,\"_\")}/${(val(row,\"acq_id\")||\"\").replace(/ /g,\"_\")}/${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": 920, - "wires": [ - [ - "131aae14607d4830" - ] - ] - }, - { - "id": "131aae14607d4830", - "type": "ui-template", - "z": "4fdbd7bacb797c5a", - "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": 920, - "y": 920, - "wires": [ - [] - ] - }, - { - "id": "69ce53c2079b63a0", - "type": "function", - "z": "4fdbd7bacb797c5a", - "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": 460, - "y": 440, - "wires": [ - [ - "d49dc2cc9f8eeaab", - "1631167b67b37d25", - "9bb69f94e4855a16", - "731608304eeaa5f3", - "a6c62b4b3da492e9", - "fc22ad681d25523b", - "2905bc12edeef503", - "5fe97b4a564498ea", - "565c6d0f098e5be0", - "0c7765fa696e0717", - "131aae14607d4830", - "9b4aa58f8a37a9d9" - ] - ] - }, - { - "id": "88371b41526bbd18", - "type": "function", - "z": "4fdbd7bacb797c5a", - "name": "GALLERY", - "func": "// GALLERY Function Node\n// TSV \u2192 { 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 // Extract pixel size (microns per pixel) - always needed for scale bar rendering\n const px = parseFloat(get(firstRow, 'process_pixel'));\n if (!isNaN(px) && px > 0) {\n meta.resolution = px;\n }\n // Flag: if true, measurement values (width, height, area, etc.) are already in microns\n const applied = get(firstRow, 'process_pixel_applied');\n meta.process_pixel_applied = (applied && applied.toString().toLowerCase() === \"true\");\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\", \"object_blur_laplacian\"\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')||'').replace(/ /g,'_')}/${(get(row, 'sample_id')||'').replace(/ /g,'_')}/${(get(row, 'acq_id')||'').replace(/ /g,'_')}/${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": 1000, - "wires": [ - [ - "9b4aa58f8a37a9d9" - ] - ] - }, - { - "id": "e16e3dace2bb507f", - "type": "delay", - "z": "4fdbd7bacb797c5a", - "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": 340, - "wires": [ - [ - "bb7e84695ec2b24c" - ] - ] - }, - { - "id": "ebb2567bdaf69c8e", - "type": "ui-template", - "z": "4fdbd7bacb797c5a", - "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": "c4a624fe9c464cda", - "type": "switch", - "z": "4fdbd7bacb797c5a", - "name": "", - "property": "topic", - "propertyType": "msg", - "rules": [ - { - "t": "neq", - "v": "$pageview", - "vt": "str" - } - ], - "checkall": "true", - "repair": false, - "outputs": 1, - "x": 170, - "y": 300, - "wires": [ - [ - "e16e3dace2bb507f" - ] - ] - }, - { - "id": "d365c80d0854bab7", - "type": "debug", - "z": "4fdbd7bacb797c5a", - "name": "debug 5", - "active": true, - "tosidebar": true, - "console": false, - "tostatus": false, - "complete": "true", - "targetType": "full", - "statusVal": "", - "statusType": "auto", - "x": 940, - "y": 400, - "wires": [] - }, - { - "id": "93ac167a60a35472", - "type": "function", - "z": "4fdbd7bacb797c5a", - "name": "Sample Identity Metadata Only", - "func": "// FUNCTION: Extract all data and metadata\nconst lines = msg.payload.toString().trim().split(/\\r?\\n/);\nif (lines.length < 2) return null;\n\nconst headers = lines[0].split(\"\\t\");\n\n// Fonction utilitaire pour mapper les colonnes\nconst rowToObject = (rowCells) => {\n const obj = {};\n headers.forEach((header, index) => {\n obj[header] = rowCells[index] || \"\";\n });\n return obj;\n};\n\nlet allRows = [];\nlet firstDataRow = null;\n\n// --- 1. Parser toutes les lignes ---\nfor (let i = 1; i < lines.length; i++) {\n const cells = lines[i].split(\"\\t\");\n const rowObj = rowToObject(cells);\n\n allRows.push(rowObj);\n\n // Identifier la premi\u00e8re vraie ligne de donn\u00e9es pour les m\u00e9tadonn\u00e9es globales\n if (!firstDataRow && !lines[i].trim().startsWith(\"[\")) {\n firstDataRow = rowObj;\n }\n}\n\n// --- 2. Extraire les m\u00e9tadonn\u00e9es (bas\u00e9es sur la premi\u00e8re ligne valide) ---\nconst meta = {\n sample_id: firstDataRow ? firstDataRow[\"sample_id\"] : \"\",\n project: firstDataRow ? firstDataRow[\"sample_project\"] : \"\",\n acq_id: firstDataRow ? firstDataRow[\"acq_id\"] : \"\"\n};\n\n// --- 3. Output : M\u00e9tadonn\u00e9es + l'int\u00e9gralit\u00e9 des donn\u00e9es ---\nmsg.payload = {\n meta: meta,\n data: allRows // Contient maintenant tous les enregistrements\n};\n\nreturn msg;", - "outputs": 1, - "timeout": 0, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 510, - "y": 400, - "wires": [ - [ - "d365c80d0854bab7" - ] - ] - }, - { - "id": "72065cd82cb87df5", - "type": "debug", - "z": "4fdbd7bacb797c5a", - "name": "debug 2", - "active": true, - "tosidebar": true, - "console": false, - "tostatus": false, - "complete": "false", - "statusVal": "", - "statusType": "auto", - "x": 440, - "y": 340, - "wires": [] - }, - { - "id": "b2958e4a07a87cad", - "type": "ui-event", - "z": "a7825c4c81ad20a0", - "ui": "e6ae26617c24c3ea", - "name": "UI Event", - "x": 80, - "y": 40, - "wires": [ - [ - "b4515f99c5e5e2da", - "26ec7d57f8231752" - ] - ] - }, - { - "id": "b4515f99c5e5e2da", - "type": "switch", - "z": "a7825c4c81ad20a0", - "name": "msg.payload.page.path === \"/visualization\"", - "property": "payload.page.path", - "propertyType": "msg", - "rules": [ - { - "t": "eq", - "v": "/visualization", - "vt": "str" - } - ], - "checkall": "true", - "repair": false, - "outputs": 1, - "x": 330, - "y": 40, - "wires": [ - [ - "a15937db31847a11", - "99d07bf34db5693f" - ] - ] - }, - { - "id": "82cee7ccf168f572", - "type": "ui-template", - "z": "a7825c4c81ad20a0", - "group": "b570f76ef526af45", - "page": "", - "ui": "", - "name": "Navigation Top", - "order": 2, - "width": 0, - "height": 0, - "head": "", - "format": "\n\n", - "storeOutMessages": true, - "passthru": true, - "resendOnRefresh": true, - "templateScope": "local", - "className": "", - "x": 1300, - "y": 40, - "wires": [ - [] - ] - }, - { - "id": "a15937db31847a11", - "type": "list segmentations", - "z": "a7825c4c81ad20a0", - "name": "", - "x": 170, - "y": 180, - "wires": [ - [ - "4f4d57c070b89857", - "adc7e075bda50785" - ] - ] - }, - { - "id": "4f4d57c070b89857", - "type": "function", - "z": "a7825c4c81ad20a0", - "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\u00e9ation 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, - "initialize": "", - "finalize": "", - "libs": [], - "x": 440, - "y": 180, - "wires": [ - [ - "eb62a19058171fa8" - ] - ] - }, - { - "id": "1802e96dd833363b", - "type": "function", - "z": "a7825c4c81ad20a0", - "name": "Size-safe file read", - "func": "const MAX_LINES = 5001;\nconst MAX_SIZE = 10 * 1024 * 1024;\n\nconst filePath = msg.payload.path || msg.filename;\nif (!filePath) { return null; }\n\ntry {\n const stats = fs.statSync(filePath);\n if (stats.size > MAX_SIZE) {\n const fd = fs.openSync(filePath, \"r\");\n const buf = Buffer.alloc(Math.min(stats.size, 2 * 1024 * 1024));\n const bytesRead = fs.readSync(fd, buf, 0, buf.length, 0);\n fs.closeSync(fd);\n const partial = buf.toString(\"utf8\", 0, bytesRead);\n const lines = partial.split(\"\\n\");\n msg.payload = lines.slice(0, MAX_LINES).join(\"\\n\");\n node.warn(\"Large TSV (\" + Math.round(stats.size/1024/1024) + \"MB) - limited to \" + MAX_LINES + \" lines\");\n } else {\n msg.payload = fs.readFileSync(filePath, \"utf8\");\n }\n return msg;\n} catch(e) {\n node.error(\"File read error: \" + e.message, msg);\n return null;\n}", - "outputs": 1, - "timeout": 0, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [ - { - "var": "fs", - "module": "fs" - } - ], - "x": 180, - "y": 380, - "wires": [ - [ - "82d199f05a6f0af2", - "248999e928a14aa9", - "09b02605b198b2f4", - "897140ab8ebde04d", - "ea4aa3af0f18be63", - "42b7f7bec1d2da57", - "09203894702cf830", - "db14c860ad4ab4ec", - "eb793dfbf316c8d9", - "63e7888198376e77", - "da58d147efe428ea", - "f8b996c6c3c8e77a", - "a351f0483ac3a747" - ] - ] - }, - { - "id": "63121349be3135fd", - "type": "function", - "z": "a7825c4c81ad20a0", - "name": "Get tsv path", - "func": "// 1. On s'assure que le path existe\nif (msg.payload.path) {\n\n const currentPath = msg.payload.path;\n const cleanPath = currentPath.endsWith('/') ? currentPath.slice(0, -1) : currentPath;\n const acqId = cleanPath.split('/').pop();\n\n // Save the actual directory path relative to objects root for image URL construction\n // e.g. \"/home/pi/data/objects/2023-10-12/SomeName_A1\" -> \"2023-10-12/SomeName_A1\"\n const objRoot = \"/home/pi/data/objects/\";\n msg.tsvDir = cleanPath.startsWith(objRoot) ? cleanPath.slice(objRoot.length) : cleanPath;\n\n // Try expected filename first\n const expectedTsv = `${cleanPath}/ecotaxa_${acqId}.tsv`;\n\n if (fs.existsSync(expectedTsv)) {\n msg.payload.path = expectedTsv;\n } else {\n // Fallback: find any ecotaxa_*.tsv in the directory\n try {\n const files = fs.readdirSync(cleanPath);\n const tsvFile = files.find(f => f.startsWith('ecotaxa_') && f.endsWith('.tsv'));\n if (tsvFile) {\n msg.payload.path = `${cleanPath}/${tsvFile}`;\n } else {\n node.warn('No ecotaxa TSV found in ' + cleanPath);\n return null;\n }\n } catch(e) {\n node.error('Cannot read directory: ' + e.message);\n return null;\n }\n }\n}\n\nreturn msg;", - "outputs": 1, - "timeout": 0, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [ - { - "var": "fs", - "module": "fs" - } - ], - "x": 930, - "y": 180, - "wires": [ - [ - "aa474626711fa5b6" - ] - ] - }, - { - "id": "2133c934126655d8", - "type": "ui-template", - "z": "a7825c4c81ad20a0", - "group": "0c1537ce71affc2b", - "page": "", - "ui": "", - "name": "Gallery", - "order": 2, - "width": 0, - "height": 0, - "head": "", - "format": "\n\n\n\n\n\n", - "storeOutMessages": true, - "passthru": true, - "resendOnRefresh": true, - "templateScope": "local", - "className": "", - "x": 920, - "y": 1000, - "wires": [ - [] - ] - }, - { - "id": "eb62a19058171fa8", - "type": "ui-template", - "z": "a7825c4c81ad20a0", - "group": "fa6393a7d7e3b7d7", - "page": "", - "ui": "", - "name": "List of Segmentation", - "order": 2, - "width": 0, - "height": 0, - "head": "", - "format": "\n\n\n\n", - "storeOutMessages": true, - "passthru": false, - "resendOnRefresh": true, - "templateScope": "local", - "className": "", - "x": 700, - "y": 180, - "wires": [ - [ - "63121349be3135fd", - "68ec210262cb873a" - ] - ] - }, - { - "id": "ce136c26883f5d99", - "type": "ui-template", - "z": "a7825c4c81ad20a0", - "group": "53490f9c39e2065d", - "page": "", - "ui": "", - "name": "Infos", - "order": 2, - "width": 0, - "height": 0, - "head": "", - "format": "\n\n\n\n\n", - "storeOutMessages": true, - "passthru": true, - "resendOnRefresh": true, - "templateScope": "local", - "className": "", - "x": 910, - "y": 440, - "wires": [ - [] - ] - }, - { - "id": "82d199f05a6f0af2", - "type": "function", - "z": "a7825c4c81ad20a0", - "name": "HEATMAP (object_x, object_y)", - "func": "// Heatmap \u2013 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, - "timeout": 0, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 510, - "y": 520, - "wires": [ - [ - "fa3836bcb90f19fd" - ] - ] - }, - { - "id": "248999e928a14aa9", - "type": "function", - "z": "a7825c4c81ad20a0", - "name": "ESD Histogram (object_equivalent_diameter)", - "func": "// ESD Size Spectrum \u2013 TSV \u2192 {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": 560, - "wires": [ - [ - "884695dfb99afa9c" - ] - ] - }, - { - "id": "09b02605b198b2f4", - "type": "function", - "z": "a7825c4c81ad20a0", - "name": "TIMELINE (sequence index + area)", - "func": "// Timeline \u2013 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": 600, - "wires": [ - [ - "6d881dec33245014" - ] - ] - }, - { - "id": "897140ab8ebde04d", - "type": "function", - "z": "a7825c4c81ad20a0", - "name": "COLORSPACE (Saturation vs Value)", - "func": "// Colorspace \u2013 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": 640, - "wires": [ - [ - "d7416bf20b4dc8bc" - ] - ] - }, - { - "id": "ea4aa3af0f18be63", - "type": "function", - "z": "a7825c4c81ad20a0", - "name": "ASPECT (Width vs Height)", - "func": "// Aspect \u2013 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": 680, - "wires": [ - [ - "14cc9379f5c5c333" - ] - ] - }, - { - "id": "42b7f7bec1d2da57", - "type": "function", - "z": "a7825c4c81ad20a0", - "name": "GREENNESS (custom index + circularity)", - "func": "// Greenness vs Circularity \u2013 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": 720, - "wires": [ - [ - "cd8b19e450d40ff7" - ] - ] - }, - { - "id": "09203894702cf830", - "type": "function", - "z": "a7825c4c81ad20a0", - "name": "COMPLEXITY (Area vs Perimeter)", - "func": "// Complexity \u2013 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": 760, - "wires": [ - [ - "5267a7f80cdf6c6f" - ] - ] - }, - { - "id": "db14c860ad4ab4ec", - "type": "function", - "z": "a7825c4c81ad20a0", - "name": "TEXTURE (Area vs StdValue)", - "func": "// Texture \u2013 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": 800, - "wires": [ - [ - "a6df52a13d3d310f" - ] - ] - }, - { - "id": "eb793dfbf316c8d9", - "type": "function", - "z": "a7825c4c81ad20a0", - "name": "SOLIDITY (Histogram)", - "func": "// Solidity \u2013 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": 840, - "wires": [ - [ - "31ba1239ad82a60c" - ] - ] - }, - { - "id": "fa3836bcb90f19fd", - "type": "ui-template", - "z": "a7825c4c81ad20a0", - "group": "90b33e458dd29d04", - "page": "", - "ui": "", - "name": "Heatmap", - "order": 2, - "width": 0, - "height": 0, - "head": "", - "format": "\n\n\n\n\n", - "storeOutMessages": true, - "passthru": true, - "resendOnRefresh": true, - "templateScope": "local", - "className": "", - "x": 920, - "y": 520, - "wires": [ - [] - ] - }, - { - "id": "63e7888198376e77", - "type": "function", - "z": "a7825c4c81ad20a0", - "name": "Sample Identity Metadata Only", - "func": "// FUNCTION: Extract minimal metadata for the \u201cSample Identity\u201d 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": [ - [ - "ce136c26883f5d99" - ] - ] - }, - { - "id": "884695dfb99afa9c", - "type": "ui-template", - "z": "a7825c4c81ad20a0", - "group": "47800358cf7cee25", - "page": "", - "ui": "", - "name": "ESD Histogram", - "order": 2, - "width": 0, - "height": 0, - "head": "", - "format": "\n\n", - "storeOutMessages": true, - "passthru": true, - "resendOnRefresh": true, - "templateScope": "local", - "className": "", - "x": 940, - "y": 560, - "wires": [ - [] - ] - }, - { - "id": "6d881dec33245014", - "type": "ui-template", - "z": "a7825c4c81ad20a0", - "group": "1d9c6927a0e0a71b", - "page": "", - "ui": "", - "name": "Timeline", - "order": 2, - "width": 0, - "height": 0, - "head": "", - "format": "\n\n\n", - "storeOutMessages": true, - "passthru": true, - "resendOnRefresh": true, - "templateScope": "local", - "className": "", - "x": 920, - "y": 600, - "wires": [ - [] - ] - }, - { - "id": "d7416bf20b4dc8bc", - "type": "ui-template", - "z": "a7825c4c81ad20a0", - "group": "03ad1c8f517e8769", - "page": "", - "ui": "", - "name": "Colorspace (Saturation vs Value)", - "order": 2, - "width": 0, - "height": 0, - "head": "", - "format": "\n\n\n\n\n", - "storeOutMessages": true, - "passthru": true, - "resendOnRefresh": true, - "templateScope": "local", - "className": "", - "x": 990, - "y": 640, - "wires": [ - [] - ] - }, - { - "id": "14cc9379f5c5c333", - "type": "ui-template", - "z": "a7825c4c81ad20a0", - "group": "0be15f8190e6fd43", - "page": "", - "ui": "", - "name": "Aspect (Width vs Height)", - "order": 2, - "width": 0, - "height": 0, - "head": "", - "format": "\n\n\n\n\n", - "storeOutMessages": true, - "passthru": true, - "resendOnRefresh": true, - "templateScope": "local", - "className": "", - "x": 970, - "y": 680, - "wires": [ - [] - ] - }, - { - "id": "cd8b19e450d40ff7", - "type": "ui-template", - "z": "a7825c4c81ad20a0", - "group": "1edc962c44888abc", - "page": "", - "ui": "", - "name": "Greenness", - "order": 2, - "width": 0, - "height": 0, - "head": "", - "format": "\n\n\n\n\n", - "storeOutMessages": true, - "passthru": true, - "resendOnRefresh": true, - "templateScope": "local", - "className": "", - "x": 930, - "y": 720, - "wires": [ - [] - ] - }, - { - "id": "5267a7f80cdf6c6f", - "type": "ui-template", - "z": "a7825c4c81ad20a0", - "group": "67c63cc92c23c4b1", - "page": "", - "ui": "", - "name": "Complexity (Area vs Perimeter)", - "order": 2, - "width": 0, - "height": 0, - "head": "", - "format": "\n\n\n\n\n", - "storeOutMessages": true, - "passthru": true, - "resendOnRefresh": true, - "templateScope": "local", - "className": "", - "x": 990, - "y": 760, - "wires": [ - [] - ] - }, - { - "id": "a6df52a13d3d310f", - "type": "ui-template", - "z": "a7825c4c81ad20a0", - "group": "95a152a68b7ba779", - "page": "", - "ui": "", - "name": "Texture (Area vs Std)", - "order": 2, - "width": 0, - "height": 0, - "head": "", - "format": "\n\n\n\n\n", - "storeOutMessages": true, - "passthru": true, - "resendOnRefresh": true, - "templateScope": "local", - "className": "", - "x": 960, - "y": 800, - "wires": [ - [] - ] - }, - { - "id": "31ba1239ad82a60c", - "type": "ui-template", - "z": "a7825c4c81ad20a0", - "group": "1baa943e505febf1", - "page": "", - "ui": "", - "name": "Solidity Histogram", - "order": 2, - "width": 0, - "height": 0, - "head": "", - "format": "\n\n\n\n\n", - "storeOutMessages": true, - "passthru": true, - "resendOnRefresh": true, - "templateScope": "local", - "className": "", - "x": 950, - "y": 840, - "wires": [ - [] - ] - }, - { - "id": "da58d147efe428ea", - "type": "function", - "z": "a7825c4c81ad20a0", - "name": "EXPLORER", - "func": "// Explorer Function Node\n// TSV \u2192 {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 // Extract pixel size (microns per pixel) - always needed for scale bar rendering\n const px = parseFloat(val(firstRow, \"process_pixel\"));\n if (!isNaN(px) && px > 0) {\n meta.resolution = px;\n }\n // Flag: if true, measurement values (width, height, area, etc.) are already in microns\n const applied = val(firstRow, \"process_pixel_applied\");\n meta.process_pixel_applied = (applied && applied.toString().toLowerCase() === \"true\");\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\", \"object_blur_laplacian\"\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: msg.tsvDir ? `/api/files/objects/${msg.tsvDir}/${val(row,\"img_file_name\")}` : `/api/files/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\n// Cap rows to prevent browser crash on huge datasets\nconst MAX_ROWS = 5000;\nif (data.length > MAX_ROWS) data.length = MAX_ROWS;\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": 920, - "wires": [ - [ - "3d4c586bf5742617" - ] - ] - }, - { - "id": "3d4c586bf5742617", - "type": "ui-template", - "z": "a7825c4c81ad20a0", - "group": "c5651e1a3e56f3f5", - "page": "", - "ui": "", - "name": "Explorer", - "order": 2, - "width": 0, - "height": 0, - "head": "", - "format": "\n\n\n\n", - "storeOutMessages": true, - "passthru": true, - "resendOnRefresh": true, - "templateScope": "local", - "className": "", - "x": 920, - "y": 920, - "wires": [ - [] - ] - }, - { - "id": "26ec7d57f8231752", - "type": "function", - "z": "a7825c4c81ad20a0", - "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": [ - [ - "fa3836bcb90f19fd", - "884695dfb99afa9c", - "6d881dec33245014", - "d7416bf20b4dc8bc", - "14cc9379f5c5c333", - "cd8b19e450d40ff7", - "5267a7f80cdf6c6f", - "a6df52a13d3d310f", - "31ba1239ad82a60c", - "ce136c26883f5d99", - "3d4c586bf5742617", - "2133c934126655d8" - ] - ] - }, - { - "id": "f8b996c6c3c8e77a", - "type": "function", - "z": "a7825c4c81ad20a0", - "name": "GALLERY", - "func": "// GALLERY Function Node\n// TSV \u2192 { 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 // Extract pixel size (microns per pixel) - always needed for scale bar rendering\n const px = parseFloat(get(firstRow, 'process_pixel'));\n if (!isNaN(px) && px > 0) {\n meta.resolution = px;\n }\n // Flag: if true, measurement values (width, height, area, etc.) are already in microns\n const applied = get(firstRow, 'process_pixel_applied');\n meta.process_pixel_applied = (applied && applied.toString().toLowerCase() === \"true\");\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\", \"object_blur_laplacian\"\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: msg.tsvDir ? `/api/files/objects/${msg.tsvDir}/${get(row, 'img_file_name')}` : `/api/files/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\n// Cap rows to prevent browser crash on huge datasets\nconst MAX_ROWS = 5000;\nif (data.length > MAX_ROWS) data.length = MAX_ROWS;\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": 1000, - "wires": [ - [ - "2133c934126655d8" - ] - ] - }, - { - "id": "59c2b02e7b50171f", - "type": "delay", - "z": "a7825c4c81ad20a0", - "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": 340, - "wires": [ - [ - "1802e96dd833363b" - ] - ] - }, - { - "id": "8c6d06345732ad31", - "type": "ui-template", - "z": "a7825c4c81ad20a0", - "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": "aa474626711fa5b6", - "type": "switch", - "z": "a7825c4c81ad20a0", - "name": "", - "property": "topic", - "propertyType": "msg", - "rules": [ - { - "t": "neq", - "v": "$pageview", - "vt": "str" - } - ], - "checkall": "true", - "repair": false, - "outputs": 1, - "x": 170, - "y": 300, - "wires": [ - [ - "59c2b02e7b50171f" - ] - ] - }, - { - "id": "a351f0483ac3a747", - "type": "function", - "z": "a7825c4c81ad20a0", - "name": "Sample Identity Metadata Only", - "func": "// FUNCTION: Extract all data and metadata\nconst lines = msg.payload.toString().trim().split(/\\r?\\n/);\nif (lines.length < 2) return null;\n\nconst headers = lines[0].split(\"\\t\");\n\n// Fonction utilitaire pour mapper les colonnes\nconst rowToObject = (rowCells) => {\n const obj = {};\n headers.forEach((header, index) => {\n obj[header] = rowCells[index] || \"\";\n });\n return obj;\n};\n\nlet allRows = [];\nlet firstDataRow = null;\n\n// --- 1. Parser toutes les lignes ---\nfor (let i = 1; i < lines.length; i++) {\n const cells = lines[i].split(\"\\t\");\n const rowObj = rowToObject(cells);\n\n allRows.push(rowObj);\n\n // Identifier la premi\u00e8re vraie ligne de donn\u00e9es pour les m\u00e9tadonn\u00e9es globales\n if (!firstDataRow && !lines[i].trim().startsWith(\"[\")) {\n firstDataRow = rowObj;\n }\n}\n\n// --- 2. Extraire les m\u00e9tadonn\u00e9es (bas\u00e9es sur la premi\u00e8re ligne valide) ---\nconst meta = {\n sample_id: firstDataRow ? firstDataRow[\"sample_id\"] : \"\",\n project: firstDataRow ? firstDataRow[\"sample_project\"] : \"\",\n acq_id: firstDataRow ? firstDataRow[\"acq_id\"] : \"\"\n};\n\n// --- 3. Output : M\u00e9tadonn\u00e9es + l'int\u00e9gralit\u00e9 des donn\u00e9es ---\nmsg.payload = {\n meta: meta,\n data: allRows // Contient maintenant tous les enregistrements\n};\n\nreturn msg;", - "outputs": 1, - "timeout": 0, - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 510, - "y": 340, - "wires": [ - [] - ] - }, - { - "id": "68ec210262cb873a", - "type": "debug", - "z": "a7825c4c81ad20a0", - "name": "debug 3", - "active": true, - "tosidebar": true, - "console": false, - "tostatus": false, - "complete": "false", - "statusVal": "", - "statusType": "auto", - "x": 960, - "y": 140, - "wires": [] - }, - { - "id": "adc7e075bda50785", - "type": "debug", - "z": "a7825c4c81ad20a0", - "name": "debug 6", - "active": true, - "tosidebar": true, - "console": false, - "tostatus": false, - "complete": "false", - "statusVal": "", - "statusType": "auto", - "x": 380, - "y": 140, - "wires": [] - }, - { - "id": "99d07bf34db5693f", - "type": "debug", - "z": "a7825c4c81ad20a0", - "name": "debug 7", - "active": true, - "tosidebar": true, - "console": false, - "tostatus": false, - "complete": "false", - "statusVal": "", - "statusType": "auto", - "x": 660, - "y": 60, - "wires": [] - }, - { - "id": "314a079a9f687b5f", - "type": "ui-template", - "z": "6fac9fa6a894b293", - "group": "822cdc5b6ef13f39", - "page": "", - "ui": "", - "name": "body", - "order": 1, - "width": "0", - "height": "0", - "head": "", - "format": "\n", - "storeOutMessages": true, - "passthru": true, - "resendOnRefresh": true, - "templateScope": "local", - "className": "", - "x": 490, - "y": 320, - "wires": [ - [] - ] - }, - { - "id": "4f3bb4938e8a4db6", - "type": "ui-template", - "z": "6fac9fa6a894b293", - "group": "c29c835a70533b69", - "page": "", - "ui": "", - "name": "header", - "order": 1, - "width": "12", - "height": "6", - "head": "", - "format": "\n", - "storeOutMessages": true, - "passthru": true, - "resendOnRefresh": true, - "templateScope": "local", - "className": "", - "x": 490, - "y": 180, - "wires": [ - [] - ] - }, - { - "id": "aa79e9d1d4c21a2b", - "type": "mqtt out", - "z": "6fac9fa6a894b293", - "name": "", - "topic": "", - "qos": "", - "retain": "", - "broker": "8dc3722c.06efa8", - "x": 710, - "y": 320, - "wires": [] - }, - { - "id": "2ca7d039baf0651d", - "type": "inject", - "z": "6fac9fa6a894b293", - "name": "", - "props": [ - { - "p": "topic", - "vt": "str" - }, - { - "p": "payload" - } - ], - "repeat": "", - "crontab": "", - "once": false, - "onceDelay": 0.1, - "topic": "calibration", - "payload": "{ \"action\": \"start\" }", - "payloadType": "json", - "x": 290, - "y": 280, - "wires": [ - [ - "97634d7d17fefdeb" - ] - ] - }, - { - "id": "97634d7d17fefdeb", - "type": "ui-template", - "z": "6fac9fa6a894b293", - "group": "c29c835a70533b69", - "page": "", - "ui": "", - "name": "Calibration", - "order": 2, - "width": "0", - "height": "0", - "head": "", - "format": "\n\n\n\n", - "storeOutMessages": true, - "passthru": true, - "resendOnRefresh": true, - "templateScope": "local", - "className": "", - "x": 510, - "y": 280, - "wires": [ - [ - "aa79e9d1d4c21a2b", - "3ec78b1561245371" - ] - ] - }, - { - "id": "3ec78b1561245371", - "type": "function", - "z": "6fac9fa6a894b293", - "name": "set light settings", - "func": "// On v\u00e9rifie si le message contient les propri\u00e9t\u00e9s attendues\n// avant de mettre \u00e0 jour les variables globales.\n\nif (msg.payload) {\n\n // Si led_status est pr\u00e9sent dans le payload, on stocke\n if (msg.payload.led_status !== undefined) {\n global.set(\"led_status\", msg.payload.led_status);\n }\n\n // Si calibration_led_intensity est pr\u00e9sent dans le payload, on stocke\n if (msg.payload.calibration_led_intensity !== undefined) {\n global.set(\"calibration_led_intensity\", msg.payload.calibration_led_intensity);\n }\n}\n\nreturn msg;", - "outputs": 1, - "timeout": "", - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 740, - "y": 280, - "wires": [ - [] - ] - }, - { - "id": "f5ecfcfd42bb61f0", - "type": "ui-template", - "z": "d70bc885c4bf46d4", - "g": "c398718e1269dc31", - "group": "af8acdfe9afbad74", - "page": "", - "ui": "", - "name": "Step Bar", - "order": 1, - "width": 0, - "height": 0, - "head": "", - "format": "\n\n\n", - "storeOutMessages": true, - "passthru": true, - "resendOnRefresh": true, - "templateScope": "local", - "className": "", - "x": 1080, - "y": 60, - "wires": [ - [ - "c1c7cc675878b0f3" - ] - ] - }, - { - "id": "c1c7cc675878b0f3", - "type": "ui-control", - "z": "d70bc885c4bf46d4", - "g": "c398718e1269dc31", - "name": "", - "ui": "e6ae26617c24c3ea", - "events": "all", - "x": 1280, - "y": 60, - "wires": [ - [] - ] - }, - { - "id": "90e29d79c8074c17", - "type": "ui-event", - "z": "d70bc885c4bf46d4", - "ui": "e6ae26617c24c3ea", - "name": "UI Event", - "x": 80, - "y": 40, - "wires": [ - [ - "82a9fd78e0554323" - ] - ] - }, - { - "id": "82a9fd78e0554323", - "type": "switch", - "z": "d70bc885c4bf46d4", - "name": "msg.payload.page.path === \"/calibration_saturation_level\"", - "property": "payload.page.path", - "propertyType": "msg", - "rules": [ - { - "t": "eq", - "v": "/calibration_saturation_level", - "vt": "str" - } - ], - "checkall": "true", - "repair": false, - "outputs": 1, - "x": 380, - "y": 40, - "wires": [ - [ - "6219b9a464bc8109" - ] - ] - }, - { - "id": "6219b9a464bc8109", - "type": "function", - "z": "d70bc885c4bf46d4", - "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": [ - [ - "10c29a65226c0da4" - ] - ] - }, - { - "id": "10951f5abc907b1d", - "type": "mqtt out", - "z": "d70bc885c4bf46d4", - "name": "", - "topic": "", - "qos": "", - "retain": "", - "broker": "8dc3722c.06efa8", - "x": 850, - "y": 100, - "wires": [] - }, - { - "id": "d1b0704bd6aeb092", - "type": "function", - "z": "d70bc885c4bf46d4", - "name": "set wb 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;", - "outputs": 1, - "timeout": "", - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 980, - "y": 180, - "wires": [ - [] - ] - }, - { - "id": "2da968133f63a1fe", - "type": "switch", - "z": "d70bc885c4bf46d4", - "name": "", - "property": "topic", - "propertyType": "msg", - "rules": [ - { - "t": "eq", - "v": "imager/image", - "vt": "str" - } - ], - "checkall": "true", - "repair": false, - "outputs": 1, - "x": 590, - "y": 140, - "wires": [ - [ - "d1b0704bd6aeb092", - "10951f5abc907b1d" - ] - ] - }, - { - "id": "10c29a65226c0da4", - "type": "ui-template", - "z": "d70bc885c4bf46d4", - "group": "af8acdfe9afbad74", - "page": "", - "ui": "", - "name": "Calibration - WB", - "order": 2, - "width": "0", - "height": "0", - "head": "", - "format": "\n\n\n\n", - "storeOutMessages": true, - "passthru": true, - "resendOnRefresh": true, - "templateScope": "local", - "className": "", - "x": 380, - "y": 140, - "wires": [ - [ - "2da968133f63a1fe" - ] - ] - }, - { - "id": "5331fa94d1b90654", - "type": "ui-template", - "z": "9dfa8db7ce31208f", - "g": "3f734d83a7327043", - "group": "728c7ed3d63dfcee", - "page": "", - "ui": "", - "name": "Step Bar", - "order": 1, - "width": 0, - "height": 0, - "head": "", - "format": "\n\n\n", - "storeOutMessages": true, - "passthru": true, - "resendOnRefresh": true, - "templateScope": "local", - "className": "", - "x": 1080, - "y": 60, - "wires": [ - [ - "5f823fb3b1c49c65" - ] - ] - }, - { - "id": "5f823fb3b1c49c65", - "type": "ui-control", - "z": "9dfa8db7ce31208f", - "g": "3f734d83a7327043", - "name": "", - "ui": "e6ae26617c24c3ea", - "events": "all", - "x": 1280, - "y": 60, - "wires": [ - [] - ] - }, - { - "id": "bd3b1d9f854c367c", - "type": "ui-event", - "z": "9dfa8db7ce31208f", - "ui": "e6ae26617c24c3ea", - "name": "UI Event", - "x": 80, - "y": 40, - "wires": [ - [ - "bff2be213a3b76b2" - ] - ] - }, - { - "id": "bff2be213a3b76b2", - "type": "switch", - "z": "9dfa8db7ce31208f", - "name": "msg.payload.page.path === \"/calibration_lightness\"", - "property": "payload.page.path", - "propertyType": "msg", - "rules": [ - { - "t": "eq", - "v": "/calibration_lightness", - "vt": "str" - } - ], - "checkall": "true", - "repair": false, - "outputs": 1, - "x": 360, - "y": 40, - "wires": [ - [ - "43220ad00872b995" - ] - ] - }, - { - "id": "43220ad00872b995", - "type": "function", - "z": "9dfa8db7ce31208f", - "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": [ - [ - "c9a6d6f19ff18dda" - ] - ] - }, - { - "id": "c9a6d6f19ff18dda", - "type": "ui-template", - "z": "9dfa8db7ce31208f", - "group": "728c7ed3d63dfcee", - "page": "", - "ui": "", - "name": "Calibration - Lightness", - "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": [ - [ - "b067a2833aee498d" - ] - ] - }, - { - "id": "371de3becc1c4e80", - "type": "mqtt out", - "z": "9dfa8db7ce31208f", - "name": "", - "topic": "", - "qos": "", - "retain": "", - "broker": "8dc3722c.06efa8", - "x": 850, - "y": 100, - "wires": [] - }, - { - "id": "b067a2833aee498d", - "type": "switch", - "z": "9dfa8db7ce31208f", - "name": "", - "property": "topic", - "propertyType": "msg", - "rules": [ - { - "t": "eq", - "v": "light", - "vt": "str" - } - ], - "checkall": "true", - "repair": false, - "outputs": 1, - "x": 590, - "y": 140, - "wires": [ - [ - "371de3becc1c4e80" - ] - ] - }, - { - "id": "9d60b1bdff1a8058", - "type": "ui-template", - "z": "e597cdc71d5a9d33", - "g": "f2d9c0f9aabc0a63", - "group": "c966455a52d121c0", - "page": "", - "ui": "", - "name": "Step Bar", - "order": 1, - "width": 0, - "height": 0, - "head": "", - "format": "\n\n\n", - "storeOutMessages": true, - "passthru": true, - "resendOnRefresh": true, - "templateScope": "local", - "className": "", - "x": 1080, - "y": 60, - "wires": [ - [ - "48d61ea72d468010" - ] - ] - }, - { - "id": "48d61ea72d468010", - "type": "ui-control", - "z": "e597cdc71d5a9d33", - "g": "f2d9c0f9aabc0a63", - "name": "", - "ui": "e6ae26617c24c3ea", - "events": "all", - "x": 1280, - "y": 60, - "wires": [ - [] - ] - }, - { - "id": "f4884f6f1ea5cdb5", - "type": "ui-template", - "z": "e597cdc71d5a9d33", - "group": "c966455a52d121c0", - "page": "", - "ui": "", - "name": "Calibration - Pixel Size", - "order": 2, - "width": "0", - "height": "0", - "head": "", - "format": "\n\n\n\n\n\n", - "storeOutMessages": true, - "passthru": true, - "resendOnRefresh": true, - "templateScope": "local", - "className": "", - "x": 400, - "y": 140, - "wires": [ - [ - "0327248fda7c3f7e" - ] - ] - }, - { - "id": "472f4ba991a77e43", - "type": "ui-event", - "z": "e597cdc71d5a9d33", - "ui": "e6ae26617c24c3ea", - "name": "UI Event", - "x": 80, - "y": 40, - "wires": [ - [ - "60b4a20faf7a015e" - ] - ] - }, - { - "id": "60b4a20faf7a015e", - "type": "switch", - "z": "e597cdc71d5a9d33", - "name": "msg.payload.page.path === \"/calibration_pixel_size\"", - "property": "payload.page.path", - "propertyType": "msg", - "rules": [ - { - "t": "eq", - "v": "/calibration_pixel_size", - "vt": "str" - } - ], - "checkall": "true", - "repair": false, - "outputs": 1, - "x": 360, - "y": 40, - "wires": [ - [ - "3e2441107949295a" - ] - ] - }, - { - "id": "3e2441107949295a", - "type": "function", - "z": "e597cdc71d5a9d33", - "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": [ - [ - "f4884f6f1ea5cdb5" - ] - ] - }, - { - "id": "0327248fda7c3f7e", - "type": "switch", - "z": "e597cdc71d5a9d33", - "name": "", - "property": "topic", - "propertyType": "msg", - "rules": [ - { - "t": "eq", - "v": "calibration/save", - "vt": "str" - } - ], - "checkall": "true", - "repair": false, - "outputs": 1, - "x": 590, - "y": 140, - "wires": [ - [ - "133c27ef75317205" - ] - ] - }, - { - "id": "133c27ef75317205", - "type": "function", - "z": "e597cdc71d5a9d33", - "name": "set calibration_pixel_size", - "func": "if (msg.topic) {\n global.set(\"process_pixel\", msg.payload.calibration_pixel_size);\n global.set(\"process_pixel_size\", msg.payload.calibration_pixel_size);\n global.set(\"calibration_pixel_size\", msg.payload.calibration_pixel_size);\n global.set(\"calibration_scale_factor\", msg.payload.calibration_scale_factor);\n global.set(\"calibration_sensor_width\", msg.payload.calibration_sensor_width);\n global.set(\"calibration_stream_width\", msg.payload.calibration_stream_width);\n global.set(\"calibration_known_distance\", msg.payload.calibration_known_distance);\n global.set(\"calibration_measured_distance\", msg.payload.calibration_measured_distance);\n global.set(\"calibration_markerA_x\", msg.payload.calibration_markerA_x);\n global.set(\"calibration_markerA_y\", msg.payload.calibration_markerA_y);\n global.set(\"calibration_markerB_x\", msg.payload.calibration_markerB_x);\n global.set(\"calibration_markerB_y\", msg.payload.calibration_markerB_y);\n}\nreturn msg;", - "outputs": 1, - "timeout": "", - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 1010, - "y": 180, - "wires": [ - [] - ] - }, - { - "id": "07e989594965ab7d", - "type": "ui-template", - "z": "b8e6b9dc69aa3f56", - "g": "a75dd1f78f8f991a", - "group": "6039d653304537af", - "page": "", - "ui": "", - "name": "Step Bar", - "order": 1, - "width": 0, - "height": 0, - "head": "", - "format": "\n\n\n", - "storeOutMessages": true, - "passthru": true, - "resendOnRefresh": true, - "templateScope": "local", - "className": "", - "x": 1080, - "y": 60, - "wires": [ - [ - "5e94860de36e0a1c" - ] - ] - }, - { - "id": "5e94860de36e0a1c", - "type": "ui-control", - "z": "b8e6b9dc69aa3f56", - "g": "a75dd1f78f8f991a", - "name": "", - "ui": "e6ae26617c24c3ea", - "events": "all", - "x": 1280, - "y": 60, - "wires": [ - [] - ] - }, - { - "id": "3430afb16e1eccf0", - "type": "ui-event", - "z": "b8e6b9dc69aa3f56", - "ui": "e6ae26617c24c3ea", - "name": "UI Event", - "x": 80, - "y": 40, - "wires": [ - [ - "4d3f1761bfaea85b" - ] - ] - }, - { - "id": "4d3f1761bfaea85b", - "type": "switch", - "z": "b8e6b9dc69aa3f56", - "name": "msg.payload.page.path === \"/calibration_pump\"", - "property": "payload.page.path", - "propertyType": "msg", - "rules": [ - { - "t": "eq", - "v": "/calibration_pump", - "vt": "str" - } - ], - "checkall": "true", - "repair": false, - "outputs": 1, - "x": 350, - "y": 40, - "wires": [ - [ - "d578e28343d8286b" - ] - ] - }, - { - "id": "d578e28343d8286b", - "type": "function", - "z": "b8e6b9dc69aa3f56", - "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": [ - [ - "1c11c424e6c0c405", - "33f590e854486a56" - ] - ] - }, - { - "id": "33f590e854486a56", - "type": "ui-template", - "z": "b8e6b9dc69aa3f56", - "group": "6039d653304537af", - "page": "", - "ui": "", - "name": "Calibration - Pump", - "order": 2, - "width": "0", - "height": "0", - "head": "", - "format": "\n\n\n\n", - "storeOutMessages": true, - "passthru": true, - "resendOnRefresh": true, - "templateScope": "local", - "className": "", - "x": 390, - "y": 140, - "wires": [ - [ - "bc5e30bc7ed09916" - ] - ] - }, - { - "id": "27489e08148cc627", - "type": "mqtt out", - "z": "b8e6b9dc69aa3f56", - "name": "", - "topic": "", - "qos": "", - "retain": "", - "broker": "8dc3722c.06efa8", - "x": 850, - "y": 100, - "wires": [] - }, - { - "id": "bc5e30bc7ed09916", - "type": "switch", - "z": "b8e6b9dc69aa3f56", - "name": "", - "property": "topic", - "propertyType": "msg", - "rules": [ - { - "t": "eq", - "v": "actuator/pump", - "vt": "str" - }, - { - "t": "eq", - "v": "calibration/save", - "vt": "str" - } - ], - "checkall": "true", - "repair": false, - "outputs": 2, - "x": 590, - "y": 140, - "wires": [ - [ - "27489e08148cc627" - ], - [ - "6f3a85a2de771d90" - ] - ] - }, - { - "id": "495701d383cde0e8", - "type": "mqtt in", - "z": "b8e6b9dc69aa3f56", - "name": "", - "topic": "status/pump", - "qos": "0", - "datatype": "json", - "broker": "8dc3722c.06efa8", - "nl": false, - "rap": false, - "inputs": 0, - "x": 90, - "y": 340, - "wires": [ - [ - "33f590e854486a56" - ] - ] - }, - { - "id": "6f3a85a2de771d90", - "type": "function", - "z": "b8e6b9dc69aa3f56", - "name": "set calibration_pump", - "func": "if (msg.topic) {\n global.set(\"calibration_nb_step\", msg.payload.calibration_nb_step);\n}\nreturn msg;\n", - "outputs": 1, - "timeout": "", - "noerr": 0, - "initialize": "", - "finalize": "", - "libs": [], - "x": 860, - "y": 200, - "wires": [ - [] - ] - }, - { - "id": "1c11c424e6c0c405", - "type": "debug", - "z": "b8e6b9dc69aa3f56", - "name": "debug 4", - "active": true, - "tosidebar": true, - "console": false, - "tostatus": false, - "complete": "false", - "statusVal": "", - "statusType": "auto", - "x": 360, - "y": 280, - "wires": [] - }, - { - "id": "aa4f580d98195efa", - "type": "ecotaxa", - "z": "f8d7305edb3dce2c", - "name": "Import to Ecotaxa Project", - "api_url": "https://ecotaxa.obs-vlfr.fr/api/", - "project_id": "9366", - "x": 830, - "y": 300, - "wires": [ - [ - "3f2db75b735b1cea" - ] - ] - }, - { - "id": "56563df41429a63b", - "type": "inject", - "z": "f8d7305edb3dce2c", - "name": "Lancer import", - "props": [], - "repeat": "", - "crontab": "", - "once": false, - "onceDelay": "0", - "topic": "", - "x": 310, - "y": 300, - "wires": [ - [ - "5deb75226b490f72" - ] - ] - }, - { - "id": "3f2db75b735b1cea", - "type": "debug", - "z": "f8d7305edb3dce2c", - "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": "5deb75226b490f72", - "type": "function", - "z": "f8d7305edb3dce2c", - "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": [ - [ - "aa4f580d98195efa", - "8f63d2d9ba29a929" - ] - ] - }, - { - "id": "8f63d2d9ba29a929", - "type": "debug", - "z": "f8d7305edb3dce2c", - "name": "debug 8", - "active": true, - "tosidebar": true, - "console": false, - "tostatus": false, - "complete": "false", - "statusVal": "", - "statusType": "auto", - "x": 580, - "y": 560, - "wires": [] - }, - { - "id": "655adc853a6956f6", - "type": "inject", - "z": "e6d820ba0f4f184e", - "g": "e25bae849bace7c6", - "name": "inject", - "props": [ - { - "p": "payload.timezone", - "v": "Europe/Paris", - "vt": "str" - } - ], - "repeat": "", - "crontab": "", - "once": false, - "onceDelay": 0.1, - "topic": "", - "x": 110, - "y": 280, - "wires": [ - [ - "c63068f9aa421f2c" - ] - ] - }, - { - "id": "fec9c2aca5437f85", - "type": "set timezone", - "z": "e6d820ba0f4f184e", - "g": "e25bae849bace7c6", - "name": "", - "x": 340, - "y": 380, - "wires": [ - [ - "5f77688682c97241" - ] - ] - }, - { - "id": "c63068f9aa421f2c", - "type": "list timezones", - "z": "e6d820ba0f4f184e", - "g": "e25bae849bace7c6", - "name": "", - "x": 140, - "y": 360, - "wires": [ - [ - "fec9c2aca5437f85" - ] - ] - }, - { - "id": "5f77688682c97241", - "type": "get timezone", - "z": "e6d820ba0f4f184e", - "g": "e25bae849bace7c6", - "name": "", - "x": 540, - "y": 400, - "wires": [ - [] - ] - }, - { - "id": "9975b01cde700ae1", - "type": "inject", - "z": "e6d820ba0f4f184e", - "g": "14cb9ab4b149e2b5", - "name": "inject", - "props": [ - { - "p": "payload.country", - "v": "FR", - "vt": "str" - } - ], - "repeat": "", - "crontab": "", - "once": false, - "onceDelay": 0.1, - "topic": "", - "x": 110, - "y": 60, - "wires": [ - [ - "b72d81f258ab6ed1" - ] - ] - }, - { - "id": "b72d81f258ab6ed1", - "type": "list countries", - "z": "e6d820ba0f4f184e", - "g": "14cb9ab4b149e2b5", - "name": "", - "x": 140, - "y": 140, - "wires": [ - [ - "8773c66a58ecab21" - ] - ] - }, - { - "id": "8773c66a58ecab21", - "type": "set country", - "z": "e6d820ba0f4f184e", - "g": "14cb9ab4b149e2b5", - "name": "", - "x": 330, - "y": 160, - "wires": [ - [ - "fe5a0dc78ca52e26" - ] - ] - }, - { - "id": "fe5a0dc78ca52e26", - "type": "get country", - "z": "e6d820ba0f4f184e", - "g": "14cb9ab4b149e2b5", - "name": "", - "x": 510, - "y": 180, - "wires": [ - [] - ] - }, - { - "id": "ec2b56583e1703f0", - "type": "inject", - "z": "e6d820ba0f4f184e", - "g": "e5f0bc40b541e13f", - "name": "inject", - "props": [], - "repeat": "", - "crontab": "", - "once": false, - "onceDelay": 0.1, - "topic": "", - "x": 110, - "y": 700, - "wires": [ - [ - "078727b344f7e34a" - ] - ] - }, - { - "id": "078727b344f7e34a", - "type": "get hostname", - "z": "e6d820ba0f4f184e", - "g": "e5f0bc40b541e13f", - "name": "", - "x": 140, - "y": 760, - "wires": [ - [] - ] - }, - { - "id": "355d4990f8958d65", - "type": "get name", - "z": "e6d820ba0f4f184e", - "g": "329d175baf2183b1", - "name": "", - "x": 130, - "y": 920, - "wires": [ - [] - ] - }, - { - "id": "3058d95dae6eea1f", - "type": "inject", - "z": "e6d820ba0f4f184e", - "g": "329d175baf2183b1", - "name": "", - "props": [], - "repeat": "", - "crontab": "", - "once": false, - "onceDelay": 0.1, - "topic": "", - "x": 110, - "y": 860, - "wires": [ - [ - "355d4990f8958d65" - ] - ] - }, - { - "id": "528f6ce911af8982", - "type": "storage info", - "z": "e6d820ba0f4f184e", - "g": "62930a749d014d90", - "name": "", - "x": 530, - "y": 920, - "wires": [ - [] - ] - }, - { - "id": "baac40d3e7a8fcb2", - "type": "inject", - "z": "e6d820ba0f4f184e", - "g": "62930a749d014d90", - "name": "inject", - "props": [], - "repeat": "", - "crontab": "", - "once": false, - "onceDelay": 0.1, - "topic": "", - "x": 510, - "y": 860, - "wires": [ - [ - "528f6ce911af8982" - ] - ] - }, - { - "id": "4f9158d3ed8eadd3", - "type": "list hardware versions", - "z": "e6d820ba0f4f184e", - "g": "034aca701dd2a256", - "name": "", - "x": 170, - "y": 560, - "wires": [ - [ - "0820acd0d3b628d4" - ] - ] - }, - { - "id": "287f7112bac18c51", - "type": "get hardware version", - "z": "e6d820ba0f4f184e", - "g": "034aca701dd2a256", - "name": "", - "x": 660, - "y": 600, - "wires": [ - [] - ] - }, - { - "id": "0820acd0d3b628d4", - "type": "set hardware version", - "z": "e6d820ba0f4f184e", - "g": "034aca701dd2a256", - "name": "", - "x": 420, - "y": 580, - "wires": [ - [ - "287f7112bac18c51" - ] - ] - }, - { - "id": "327e3da2b7e9e50b", - "type": "inject", - "z": "e6d820ba0f4f184e", - "g": "034aca701dd2a256", - "name": "inject", - "props": [ - { - "p": "payload.hardware_version", - "v": "v3.0", - "vt": "str" - } - ], - "repeat": "", - "crontab": "", - "once": false, - "onceDelay": 0.1, - "topic": "", - "x": 110, - "y": 500, - "wires": [ - [ - "4f9158d3ed8eadd3" - ] - ] - }, - { - "id": "80b749ee1cbdf98e", - "type": "comment", - "z": "e6d820ba0f4f184e", - "g": "14cb9ab4b149e2b5", - "name": "country", - "info": "", - "x": 690, - "y": 60, - "wires": [] - }, - { - "id": "e49db28bb805280e", - "type": "comment", - "z": "e6d820ba0f4f184e", - "g": "e25bae849bace7c6", - "name": "timezone", - "info": "", - "x": 740, - "y": 280, - "wires": [] - }, - { - "id": "c80721a372673baf", - "type": "comment", - "z": "e6d820ba0f4f184e", - "g": "034aca701dd2a256", - "name": "hardware version", - "info": "", - "x": 840, - "y": 500, - "wires": [] - }, - { - "id": "3e333d969e41b545", - "type": "comment", - "z": "e6d820ba0f4f184e", - "g": "e5f0bc40b541e13f", - "name": "hostname", - "info": "", - "x": 300, - "y": 700, - "wires": [] - }, - { - "id": "a6c33b2025837058", - "type": "comment", - "z": "e6d820ba0f4f184e", - "g": "329d175baf2183b1", - "name": "name", - "info": "", - "x": 310, - "y": 860, - "wires": [] - }, - { - "id": "660f8462d674baea", - "type": "comment", - "z": "e6d820ba0f4f184e", - "g": "62930a749d014d90", - "name": "storage", - "info": "", - "x": 690, - "y": 860, - "wires": [] - }, - { - "id": "f941634f9acf1e65", - "type": "poweroff", - "z": "e6d820ba0f4f184e", - "g": "039d9046b10d6840", - "name": "", - "x": 1180, - "y": 140, - "wires": [ - [] - ] - }, - { - "id": "8d538b74ac03964b", - "type": "reboot", - "z": "e6d820ba0f4f184e", - "g": "039d9046b10d6840", - "name": "", - "x": 1180, - "y": 200, - "wires": [ - [] - ] - }, - { - "id": "3cb007c279dceed0", - "type": "inject", - "z": "e6d820ba0f4f184e", - "g": "039d9046b10d6840", - "name": "inject", - "props": [], - "repeat": "", - "crontab": "", - "once": false, - "onceDelay": 0.1, - "topic": "", - "x": 1030, - "y": 140, - "wires": [ - [ - "f941634f9acf1e65" - ] - ] - }, - { - "id": "05d4bd56d2b42da1", - "type": "inject", - "z": "e6d820ba0f4f184e", - "g": "039d9046b10d6840", - "name": "inject", - "props": [], - "repeat": "", - "crontab": "", - "once": false, - "onceDelay": 0.1, - "topic": "", - "x": 1030, - "y": 200, - "wires": [ - [ - "8d538b74ac03964b" - ] - ] - }, - { - "id": "8972c5887d7de2dc", - "type": "comment", - "z": "e6d820ba0f4f184e", - "g": "039d9046b10d6840", - "name": "system \u26a0\ufe0f", - "info": "", - "x": 1200, - "y": 80, - "wires": [] - }, - { - "id": "13bdc8d028ab9063", - "type": "wake up", - "z": "e6d820ba0f4f184e", - "g": "039d9046b10d6840", - "name": "", - "minutes": "2", - "x": 1180, - "y": 260, - "wires": [ - [] - ] - }, - { - "id": "8283323ac1aca81a", - "type": "inject", - "z": "e6d820ba0f4f184e", - "g": "039d9046b10d6840", - "name": "inject", - "props": [], - "repeat": "", - "crontab": "", - "once": false, - "onceDelay": 0.1, - "topic": "", - "x": 1030, - "y": 260, - "wires": [ - [ - "13bdc8d028ab9063" - ] - ] - }, - { - "id": "d25760aa70fd1524", - "type": "list acquisitions", - "z": "e6d820ba0f4f184e", - "g": "0a51b211ebbb9873", - "name": "", - "x": 1120, - "y": 600, - "wires": [ - [] - ] - }, - { - "id": "d5950445bf78c0c6", - "type": "inject", - "z": "e6d820ba0f4f184e", - "g": "0a51b211ebbb9873", - "name": "inject", - "props": [], - "repeat": "", - "crontab": "", - "once": false, - "onceDelay": 0.1, - "topic": "", - "x": 1090, - "y": 540, - "wires": [ - [ - "d25760aa70fd1524" - ] - ] - }, - { - "id": "36a787c8463c3fab", - "type": "comment", - "z": "e6d820ba0f4f184e", - "g": "0a51b211ebbb9873", - "name": "acquisitions", - "info": "", - "x": 1310, - "y": 540, - "wires": [] - }, - { - "id": "9203a825fc1c63e4", - "type": "inject", - "z": "e6d820ba0f4f184e", - "g": "ef4667eb73b18227", - "name": "inject", - "props": [], - "repeat": "", - "crontab": "", - "once": false, - "onceDelay": 0.1, - "topic": "", - "x": 1090, - "y": 700, - "wires": [ - [ - "9d2be08e52de0394" - ] - ] - }, - { - "id": "110801015a0ec067", - "type": "comment", - "z": "e6d820ba0f4f184e", - "g": "ef4667eb73b18227", - "name": "segmentations", - "info": "", - "x": 1300, - "y": 700, - "wires": [] - }, - { - "id": "9d2be08e52de0394", - "type": "list segmentations", - "z": "e6d820ba0f4f184e", - "g": "ef4667eb73b18227", - "name": "", - "x": 1130, - "y": 760, - "wires": [ - [] - ] - }, - { - "id": "3973105e8c349a38", - "type": "get machine info", - "z": "e6d820ba0f4f184e", - "g": "693296edc15d81c2", - "name": "", - "x": 150, - "y": 1080, - "wires": [ - [ - "d96066c662742119" - ] - ] - }, - { - "id": "b8a021de6b15a728", - "type": "inject", - "z": "e6d820ba0f4f184e", - "g": "693296edc15d81c2", - "name": "inject", - "props": [], - "repeat": "", - "crontab": "", - "once": false, - "onceDelay": 0.1, - "topic": "", - "x": 110, - "y": 1020, - "wires": [ - [ - "3973105e8c349a38" - ] - ] - }, - { - "id": "e2c7db0aef7f64ef", - "type": "comment", - "z": "e6d820ba0f4f184e", - "g": "693296edc15d81c2", - "name": "machine info", - "info": "", - "x": 290, - "y": 1020, - "wires": [] - }, - { - "id": "47dce78fa2e5973b", - "type": "inject", - "z": "e6d820ba0f4f184e", - "g": "68e954becfafbf7b", - "name": "", - "props": [], - "repeat": "", - "crontab": "", - "once": false, - "onceDelay": 0.1, - "topic": "", - "x": 1090, - "y": 860, - "wires": [ - [ - "9f0db647f3595ef9" - ] - ] - }, - { - "id": "9f0db647f3595ef9", - "type": "capture", - "z": "e6d820ba0f4f184e", - "g": "68e954becfafbf7b", - "name": "", - "x": 1100, - "y": 920, - "wires": [ - [] - ] - }, - { - "id": "e14a82906a749dc5", - "type": "comment", - "z": "e6d820ba0f4f184e", - "g": "68e954becfafbf7b", - "name": "camera", - "info": "", - "x": 1330, - "y": 860, - "wires": [] - }, - { - "id": "d96066c662742119", - "type": "debug", - "z": "e6d820ba0f4f184e", - "g": "693296edc15d81c2", - "name": "debug 10", - "active": true, - "tosidebar": true, - "console": false, - "tostatus": false, - "complete": "false", - "statusVal": "", - "statusType": "auto", - "x": 470, - "y": 1100, - "wires": [] - }, - { - "id": "df6aac39f636d1d9", - "type": "ui-template", - "z": "f90406ba2da5932f", - "group": "", - "page": "", - "ui": "e6ae26617c24c3ea", - "name": "Favicon", - "order": 0, - "width": 0, - "height": 0, - "head": "", - "format": "", - "storeOutMessages": true, - "passthru": true, - "resend": true, - "templateScope": "widget:ui", - "className": "", - "x": 200, - "y": 200, - "wires": [ - [] - ] - } -] \ No newline at end of file +]