├── .gitignore ├── flows_cred.json ├── settings.js ├── README.md ├── Dockerfile ├── package.json └── flows.json /.gitignore: -------------------------------------------------------------------------------- 1 | *.backup -------------------------------------------------------------------------------- /flows_cred.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /settings.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | uiPort: process.env.PORT || 1880, 3 | credentialSecret: process.env.NODE_RED_CREDENTIAL_SECRET, 4 | httpAdminRoot: false, 5 | ui: { path: "/" } 6 | } 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | photobooth-workshop 2 | =================== 3 | 4 | ### About 5 | 6 | This is your project's README.md file. It helps users understand what your 7 | project does, how to use it and anything else they may need to know. -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:lts as build 2 | 3 | RUN apt-get update \ 4 | && apt-get install -y build-essential 5 | 6 | WORKDIR /data 7 | 8 | COPY ./package.json /data/ 9 | 10 | RUN npm install 11 | 12 | COPY ./settings.js /data/ 13 | COPY ./flows.json /data/ 14 | COPY ./flows_cred.json /data/ 15 | 16 | ## Release image 17 | 18 | FROM node:lts-slim 19 | RUN apt-get update 20 | 21 | RUN mkdir -p /data 22 | 23 | COPY --from=build /data /data 24 | 25 | WORKDIR /data 26 | 27 | ENV PORT 1880 28 | ENV NODE_ENV=production 29 | ENV NODE_PATH=/data/node_modules 30 | EXPOSE 1880 31 | CMD ["npm", "start"] 32 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "photobooth-workshop", 3 | "description": "A Node-RED Project", 4 | "version": "0.0.1", 5 | "dependencies": { 6 | "node-red": "1.x", 7 | "node-red-contrib-tfjs-coco-ssd": "0.5.2", 8 | "node-red-dashboard": "2.23.5", 9 | "node-red-node-annotate-image": "0.1.0", 10 | "node-red-node-ui-table": "0.3.7", 11 | "node-red-node-ui-webcam": "0.2.1" 12 | }, 13 | "node-red": { 14 | "settings": { 15 | "flowFile": "flows.json", 16 | "credentialsFile": "flows_cred.json" 17 | } 18 | }, 19 | "scripts": { 20 | "start": "node --max-old-space-size=256 ./node_modules/node-red/red.js --userDir . --settings ./settings.js flows.json" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /flows.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "ddeb3b89.ad9748", 4 | "type": "tab", 5 | "label": "Flow 1", 6 | "disabled": false, 7 | "info": "" 8 | }, 9 | { 10 | "id": "95389422.0480c8", 11 | "type": "ui_base", 12 | "theme": { 13 | "name": "theme-light", 14 | "lightTheme": { 15 | "default": "#0094CE", 16 | "baseColor": "#0094CE", 17 | "baseFont": "-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif", 18 | "edited": true, 19 | "reset": false 20 | }, 21 | "darkTheme": { 22 | "default": "#097479", 23 | "baseColor": "#097479", 24 | "baseFont": "-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif", 25 | "edited": false 26 | }, 27 | "customTheme": { 28 | "name": "Untitled Theme 1", 29 | "default": "#4B7930", 30 | "baseColor": "#4B7930", 31 | "baseFont": "-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif" 32 | }, 33 | "themeState": { 34 | "base-color": { 35 | "default": "#0094CE", 36 | "value": "#0094CE", 37 | "edited": false 38 | }, 39 | "page-titlebar-backgroundColor": { 40 | "value": "#0094CE", 41 | "edited": false 42 | }, 43 | "page-backgroundColor": { 44 | "value": "#fafafa", 45 | "edited": false 46 | }, 47 | "page-sidebar-backgroundColor": { 48 | "value": "#ffffff", 49 | "edited": false 50 | }, 51 | "group-textColor": { 52 | "value": "#1bbfff", 53 | "edited": false 54 | }, 55 | "group-borderColor": { 56 | "value": "#ffffff", 57 | "edited": false 58 | }, 59 | "group-backgroundColor": { 60 | "value": "#ffffff", 61 | "edited": false 62 | }, 63 | "widget-textColor": { 64 | "value": "#111111", 65 | "edited": false 66 | }, 67 | "widget-backgroundColor": { 68 | "value": "#0094ce", 69 | "edited": false 70 | }, 71 | "widget-borderColor": { 72 | "value": "#ffffff", 73 | "edited": false 74 | }, 75 | "base-font": { 76 | "value": "-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif" 77 | } 78 | }, 79 | "angularTheme": { 80 | "primary": "indigo", 81 | "accents": "blue", 82 | "warn": "red", 83 | "background": "grey" 84 | } 85 | }, 86 | "site": { 87 | "name": "Node-RED Dashboard", 88 | "hideToolbar": "false", 89 | "allowSwipe": "false", 90 | "lockMenu": "false", 91 | "allowTempTheme": "true", 92 | "dateFormat": "DD/MM/YYYY", 93 | "sizes": { 94 | "sx": 48, 95 | "sy": 48, 96 | "gx": 6, 97 | "gy": 6, 98 | "cx": 6, 99 | "cy": 6, 100 | "px": 0, 101 | "py": 0 102 | } 103 | } 104 | }, 105 | { 106 | "id": "5967b7da.b44598", 107 | "type": "ui_tab", 108 | "name": "AI Photo Booth", 109 | "icon": "dashboard", 110 | "order": 1, 111 | "disabled": false, 112 | "hidden": false 113 | }, 114 | { 115 | "id": "5ecf1e5d.c16c4", 116 | "type": "ui_group", 117 | "name": "WebCam", 118 | "tab": "5967b7da.b44598", 119 | "order": 1, 120 | "disp": false, 121 | "width": "10", 122 | "collapse": false 123 | }, 124 | { 125 | "id": "cc44de93.d9496", 126 | "type": "ui_group", 127 | "name": "Objects", 128 | "tab": "5967b7da.b44598", 129 | "order": 2, 130 | "disp": false, 131 | "width": "6", 132 | "collapse": false 133 | }, 134 | { 135 | "id": "800f274b.6632d8", 136 | "type": "ui_webcam", 137 | "z": "ddeb3b89.ad9748", 138 | "name": "", 139 | "group": "5ecf1e5d.c16c4", 140 | "order": 1, 141 | "width": "10", 142 | "height": "7", 143 | "countdown": false, 144 | "autoStart": false, 145 | "hideCaptureButton": true, 146 | "showImage": 0, 147 | "format": "jpeg", 148 | "x": 400, 149 | "y": 100, 150 | "wires": [ 151 | [ 152 | "f0e86e5b.c2eb5", 153 | "deade4b6.926288", 154 | "2be0395e.1879b6" 155 | ] 156 | ] 157 | }, 158 | { 159 | "id": "f0e86e5b.c2eb5", 160 | "type": "debug", 161 | "z": "ddeb3b89.ad9748", 162 | "name": "", 163 | "active": false, 164 | "tosidebar": true, 165 | "console": false, 166 | "tostatus": false, 167 | "complete": "false", 168 | "statusVal": "", 169 | "statusType": "auto", 170 | "x": 590, 171 | "y": 60, 172 | "wires": [] 173 | }, 174 | { 175 | "id": "deade4b6.926288", 176 | "type": "change", 177 | "z": "ddeb3b89.ad9748", 178 | "name": "", 179 | "rules": [ 180 | { 181 | "t": "set", 182 | "p": "filename", 183 | "pt": "msg", 184 | "to": "'/tmp/webcam_'& $moment().format('YYYY-MM-DD-hhmmss') & '.jpeg'", 185 | "tot": "jsonata" 186 | } 187 | ], 188 | "action": "", 189 | "property": "", 190 | "from": "", 191 | "to": "", 192 | "reg": false, 193 | "x": 610, 194 | "y": 100, 195 | "wires": [ 196 | [ 197 | "d062ebca.fa31e8" 198 | ] 199 | ] 200 | }, 201 | { 202 | "id": "d062ebca.fa31e8", 203 | "type": "file", 204 | "z": "ddeb3b89.ad9748", 205 | "name": "", 206 | "filename": "", 207 | "appendNewline": true, 208 | "createDir": false, 209 | "overwriteFile": "false", 210 | "encoding": "none", 211 | "x": 770, 212 | "y": 100, 213 | "wires": [ 214 | [] 215 | ] 216 | }, 217 | { 218 | "id": "bc8cf8a5.78e498", 219 | "type": "ui_button", 220 | "z": "ddeb3b89.ad9748", 221 | "name": "", 222 | "group": "5ecf1e5d.c16c4", 223 | "order": 2, 224 | "width": "9", 225 | "height": "1", 226 | "passthru": false, 227 | "label": "Capture", 228 | "tooltip": "", 229 | "color": "", 230 | "bgcolor": "", 231 | "icon": "fa-camera fa-2x", 232 | "payload": "", 233 | "payloadType": "str", 234 | "topic": "", 235 | "x": 80, 236 | "y": 60, 237 | "wires": [ 238 | [ 239 | "1491b6df.c56d59" 240 | ] 241 | ] 242 | }, 243 | { 244 | "id": "1491b6df.c56d59", 245 | "type": "change", 246 | "z": "ddeb3b89.ad9748", 247 | "name": "msg.capture", 248 | "rules": [ 249 | { 250 | "t": "set", 251 | "p": "capture", 252 | "pt": "msg", 253 | "to": "true", 254 | "tot": "bool" 255 | } 256 | ], 257 | "action": "", 258 | "property": "", 259 | "from": "", 260 | "to": "", 261 | "reg": false, 262 | "x": 230, 263 | "y": 60, 264 | "wires": [ 265 | [ 266 | "800f274b.6632d8" 267 | ] 268 | ] 269 | }, 270 | { 271 | "id": "1bfbdc10.aade94", 272 | "type": "ui_button", 273 | "z": "ddeb3b89.ad9748", 274 | "name": "Clear", 275 | "group": "5ecf1e5d.c16c4", 276 | "order": 2, 277 | "width": "1", 278 | "height": "1", 279 | "passthru": false, 280 | "label": "", 281 | "tooltip": "", 282 | "color": "", 283 | "bgcolor": "", 284 | "icon": "fa-trash fa-2x", 285 | "payload": "\"\"", 286 | "payloadType": "json", 287 | "topic": "", 288 | "x": 70, 289 | "y": 100, 290 | "wires": [ 291 | [ 292 | "800f274b.6632d8" 293 | ] 294 | ] 295 | }, 296 | { 297 | "id": "8cd39b6c.1e3608", 298 | "type": "tensorflowCoco", 299 | "z": "ddeb3b89.ad9748", 300 | "name": "", 301 | "modelUrl": "http://localhost:1880/coco/model.json", 302 | "scoreThreshold": 0.5, 303 | "passthru": "false", 304 | "lineColour": "magenta", 305 | "x": 390, 306 | "y": 240, 307 | "wires": [ 308 | [ 309 | "75fbf592.1bde7c", 310 | "3e1bfffc.64667" 311 | ] 312 | ] 313 | }, 314 | { 315 | "id": "75fbf592.1bde7c", 316 | "type": "debug", 317 | "z": "ddeb3b89.ad9748", 318 | "name": "", 319 | "active": true, 320 | "tosidebar": true, 321 | "console": false, 322 | "tostatus": false, 323 | "complete": "false", 324 | "statusVal": "", 325 | "statusType": "auto", 326 | "x": 590, 327 | "y": 200, 328 | "wires": [] 329 | }, 330 | { 331 | "id": "2936e203.f3245e", 332 | "type": "ui_table", 333 | "z": "ddeb3b89.ad9748", 334 | "group": "cc44de93.d9496", 335 | "name": "", 336 | "order": 0, 337 | "width": "6", 338 | "height": "8", 339 | "columns": [ 340 | { 341 | "field": "class", 342 | "title": "Object Type", 343 | "width": "", 344 | "align": "left", 345 | "formatter": "plaintext", 346 | "formatterParams": { 347 | "target": "_blank" 348 | } 349 | }, 350 | { 351 | "field": "score", 352 | "title": "Score", 353 | "width": "", 354 | "align": "left", 355 | "formatter": "progress", 356 | "formatterParams": { 357 | "target": "_blank" 358 | } 359 | } 360 | ], 361 | "outputs": 1, 362 | "cts": true, 363 | "x": 390, 364 | "y": 360, 365 | "wires": [ 366 | [ 367 | "afc2661c.8a52a8", 368 | "f1fc41c3.1b5d8" 369 | ] 370 | ] 371 | }, 372 | { 373 | "id": "3e1bfffc.64667", 374 | "type": "change", 375 | "z": "ddeb3b89.ad9748", 376 | "name": "", 377 | "rules": [ 378 | { 379 | "t": "set", 380 | "p": "payload", 381 | "pt": "msg", 382 | "to": "$append([],payload.{\"class\":class,\"score\":score*100,\"bbox\":bbox})", 383 | "tot": "jsonata" 384 | } 385 | ], 386 | "action": "", 387 | "property": "", 388 | "from": "", 389 | "to": "", 390 | "reg": false, 391 | "x": 220, 392 | "y": 360, 393 | "wires": [ 394 | [ 395 | "2936e203.f3245e" 396 | ] 397 | ] 398 | }, 399 | { 400 | "id": "847c5db6.44f4e", 401 | "type": "link in", 402 | "z": "ddeb3b89.ad9748", 403 | "name": "to-webcam", 404 | "links": [ 405 | "23c88371.85c4bc", 406 | "70eca315.946a6c" 407 | ], 408 | "x": 275, 409 | "y": 140, 410 | "wires": [ 411 | [ 412 | "800f274b.6632d8" 413 | ] 414 | ] 415 | }, 416 | { 417 | "id": "afc2661c.8a52a8", 418 | "type": "debug", 419 | "z": "ddeb3b89.ad9748", 420 | "name": "", 421 | "active": true, 422 | "tosidebar": true, 423 | "console": false, 424 | "tostatus": false, 425 | "complete": "false", 426 | "statusVal": "", 427 | "statusType": "auto", 428 | "x": 590, 429 | "y": 360, 430 | "wires": [] 431 | }, 432 | { 433 | "id": "90d9565a.3b5ea8", 434 | "type": "annotate-image", 435 | "z": "ddeb3b89.ad9748", 436 | "name": "", 437 | "fill": "", 438 | "stroke": "#ffC000", 439 | "lineWidth": 5, 440 | "fontSize": 24, 441 | "fontColor": "#ffC000", 442 | "x": 600, 443 | "y": 420, 444 | "wires": [ 445 | [ 446 | "70eca315.946a6c" 447 | ] 448 | ] 449 | }, 450 | { 451 | "id": "70eca315.946a6c", 452 | "type": "link out", 453 | "z": "ddeb3b89.ad9748", 454 | "name": "annotated-image-output", 455 | "links": [ 456 | "847c5db6.44f4e" 457 | ], 458 | "x": 715, 459 | "y": 420, 460 | "wires": [] 461 | }, 462 | { 463 | "id": "2be0395e.1879b6", 464 | "type": "change", 465 | "z": "ddeb3b89.ad9748", 466 | "name": "", 467 | "rules": [ 468 | { 469 | "t": "set", 470 | "p": "image", 471 | "pt": "flow", 472 | "to": "payload", 473 | "tot": "msg" 474 | } 475 | ], 476 | "action": "", 477 | "property": "", 478 | "from": "", 479 | "to": "", 480 | "reg": false, 481 | "x": 200, 482 | "y": 240, 483 | "wires": [ 484 | [ 485 | "8cd39b6c.1e3608" 486 | ] 487 | ] 488 | }, 489 | { 490 | "id": "f1fc41c3.1b5d8", 491 | "type": "change", 492 | "z": "ddeb3b89.ad9748", 493 | "name": "", 494 | "rules": [ 495 | { 496 | "t": "set", 497 | "p": "annotations", 498 | "pt": "msg", 499 | "to": "$append([],payload.{\"label\": class, \"bbox\": bbox})", 500 | "tot": "jsonata" 501 | }, 502 | { 503 | "t": "set", 504 | "p": "payload", 505 | "pt": "msg", 506 | "to": "image", 507 | "tot": "flow" 508 | } 509 | ], 510 | "action": "", 511 | "property": "", 512 | "from": "", 513 | "to": "", 514 | "reg": false, 515 | "x": 420, 516 | "y": 420, 517 | "wires": [ 518 | [ 519 | "90d9565a.3b5ea8" 520 | ] 521 | ] 522 | } 523 | ] --------------------------------------------------------------------------------