{ "name": "v1.6 Test", "nodes": [ { "parameters": { "formTitle": "Form input data", "formFields": { "values": [ { "fieldLabel": "image", "fieldType": "file" } ] }, "options": {} }, "type": "n8n-nodes-base.formTrigger", "typeVersion": 2.5, "position": [ -1200, 160 ], "id": "bc08484e-ccbe-4ccf-b6dc-9f8bd59d1197", "name": "Form input data", "webhookId": "afa8943e-cab2-4554-b64d-2c888ecfd9c3" }, { "parameters": { "method": "POST", "url": "=http://{{ $json.LLaVA }}/llava-api/v1/get-object-types", "sendHeaders": true, "headerParameters": { "parameters": [ { "name": "accept", "value": "application/json" }, { "name": "Authorization", "value": "=Bearer {{ $json.Token }}" } ] }, "sendBody": true, "contentType": "multipart-form-data", "bodyParameters": { "parameters": [ { "name": "temperature", "value": "0.2" }, { "name": "top_p", "value": "0.9" }, { "parameterType": "formBinaryData", "name": "image", "inputDataFieldName": "=image" } ] }, "options": {} }, "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.3, "position": [ 64, -240 ], "id": "fa89e8ca-2669-48f2-bdcf-e4d2aeee1311", "name": "LLaVA-based object types extractor", "onError": "continueRegularOutput" }, { "parameters": { "method": "POST", "url": "=http://{{ $json.Grounding_DINO }}/grounding-dino-api/v1/get-objects-bboxes ", "sendHeaders": true, "headerParameters": { "parameters": [ { "name": "accept", "value": "application/json" }, { "name": "Authorization", "value": "=Bearer {{ $json.Token }}" } ] }, "sendBody": true, "contentType": "multipart-form-data", "bodyParameters": { "parameters": [ { "name": "object_types", "value": "={{ $json.labels_string }}" }, { "name": "max_objects", "value": "50" }, { "name": "base64", "value": "false" }, { "parameterType": "formBinaryData", "name": "image", "inputDataFieldName": "image" } ] }, "options": {} }, "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.3, "position": [ 800, -16 ], "id": "6cfd2993-30d9-4905-a643-48a73273c60c", "name": "Grounding DINO" }, { "parameters": { "operation": "binaryToPropery", "binaryPropertyName": "image", "destinationKey": "image_input", "options": {} }, "type": "n8n-nodes-base.extractFromFile", "typeVersion": 1.1, "position": [ -384, 560 ], "id": "eb0dc17a-cedb-4ea2-a28e-c9aac74f188e", "name": "base64" }, { "parameters": { "method": "POST", "url": "=http://{{ $json.DeepGaze }}/v1/predict-heatmap ", "sendHeaders": true, "headerParameters": { "parameters": [ { "name": "accept", "value": "application/json" }, { "name": "Authorization", "value": "=Bearer {{ $json.Token }}" } ] }, "sendBody": true, "contentType": "multipart-form-data", "bodyParameters": { "parameters": [ { "parameterType": "formBinaryData", "name": "image", "inputDataFieldName": "image" } ] }, "options": {} }, "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.3, "position": [ 96, 352 ], "id": "4e3419b0-dae9-4bba-8729-8798d4d5f289", "name": "DeepGaze", "onError": "continueRegularOutput" }, { "parameters": { "jsCode": "const item = $input.first().json;\n\nconst binaryData = Buffer.from(item.heatmap_b64, 'base64');\n\nreturn [{\n json: item,\n binary: {\n image: await this.helpers.prepareBinaryData(\n binaryData,\n 'heatmap.png',\n 'image/png'\n )\n }\n}];" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 1120, 352 ], "id": "456beac3-734b-4cc2-9006-3b671462b9ed", "name": "Image" }, { "parameters": {}, "type": "n8n-nodes-base.merge", "typeVersion": 3.2, "position": [ 1120, 576 ], "id": "e3ea6d0e-cc9e-491b-9a52-e83f733d59ed", "name": "Merge" }, { "parameters": { "content": "## Inverted heatmap - DeepGaze\nPrezentacja heatmap nałożonej na grafikę źródłową", "height": 112, "width": 464, "color": 4 }, "type": "n8n-nodes-base.stickyNote", "typeVersion": 1, "position": [ 1424, 720 ], "id": "d4207e63-5241-4b70-a01a-bb4d52e1641e", "name": "Sticky Note" }, { "parameters": {}, "type": "n8n-nodes-base.merge", "typeVersion": 3.2, "position": [ 1120, 144 ], "id": "b8dec319-2fa6-4655-afbb-87ab03a7d167", "name": "Merge1" }, { "parameters": { "content": "## Object Detection\nObiekty nałożone na grafikę źródłową", "height": 112, "width": 464, "color": 4 }, "type": "n8n-nodes-base.stickyNote", "typeVersion": 1, "position": [ 1424, 288 ], "id": "02ee310f-191b-4e74-b562-1ee1b030560e", "name": "Sticky Note1" }, { "parameters": { "content": "## Object Listing\nLista obiektów wraz z opisem grafiki", "height": 112, "width": 464, "color": 4 }, "type": "n8n-nodes-base.stickyNote", "typeVersion": 1, "position": [ -96, -384 ], "id": "426c4aca-e358-4aeb-8de1-182eaff75a44", "name": "Sticky Note2" }, { "parameters": { "mode": "combine", "combineBy": "combineAll", "options": {} }, "type": "n8n-nodes-base.merge", "typeVersion": 3.2, "position": [ -384, -16 ], "id": "7cd356f2-501a-418a-b809-67f520433273", "name": "Merge2" }, { "parameters": { "operation": "get", "dataTableId": { "__rl": true, "value": "vNV6H2oqepIaXaDj", "mode": "list", "cachedResultName": "Credentials", "cachedResultUrl": "/projects/OZaIb3cfHKXhndDW/datatables/vNV6H2oqepIaXaDj" }, "limit": 1 }, "type": "n8n-nodes-base.dataTable", "typeVersion": 1.1, "position": [ -848, -32 ], "id": "7c2ee8f0-8eab-4c80-bca8-818e5335feff", "name": "Credentials" }, { "parameters": { "mode": "combine", "combineBy": "combineAll", "options": {} }, "type": "n8n-nodes-base.merge", "typeVersion": 3.2, "position": [ 560, -16 ], "id": "9298bcb8-d2f6-4d42-8e03-13b556495cab", "name": "Merge5" }, { "parameters": { "content": "## Heatmap\n", "height": 112, "width": 464 }, "type": "n8n-nodes-base.stickyNote", "typeVersion": 1, "position": [ 1424, 1632 ], "id": "786650ff-5fd8-4a83-9ad1-9fb22041789c", "name": "Sticky Note3" }, { "parameters": { "content": "## Interwały\n30 sek, 6 obrazów\n", "height": 112, "width": 464 }, "type": "n8n-nodes-base.stickyNote", "typeVersion": 1, "position": [ 1904, 720 ], "id": "f5e0761f-b4df-4b27-910d-d7cecf3110c2", "name": "Sticky Note4" }, { "parameters": { "content": "## Interwały\n30 sek, 6 obrazów\n20 sek. do 0,5 sekundy", "height": 112, "width": 464 }, "type": "n8n-nodes-base.stickyNote", "typeVersion": 1, "position": [ 1904, 1632 ], "id": "05d80ebd-4a8d-435f-b125-96d61162b1e1", "name": "Sticky Note5" }, { "parameters": { "content": "## Ścieżki fiksacji\n", "height": 112, "width": 464 }, "type": "n8n-nodes-base.stickyNote", "typeVersion": 1, "position": [ 1424, 1920 ], "id": "8671e093-3278-4d7d-b028-7066a634aa10", "name": "Sticky Note6" }, { "parameters": { "content": "## Interwały\n???\n", "height": 112, "width": 464 }, "type": "n8n-nodes-base.stickyNote", "typeVersion": 1, "position": [ 1904, 1920 ], "id": "3d9a385e-377a-494d-be51-57c02eb6bbc6", "name": "Sticky Note7" }, { "parameters": { "content": "## Mapa emocji\n", "height": 112, "width": 464 }, "type": "n8n-nodes-base.stickyNote", "typeVersion": 1, "position": [ 1424, 2176 ], "id": "be6a9c40-a7d9-489a-8452-b1dd79efaf01", "name": "Sticky Note8" }, { "parameters": { "jsCode": "const labels = $input.first().json.labels;\nreturn [{ json: { labels_string: labels.join(\", \") } }];" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 256, -240 ], "id": "040cb049-68ca-46ca-b46f-06ecce243387", "name": "Labels string" }, { "parameters": { "jsCode": "const items = $input.all();\n\nconst originalB64 = items[0].json.image_input;\nconst heatmapB64 = items[1].json.heatmap_b64;\nconst heatmapMime = items[1].json.heatmap_mime;\nconst width = items[1].json.width;\nconst height = items[1].json.height;\n\nconst html = `\n\n\n\n\nHeatmap Overlay Viewer\n\n\n\n\n

Heatmap overlay viewer

\n\n
\n \"Original\"\n \"Heatmap\"\n
\n\n
\n \n \n 50%\n
\n\n

Przesuń suwak w lewo, aby zobaczyć oryginał · w prawo, aby zobaczyć heatmapę

\n\n\n\n`;\n\nconst binaryData = await this.helpers.prepareBinaryData(\n Buffer.from(html, 'utf-8'),\n 'heatmap_viewer.html',\n 'text/html'\n);\n\nreturn [{\n json: {\n filename: 'heatmap_viewer.html',\n width,\n height,\n model: items[1].json.model\n },\n binary: { file: binaryData }\n}];" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 1424, 576 ], "id": "fbc15b23-9640-48da-b79f-33328a1c92dd", "name": "Inverted heatmap" }, { "parameters": { "method": "POST", "url": "http://1.208.108.242:58951/unisal/process-image", "sendQuery": true, "queryParameters": { "parameters": [ { "name": "as_base64", "value": "true" } ] }, "sendHeaders": true, "headerParameters": { "parameters": [ { "name": "accept", "value": "application/json" }, { "name": "X-API-Key", "value": "={{ $json.Token }}" } ] }, "sendBody": true, "contentType": "multipart-form-data", "bodyParameters": { "parameters": [ { "parameterType": "formBinaryData", "name": "file", "inputDataFieldName": "image" } ] }, "options": {} }, "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.4, "position": [ 96, 832 ], "id": "edd412d8-cbc2-48ab-8df7-2ef047ee9f5e", "name": "unisal - inverted heatmap", "onError": "continueRegularOutput" }, { "parameters": { "method": "POST", "url": "http://1.208.108.242:58951/asmModel/process-image", "sendQuery": true, "queryParameters": { "parameters": [ { "name": "prompt", "value": "Describe the image in the style of a polished marketing ad." }, { "name": "as_raw_output", "value": "false" }, { "name": "max_new_tokens", "value": "100" } ] }, "sendHeaders": true, "headerParameters": { "parameters": [ { "name": "accept", "value": "application/json" }, { "name": "X-API-Key", "value": "={{ $json.Token }}" } ] }, "sendBody": true, "contentType": "multipart-form-data", "bodyParameters": { "parameters": [ { "parameterType": "formBinaryData", "name": "file", "inputDataFieldName": "image" } ] }, "options": {} }, "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.4, "position": [ 96, 1040 ], "id": "91aabc80-b814-4c31-9796-e9acdad65192", "name": "asmModel - inverted heatmap", "onError": "continueRegularOutput" }, { "parameters": { "numberInputs": 4 }, "type": "n8n-nodes-base.merge", "typeVersion": 3.2, "position": [ 1120, 944 ], "id": "2abef155-3c6f-4c64-9aaa-a5bc12ef2c43", "name": "Merge6" }, { "parameters": { "content": "## Inverted heatmap - Compare\nPrezentacja heatmap nałożonej na grafikę źródłową", "height": 112, "width": 464, "color": 4 }, "type": "n8n-nodes-base.stickyNote", "typeVersion": 1, "position": [ 1424, 1120 ], "id": "b3a754d2-c3ff-4dfd-a8a0-71c8a37d3bb5", "name": "Sticky Note9" }, { "parameters": { "jsCode": "// n8n Code Node – Generate Inverted Heatmap HTML (Canvas pixel-blend)\n// Input items (4):\n// [0] image_input – original image (base64 PNG)\n// [1] heatmap_b64 – DeepGaze heatmap (base64 PNG), heatmap_mime\n// [2] data – unisal heatmap (base64 JPEG), mimetype\n// [3] image_detailed – asmModel detailed (base64), image_general – asmModel general, mimetype\n\nconst imgInput = items[0].json.image_input;\nconst deepGazeB64 = items[1].json.heatmap_b64;\nconst deepGazeMime = items[1].json.heatmap_mime || 'image/png';\nconst unisalB64 = items[2].json.data;\nconst unisalMime = items[2].json.mimetype || 'image/jpeg';\nconst detailedB64 = items[3].json.image_detailed;\nconst generalB64 = items[3].json.image_general;\nconst asmMime = items[3].json.mimetype || 'image/jpeg';\n\nconst origMime = imgInput.startsWith('/9j/') ? 'image/jpeg' : 'image/png';\n\nconst html = `\n\n\n\n\nInverted Heatmap\n\n\n\n

Inverted Heatmap

\n\n
\n \n \n 70%\n
\n
Ładowanie obrazów…
\n\n
\n
\n
DeepGaze
\n \n
\n
\n
unisal
\n \n
\n
\n
asmModel detailed
\n \n
\n
\n
asmModel general
\n \n
\n
\n\n\n\n`;\n\n// Zwróć jako plik binarny do pobrania z n8n\nconst binaryData = await this.helpers.prepareBinaryData(\n Buffer.from(html, 'utf8'),\n 'inverted_heatmap.html',\n 'text/html'\n);\n\nreturn [{ json: {}, binary: { data: binaryData } }];\n" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 1424, 976 ], "id": "fe201da2-822e-4433-973d-d14d3e72421e", "name": "Inverted heatmap - Compare" }, { "parameters": { "jsCode": "const items = $input.all();\n\nconst imageB64 = items[0].json.image_input;\nconst detections = items[1].json.objects_bboxes || [];\n\n// --- Odczyt rzeczywistych wymiarów obrazu z nagłówka binarnego (base64) ---\n// Eliminuje zależność od pól width/height, których może nie być w danych\nconst imgBuffer = Buffer.from(imageB64, 'base64');\n\nlet imgWidth, imgHeight, mimeType;\n\nif (\n imgBuffer[0] === 0x89 && imgBuffer[1] === 0x50 &&\n imgBuffer[2] === 0x4E && imgBuffer[3] === 0x47\n) {\n // PNG — wymiary w bajtach 16-23 (big-endian uint32)\n mimeType = 'image/png';\n imgWidth = imgBuffer.readUInt32BE(16);\n imgHeight = imgBuffer.readUInt32BE(20);\n} else if (imgBuffer[0] === 0xFF && imgBuffer[1] === 0xD8) {\n // JPEG — szukamy markera SOF0 (0xC0) lub SOF2 (0xC2)\n mimeType = 'image/jpeg';\n let offset = 2;\n let found = false;\n while (offset + 3 < imgBuffer.length) {\n if (imgBuffer[offset] !== 0xFF) break;\n const marker = imgBuffer[offset + 1];\n if (marker === 0xC0 || marker === 0xC2) {\n imgHeight = imgBuffer.readUInt16BE(offset + 5);\n imgWidth = imgBuffer.readUInt16BE(offset + 7);\n found = true;\n break;\n }\n // Przejdź do następnego markera\n const segLen = imgBuffer.readUInt16BE(offset + 2);\n offset += 2 + segLen;\n }\n if (!found) {\n imgWidth = items[1].json.width || 839;\n imgHeight = items[1].json.height || 1190;\n }\n} else {\n // Nieznany format — fallback na dane z JSON lub wartości domyślne\n mimeType = 'image/png';\n imgWidth = items[1].json.width || 839;\n imgHeight = items[1].json.height || 1190;\n}\n\n// --- Dynamiczna paleta kolorów ---\nconst palette = [\n '#00e5ff', '#ff4081', '#ffea00', '#76ff03', '#d500f9',\n '#ff6d00', '#00bfa5', '#304ffe', '#ff1744', '#64ffda',\n '#f50057', '#00b0ff', '#ffc400', '#69f0ae', '#e040fb',\n '#ff9100', '#18ffff', '#7c4dff', '#ff3d00', '#b2ff59',\n];\n\nconst uniqueTypes = [...new Set(detections.map(d => d.object_type))];\nconst colors = {};\nuniqueTypes.forEach((type, i) => {\n colors[type] = palette[i % palette.length];\n});\n\n// --- Generowanie boksów z pozycjonowaniem procentowym ---\n// Kluczowa poprawka: używamy % zamiast px, dzięki czemu boxy\n// skalują się razem z obrazem przy każdej rozdzielczości ekranu.\nconst boxesHtml = detections.map(det => {\n const [x1, y1, x2, y2] = det.bbox_coords_xyxy;\n const color = colors[det.object_type];\n const score = (det.score * 100).toFixed(1);\n\n const left = ((x1 / imgWidth) * 100).toFixed(4);\n const top = ((y1 / imgHeight) * 100).toFixed(4);\n const width = (((x2 - x1) / imgWidth) * 100).toFixed(4);\n const height = (((y2 - y1) / imgHeight)* 100).toFixed(4);\n\n return `\n
\n ${det.object_type} ${score}%\n
`;\n}).join('\\n');\n\n// --- Legenda i filtry ---\nconst legend = uniqueTypes.map(type =>\n `${type}`\n).join('');\n\nconst filters = uniqueTypes.map(type => {\n const count = detections.filter(d => d.object_type === type).length;\n return ``;\n}).join('\\n');\n\nconst html = `\n\n\n\n\nObject Detection Viewer\n\n\n\n\n

🎯 Object Detection — ${detections.length} obiektów

\n\n
\n Filtruj:\n ${filters}\n \n Legenda:\n ${legend}\n
\n\n
\n \"Source\"\n ${boxesHtml}\n
\n\n
\n Model: ${items[1].json.model_id || '—'} ·\n Threshold: ${items[1].json.threshold ?? '—'} ·\n Rozdzielczość: ${imgWidth}×${imgHeight} ·\n Typy: ${uniqueTypes.join(', ')}\n
\n\n\n\n`;\n\nconst binaryData = await this.helpers.prepareBinaryData(\n Buffer.from(html, 'utf-8'),\n 'detection_viewer.html',\n 'text/html'\n);\n\nreturn [{\n json: {\n filename: 'detection_viewer.html',\n total_objects: detections.length,\n types: uniqueTypes,\n model: items[1].json.model_id,\n image_width: imgWidth,\n image_height: imgHeight,\n mime_type: mimeType,\n },\n binary: { file: binaryData }\n}];\n" }, "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 1424, 144 ], "id": "fa626aa0-64a4-4818-83b3-ac1f88228694", "name": "Object Detection" } ], "pinData": {}, "connections": { "Form input data": { "main": [ [ { "node": "base64", "type": "main", "index": 0 }, { "node": "Merge2", "type": "main", "index": 1 }, { "node": "Credentials", "type": "main", "index": 0 } ] ] }, "DeepGaze": { "main": [ [ { "node": "Image", "type": "main", "index": 0 }, { "node": "Merge", "type": "main", "index": 1 }, { "node": "Merge6", "type": "main", "index": 1 } ] ] }, "Image": { "main": [ [] ] }, "Merge": { "main": [ [ { "node": "Inverted heatmap", "type": "main", "index": 0 } ] ] }, "base64": { "main": [ [ { "node": "Merge", "type": "main", "index": 0 }, { "node": "Merge1", "type": "main", "index": 0 }, { "node": "Merge6", "type": "main", "index": 0 } ] ] }, "Grounding DINO": { "main": [ [ { "node": "Merge1", "type": "main", "index": 1 } ] ] }, "Merge1": { "main": [ [ { "node": "Object Detection", "type": "main", "index": 0 } ] ] }, "Merge2": { "main": [ [ { "node": "LLaVA-based object types extractor", "type": "main", "index": 0 }, { "node": "unisal - inverted heatmap", "type": "main", "index": 0 }, { "node": "Merge5", "type": "main", "index": 1 }, { "node": "DeepGaze", "type": "main", "index": 0 }, { "node": "asmModel - inverted heatmap", "type": "main", "index": 0 } ] ] }, "Credentials": { "main": [ [ { "node": "Merge2", "type": "main", "index": 0 } ] ] }, "LLaVA-based object types extractor": { "main": [ [ { "node": "Labels string", "type": "main", "index": 0 } ] ] }, "Merge5": { "main": [ [ { "node": "Grounding DINO", "type": "main", "index": 0 } ] ] }, "Labels string": { "main": [ [ { "node": "Merge5", "type": "main", "index": 0 } ] ] }, "Inverted heatmap": { "main": [ [] ] }, "unisal - inverted heatmap": { "main": [ [ { "node": "Merge6", "type": "main", "index": 2 } ] ] }, "asmModel - inverted heatmap": { "main": [ [ { "node": "Merge6", "type": "main", "index": 3 } ] ] }, "Merge6": { "main": [ [ { "node": "Inverted heatmap - Compare", "type": "main", "index": 0 } ] ] } }, "active": false, "settings": { "executionOrder": "v1", "binaryMode": "separate", "availableInMCP": false }, "versionId": "5e97360b-e09b-43a9-962f-91090d292f8c", "meta": { "instanceId": "1e4ac0fc2b33692d40da602263ad5f150100624f047385c3b326b30a4678b299" }, "id": "ByacxQXqRD7pGkWW", "tags": [] }