diff --git a/README.md b/README.md index c7f3a2d..31a7349 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ A set of ComfyUI nodes to dynamically create QR image layers for generative QR art without the hassle of leaving the webui. -Everything in this pack is for the creation of patterns. If you would like to evaluate rendered patterns for scannability, check out the companion project [Comfy QR Validation Nodes](https://gitlab.com/sofuego-comfy-nodes/comfy-qr-validation-nodes). Any workflow in the example that ends with "validated" assume the installation of the validation pack as well. +Everything in this pack is primarily focused on the creation of patterns. While the `Mask QR Errors` does some basic statistics on the differences bwtween pixels, a more definative node on determining scannability can be found in the companion project [Comfy QR Validation Nodes](https://gitlab.com/sofuego-comfy-nodes/comfy-qr-validation-nodes) which uses additional dependencies for the reading of QRs. This repository is managed publicly on [Gitlab](https://gitlab.com/sofuego-comfy-nodes/comfy-qr), but also mirrored on [Github](https://github.com/coreyryanhanson/comfy-qr). Please submit any [issues](https://gitlab.com/sofuego-comfy-nodes/comfy-qr/-/issues) or [pull requests](https://gitlab.com/sofuego-comfy-nodes/comfy-qr/-/merge_requests) to the gitlab repo. @@ -12,9 +12,11 @@ This repository is managed publicly on [Gitlab](https://gitlab.com/sofuego-comfy This project currently contains two nodes for generating QR codes. -### QR Code +### QR Code and QR Code (Split) -A dynamically sized QR code without any resampling scaled by the text length, the module size, and the error correction level. +A dynamically sized QR code without any resampling scaled by the text length, the module size, and the error correction level. Use the `QR Code `for simple workflows and the `QR Code (Split)` if you want to build more advanced pipelines with additional outputs for the `MODULE_LAYER`, `FINDER_LAYER`, or `FINDER_MASK`. + +![Split QR Code Node Example](img/node-qr-code-split.png) #### Inputs @@ -36,12 +38,17 @@ A dynamically sized QR code without any resampling scaled by the text length, th #### Outputs -* `IMAGE` - The QR code +* `QR_CODE` - The QR code +* `MODULE_LAYER` - The QR code with the `back_hexcolor` filling in the function patterns (to remove their influence from the early diffusion steps). +* `FINDER_LAYER` - The isolated finder patterns over the `back_hexcolor`. +* `FINDER_MASK` - A mask that isolates the locations of the 3 finder patterns. * `QR_VERSION` - An integer of the QR version that was selected based on the text length and error correction level. * `IMAGE_SIZE` - An integer based on the pixel width of the resulting QR code. ### QR Code (Conformed to Image Size) +![Conformed QR Code Node Example](img/node-qr-code-conformed.png) + A QR code fixed to specific output dimensions through image resampling. #### Inputs @@ -70,39 +77,55 @@ A QR code fixed to specific output dimensions through image resampling. #### Outputs -* `IMAGE` - The QR code +* `QR_CODE` - The QR code * `QR_VERSION` - An integer of the QR version that was selected based on the text length and error correction level. -### QR Code (Split) +### Mask QR Errors -A version of the `QR Code` node that also outputs split layers for the finder patterns along with an accompanying mask. +A node that that will analyze the differences between a modified QR and create a mask of the estimated errors. + +![Mask QR Errors Node Example](img/node-mask-qr-errors.png) + +**Note**: _This is an extremly powerful node but relies on several assumptions in order to be used._ +1. A QR with a fixed module size that has not been resampled irregularly or distorted. +2. Shared allignment between the `source_qr` and `modified_qr`. +3. Fill and back colors must be perfectly white and black (`#FFFFFF`, `#000000`) for the `source_qr`. +4. The canvas can be bigger than the QR code, but on the `source_qr`, there should be no pixels the same color as the module color outside of the QR exterior. #### Inputs -* `protocol` - If enabled this will prefix the textbox input with a preset to represent the internet protocol. This is included both for convenience and as a workaround for the textbox clipping strings with this character combination. - * `Http` - Adds "http://" before the text. - * `Https` - Adds "https://" before the text. - * `None` - Uses only the contents of the `text` box. -* `text` - What text to build your QR code with. If `protocol` is specified, this textbox will be combined it with the selected option. -* `module_size` - The pixel width of the smallest unit of a QR code. -* `max_image_size` - The maximum size of the resulting QR code. If the combination of `text`, `module_size`, and `error_correction` create dimensions that exceed this, an error will halt the pipeline. -* `fill_hexcolor` - A string of characters representing the hex value of the QR units. Can be 3 or 6 hexadecimal characters with an optional # before. -* `back_hexcolor` - A string of characters representing the hex value of the space around the QR units. Can be 3 or 6 hexadecimal characters with an optional # before. -* `error_correction` - The level of error correction to apply. - * `Low` - 7% error correction. - * `Medium` - 15% error correction. - * `Quartile` - 25% error correction. - * `High` - 30% error correction. -* `border` - The border size (In multiples of `module` widths) +* `source_qr` - The original QR code used to generate the image (with the same placement and dimensions as `modified_qr`). +* `modified_qr` - The generated QR art to evaluate (with the same placement and dimensions as `source_qr`). +* `module_size` - The size in pixels of each QR module (the individual blocks that make up a qr code) in the provided images. +* `grayscale_method` - How to convert the RGB color channels to a single grayscale value. + * `mean` - A simple average of the RGB values. +* `aggregate_method` - How to aggregate the many pixels that make up a QR module into a single value. + * `mean` - Within each module a simple average is taken of all constituent pixels. +* `evaluate` - Which part of the QR to examine. + * `full_qr` - The entire QR code is evaluated. + * `module_pattern` - Has the node focus only on the modules while excluding the finder pattern. + * `finder_pattern` - Ignores the modules while examining only the 3 finder patterns at the corners of the QR. +* `error_difficulty` - It is possible that while using the same reader a QR code could be easily scannable on one monitor, but not on another. The way the colors are displayed can have a major effect on unsability. By increasing this threshold, you are simulating inability to distinguish midrange pixels for both the `PERCENT_ERROR` and `QR_ERROR_MASK` outputs. It ranges from 0 to 1 where 0 only automatically fails perfect grays while 1 accepts only exactly matched modules that are perfectly black or white. +* `inverted_pattern` - If set to `True` this assumes that the QR code has inverted colors (a black background with white modules.) #### Outputs -* `QR_FLATTENED` - The QR code -* `MODULE_LAYER` - The QR code with the `back_hexcolor` filling in the function patterns (to remove their influence from the early diffusion steps). -* `FINDER_LAYER` - The isolated finder patterns over the `back_hexcolor`. -* `FINDER_MASK` - A mask that isolates the locations of the 3 finder patterns. -* `QR_VERSION` - An integer of the QR version that was selected based on the text length and error correction level. -* `IMAGE_SIZE` - An integer based on the pixel width of the resulting QR code. +* `QR_ERROR_MASK` - A mask with white areas falling on locations of modules with errors (precision set by `error_difficulty`). This mask can be used for streamlined img2img operations to salvage unscannable QRs. +* `PERCENT_ERROR` - Number of modules that do not fall into the acceptable threshold (set by `error_difficulty`) divided by the total number of modules. +* `CORRELATION` - The Pearson correlation coefficient between the QR patterns on the `source_qr` and `modified_qr` +* `RMSE` - The root mean squared error between both QR codes. In this case, a value of 0 indicates an identical match to the `source_qr`. A value of 1 indicates a perfect inversion where each expected color is the complete opposite. + +## Examples + +There is an assortment of workflow examples that can be found in the `examples` directory or in the metadata of the images in the `example_generations` folder. For example taking this image. + +![Bad Scan Example](example_generations/unscannable_00001_.png) + +And then aggressively increasing the strength of the ControlNet on only the error pixels to make something previously completely unscannable more reliably across different monitors and QR readers. + +![Fixed Scan Example](example_generations/unscannable_00001_fixed_.png) + +Any workflow in the example that ends with "validated" (and a few image examples) assume the installation of the [validation pack](https://gitlab.com/sofuego-comfy-nodes/comfy-qr-validation-nodes) as well. ## Future updates diff --git a/example_generations/main_qr_raw_output_00001_.png b/example_generations/main_qr_raw_output_00001_.png new file mode 100644 index 0000000..53fb4c6 Binary files /dev/null and b/example_generations/main_qr_raw_output_00001_.png differ diff --git a/example_generations/unscannable_00001_.png b/example_generations/unscannable_00001_.png new file mode 100644 index 0000000..1a18a43 Binary files /dev/null and b/example_generations/unscannable_00001_.png differ diff --git a/example_generations/unscannable_00001_fixed_.png b/example_generations/unscannable_00001_fixed_.png new file mode 100644 index 0000000..c6cd7b5 Binary files /dev/null and b/example_generations/unscannable_00001_fixed_.png differ diff --git a/examples/img2img-on-errors-validated.json b/examples/img2img-on-errors-validated.json new file mode 100644 index 0000000..b0df502 --- /dev/null +++ b/examples/img2img-on-errors-validated.json @@ -0,0 +1,2260 @@ +{ + "last_node_id": 50, + "last_link_id": 110, + "nodes": [ + { + "id": 12, + "type": "ControlNetLoader", + "pos": [ + -692.0780999999998, + 618.3673000000019 + ], + "size": { + "0": 315, + "1": 58 + }, + "flags": {}, + "order": 0, + "mode": 0, + "outputs": [ + { + "name": "CONTROL_NET", + "type": "CONTROL_NET", + "links": [ + 13 + ], + "shape": 3, + "slot_index": 0 + } + ], + "properties": { + "Node name for S&R": "ControlNetLoader" + }, + "widgets_values": [ + "control_v1p_sd15_qrcode_monster_v2.safetensors" + ] + }, + { + "id": 6, + "type": "ImageCompositeMasked", + "pos": [ + -332.0780999999991, + -91.63270000000001 + ], + "size": [ + 315, + 146 + ], + "flags": {}, + "order": 18, + "mode": 0, + "inputs": [ + { + "name": "destination", + "type": "IMAGE", + "link": 3 + }, + { + "name": "source", + "type": "IMAGE", + "link": 2 + }, + { + "name": "mask", + "type": "MASK", + "link": null + }, + { + "name": "x", + "type": "INT", + "link": 107, + "widget": { + "name": "x", + "config": [ + "INT", + { + "default": 0, + "min": 0, + "max": 8192, + "step": 1 + } + ] + } + }, + { + "name": "y", + "type": "INT", + "link": 108, + "widget": { + "name": "y", + "config": [ + "INT", + { + "default": 0, + "min": 0, + "max": 8192, + "step": 1 + } + ] + } + } + ], + "outputs": [ + { + "name": "IMAGE", + "type": "IMAGE", + "links": [ + 4, + 12, + 25 + ], + "shape": 3, + "slot_index": 0 + } + ], + "properties": { + "Node name for S&R": "ImageCompositeMasked" + }, + "widgets_values": [ + 16, + 16, + false + ] + }, + { + "id": 7, + "type": "PreviewImage", + "pos": [ + 877.9219000000076, + 18.367300000000753 + ], + "size": { + "0": 210, + "1": 246 + }, + "flags": {}, + "order": 25, + "mode": 0, + "inputs": [ + { + "name": "images", + "type": "IMAGE", + "link": 7 + } + ], + "properties": { + "Node name for S&R": "PreviewImage" + } + }, + { + "id": 19, + "type": "PreviewImage", + "pos": [ + 1937.9218999999898, + 158.36729999999957 + ], + "size": { + "0": 210, + "1": 246 + }, + "flags": {}, + "order": 32, + "mode": 0, + "inputs": [ + { + "name": "images", + "type": "IMAGE", + "link": 27 + } + ], + "properties": { + "Node name for S&R": "PreviewImage" + } + }, + { + "id": 20, + "type": "MaskToImage", + "pos": [ + 1637.9218999999982, + 148.3672999999998 + ], + "size": { + "0": 210, + "1": 26 + }, + "flags": {}, + "order": 30, + "mode": 0, + "inputs": [ + { + "name": "mask", + "type": "MASK", + "link": 26 + } + ], + "outputs": [ + { + "name": "IMAGE", + "type": "IMAGE", + "links": [ + 27 + ], + "shape": 3, + "slot_index": 0 + } + ], + "properties": { + "Node name for S&R": "MaskToImage" + } + }, + { + "id": 10, + "type": "CLIPTextEncode", + "pos": [ + -242.0780999999985, + 498.3673000000002 + ], + "size": { + "0": 400, + "1": 200 + }, + "flags": {}, + "order": 10, + "mode": 0, + "inputs": [ + { + "name": "clip", + "type": "CLIP", + "link": 8 + } + ], + "outputs": [ + { + "name": "CONDITIONING", + "type": "CONDITIONING", + "links": [ + 10 + ], + "shape": 3, + "slot_index": 0 + } + ], + "title": "Positive Prompt", + "properties": { + "Node name for S&R": "CLIPTextEncode" + }, + "widgets_values": [ + "An evil robot inside a factory destroys paperwork by firing a laser beam with his eyes, futuristic, colorful" + ] + }, + { + "id": 11, + "type": "CLIPTextEncode", + "pos": [ + -242.0780999999985, + 768.3673000000036 + ], + "size": { + "0": 400, + "1": 200 + }, + "flags": {}, + "order": 11, + "mode": 0, + "inputs": [ + { + "name": "clip", + "type": "CLIP", + "link": 9 + } + ], + "outputs": [ + { + "name": "CONDITIONING", + "type": "CONDITIONING", + "links": [ + 11 + ], + "shape": 3, + "slot_index": 0 + } + ], + "title": "Negative Prompt", + "properties": { + "Node name for S&R": "CLIPTextEncode" + }, + "widgets_values": [ + "worst quality" + ] + }, + { + "id": 9, + "type": "CheckpointLoaderSimple", + "pos": [ + -692.0780999999998, + 748.3673000000032 + ], + "size": { + "0": 315, + "1": 98 + }, + "flags": {}, + "order": 1, + "mode": 0, + "outputs": [ + { + "name": "MODEL", + "type": "MODEL", + "links": [ + 16 + ], + "shape": 3, + "slot_index": 0 + }, + { + "name": "CLIP", + "type": "CLIP", + "links": [ + 8, + 9 + ], + "shape": 3, + "slot_index": 1 + }, + { + "name": "VAE", + "type": "VAE", + "links": [ + 18, + 33 + ], + "shape": 3, + "slot_index": 2 + } + ], + "properties": { + "Node name for S&R": "CheckpointLoaderSimple" + }, + "widgets_values": [ + "1.5/Deliberate-inpainting.safetensors" + ] + }, + { + "id": 25, + "type": "PrimitiveNode", + "pos": [ + 1037.9219000000098, + 578.3673000000001 + ], + "size": { + "0": 210, + "1": 82 + }, + "flags": {}, + "order": 2, + "mode": 0, + "outputs": [ + { + "name": "INT", + "type": "INT", + "links": [ + 38, + 39, + 40, + 41 + ], + "slot_index": 0, + "widget": { + "name": "left", + "config": [ + "INT", + { + "default": 0, + "min": 0, + "max": 8192, + "step": 8 + } + ] + } + } + ], + "title": "Pad", + "properties": {}, + "widgets_values": [ + 40, + "fixed" + ] + }, + { + "id": 28, + "type": "PrimitiveNode", + "pos": [ + -783.0781000000013, + -277.6327000000003 + ], + "size": { + "0": 210, + "1": 82 + }, + "flags": {}, + "order": 3, + "mode": 0, + "outputs": [ + { + "name": "COMBO", + "type": "Http,Https,None", + "links": [ + 46, + 48 + ], + "slot_index": 0, + "widget": { + "name": "protocol", + "config": [ + [ + "Http", + "Https", + "None" + ], + { + "default": "Https" + } + ] + } + } + ], + "title": "Protocol", + "properties": {}, + "widgets_values": [ + "Https", + "fixed" + ] + }, + { + "id": 29, + "type": "PrimitiveNode", + "pos": [ + -535.078099999998, + -266.6327000000004 + ], + "size": { + "0": 210, + "1": 76 + }, + "flags": {}, + "order": 4, + "mode": 0, + "outputs": [ + { + "name": "STRING", + "type": "STRING", + "links": [ + 47, + 85 + ], + "slot_index": 0, + "widget": { + "name": "text", + "config": [ + "STRING", + { + "multiline": true + } + ] + } + } + ], + "title": "URL", + "properties": {}, + "widgets_values": [ + "gitlab.com/sofuego-comfy-nodes/comfy-qr-validation-nodes" + ] + }, + { + "id": 22, + "type": "VAEEncode", + "pos": [ + 208, + 231 + ], + "size": { + "0": 210, + "1": 46 + }, + "flags": {}, + "order": 13, + "mode": 0, + "inputs": [ + { + "name": "pixels", + "type": "IMAGE", + "link": 87 + }, + { + "name": "vae", + "type": "VAE", + "link": 33 + } + ], + "outputs": [ + { + "name": "LATENT", + "type": "LATENT", + "links": [ + 31 + ], + "shape": 3, + "slot_index": 0 + } + ], + "properties": { + "Node name for S&R": "VAEEncode" + } + }, + { + "id": 14, + "type": "KSampler", + "pos": [ + 647.9219000000045, + 458.3673000000005 + ], + "size": [ + 315, + 474.00000762939453 + ], + "flags": {}, + "order": 26, + "mode": 0, + "inputs": [ + { + "name": "model", + "type": "MODEL", + "link": 16 + }, + { + "name": "positive", + "type": "CONDITIONING", + "link": 35 + }, + { + "name": "negative", + "type": "CONDITIONING", + "link": 36 + }, + { + "name": "latent_image", + "type": "LATENT", + "link": 32 + } + ], + "outputs": [ + { + "name": "LATENT", + "type": "LATENT", + "links": [ + 17 + ], + "shape": 3, + "slot_index": 0 + } + ], + "properties": { + "Node name for S&R": "KSampler" + }, + "widgets_values": [ + 944021096056392, + "randomize", + 25, + 8, + "euler_ancestral", + "normal", + 0.9600000000000002 + ] + }, + { + "id": 8, + "type": "MaskToImage", + "pos": [ + 558, + -42 + ], + "size": { + "0": 210, + "1": 26 + }, + "flags": {}, + "order": 23, + "mode": 0, + "inputs": [ + { + "name": "mask", + "type": "MASK", + "link": 92 + } + ], + "outputs": [ + { + "name": "IMAGE", + "type": "IMAGE", + "links": [ + 7 + ], + "shape": 3, + "slot_index": 0 + } + ], + "properties": { + "Node name for S&R": "MaskToImage" + } + }, + { + "id": 4, + "type": "SolidMask", + "pos": [ + -970, + 294 + ], + "size": [ + 315, + 106 + ], + "flags": {}, + "order": 14, + "mode": 0, + "inputs": [ + { + "name": "width", + "type": "INT", + "link": 103, + "widget": { + "name": "width", + "config": [ + "INT", + { + "default": 512, + "min": 1, + "max": 8192, + "step": 1 + } + ] + } + }, + { + "name": "height", + "type": "INT", + "link": 104, + "widget": { + "name": "height", + "config": [ + "INT", + { + "default": 512, + "min": 1, + "max": 8192, + "step": 1 + } + ] + } + } + ], + "outputs": [ + { + "name": "MASK", + "type": "MASK", + "links": [ + 1 + ], + "shape": 3, + "slot_index": 0 + } + ], + "title": "Mask (Gray)", + "properties": { + "Node name for S&R": "SolidMask" + }, + "widgets_values": [ + 0.5, + 768, + 768 + ] + }, + { + "id": 1, + "type": "comfy-qr-by-module-split", + "pos": [ + -842.0781000000012, + -131.63269999999977 + ], + "size": { + "0": 400, + "1": 344 + }, + "flags": {}, + "order": 12, + "mode": 0, + "inputs": [ + { + "name": "protocol", + "type": "Http,Https,None", + "link": 46, + "widget": { + "name": "protocol", + "config": [ + [ + "Http", + "Https", + "None" + ], + { + "default": "Https" + } + ] + } + }, + { + "name": "text", + "type": "STRING", + "link": 47, + "widget": { + "name": "text", + "config": [ + "STRING", + { + "multiline": true + } + ] + } + } + ], + "outputs": [ + { + "name": "QR_FLATTENED", + "type": "IMAGE", + "links": [ + 2 + ], + "shape": 3, + "slot_index": 0 + }, + { + "name": "MODULE_LAYER", + "type": "IMAGE", + "links": [], + "shape": 3, + "slot_index": 1 + }, + { + "name": "FUNCTION_LAYER", + "type": "IMAGE", + "links": [], + "shape": 3, + "slot_index": 2 + }, + { + "name": "FUNCTION_MASK", + "type": "MASK", + "links": [ + 89 + ], + "shape": 3, + "slot_index": 3 + }, + { + "name": "QR_VERSION", + "type": "INT", + "links": null, + "shape": 3 + }, + { + "name": "IMAGE_SIZE", + "type": "INT", + "links": null, + "shape": 3 + } + ], + "properties": { + "Node name for S&R": "comfy-qr-by-module-split" + }, + "widgets_values": [ + "Https", + "gitlab.com/sofuego-comfy-nodes/comfy-qr-validation-nodes", + 16, + 768, + "#000000", + "#FFFFFF", + "High", + 1 + ] + }, + { + "id": 21, + "type": "SetLatentNoiseMask", + "pos": [ + 551, + 238 + ], + "size": { + "0": 210, + "1": 46 + }, + "flags": {}, + "order": 24, + "mode": 0, + "inputs": [ + { + "name": "samples", + "type": "LATENT", + "link": 31 + }, + { + "name": "mask", + "type": "MASK", + "link": 93 + } + ], + "outputs": [ + { + "name": "LATENT", + "type": "LATENT", + "links": [ + 32 + ], + "shape": 3, + "slot_index": 0 + } + ], + "properties": { + "Node name for S&R": "SetLatentNoiseMask" + } + }, + { + "id": 17, + "type": "VAEDecode", + "pos": [ + 1039, + 464 + ], + "size": { + "0": 210, + "1": 46 + }, + "flags": {}, + "order": 27, + "mode": 0, + "inputs": [ + { + "name": "samples", + "type": "LATENT", + "link": 17 + }, + { + "name": "vae", + "type": "VAE", + "link": 18 + } + ], + "outputs": [ + { + "name": "IMAGE", + "type": "IMAGE", + "links": [ + 24, + 37, + 44 + ], + "shape": 3, + "slot_index": 0 + } + ], + "properties": { + "Node name for S&R": "VAEDecode" + } + }, + { + "id": 24, + "type": "ImagePadForOutpaint", + "pos": [ + 1011, + 779 + ], + "size": { + "0": 315, + "1": 174 + }, + "flags": {}, + "order": 29, + "mode": 0, + "inputs": [ + { + "name": "image", + "type": "IMAGE", + "link": 37 + }, + { + "name": "left", + "type": "INT", + "link": 38, + "widget": { + "name": "left", + "config": [ + "INT", + { + "default": 0, + "min": 0, + "max": 8192, + "step": 8 + } + ] + } + }, + { + "name": "top", + "type": "INT", + "link": 39, + "widget": { + "name": "top", + "config": [ + "INT", + { + "default": 0, + "min": 0, + "max": 8192, + "step": 8 + } + ] + } + }, + { + "name": "right", + "type": "INT", + "link": 40, + "widget": { + "name": "right", + "config": [ + "INT", + { + "default": 0, + "min": 0, + "max": 8192, + "step": 8 + } + ] + } + }, + { + "name": "bottom", + "type": "INT", + "link": 41, + "widget": { + "name": "bottom", + "config": [ + "INT", + { + "default": 0, + "min": 0, + "max": 8192, + "step": 8 + } + ] + } + } + ], + "outputs": [ + { + "name": "IMAGE", + "type": "IMAGE", + "links": [ + 42 + ], + "shape": 3, + "slot_index": 0 + }, + { + "name": "MASK", + "type": "MASK", + "links": null, + "shape": 3 + } + ], + "properties": { + "Node name for S&R": "ImagePadForOutpaint" + }, + "widgets_values": [ + 40, + 40, + 40, + 40, + 0 + ] + }, + { + "id": 26, + "type": "comfy-qr-read", + "pos": [ + 1404, + 841 + ], + "size": { + "0": 315, + "1": 58 + }, + "flags": {}, + "order": 31, + "mode": 0, + "inputs": [ + { + "name": "image", + "type": "IMAGE", + "link": 42 + } + ], + "outputs": [ + { + "name": "EXTRACTED_TEXT", + "type": "STRING", + "links": [ + 45 + ], + "shape": 3, + "slot_index": 0 + } + ], + "properties": { + "Node name for S&R": "comfy-qr-read" + }, + "widgets_values": [ + "pyzbar" + ] + }, + { + "id": 13, + "type": "ControlNetApplyAdvanced", + "pos": [ + 247.9219000000045, + 508.36730000000017 + ], + "size": { + "0": 315, + "1": 166 + }, + "flags": {}, + "order": 21, + "mode": 0, + "inputs": [ + { + "name": "positive", + "type": "CONDITIONING", + "link": 10 + }, + { + "name": "negative", + "type": "CONDITIONING", + "link": 11 + }, + { + "name": "control_net", + "type": "CONTROL_NET", + "link": 13 + }, + { + "name": "image", + "type": "IMAGE", + "link": 12 + } + ], + "outputs": [ + { + "name": "positive", + "type": "CONDITIONING", + "links": [ + 35 + ], + "shape": 3, + "slot_index": 0 + }, + { + "name": "negative", + "type": "CONDITIONING", + "links": [ + 36 + ], + "shape": 3, + "slot_index": 1 + } + ], + "properties": { + "Node name for S&R": "ControlNetApplyAdvanced" + }, + "widgets_values": [ + 5, + 0, + 1 + ] + }, + { + "id": 39, + "type": "MaskComposite", + "pos": [ + 483, + 50 + ], + "size": { + "0": 315, + "1": 126 + }, + "flags": {}, + "order": 22, + "mode": 0, + "inputs": [ + { + "name": "destination", + "type": "MASK", + "link": 91 + }, + { + "name": "source", + "type": "MASK", + "link": 102 + } + ], + "outputs": [ + { + "name": "MASK", + "type": "MASK", + "links": [ + 92, + 93 + ], + "shape": 3, + "slot_index": 0 + } + ], + "properties": { + "Node name for S&R": "MaskComposite" + }, + "widgets_values": [ + 0, + 0, + "add" + ] + }, + { + "id": 5, + "type": "MaskToImage", + "pos": [ + -592, + 305 + ], + "size": { + "0": 210, + "1": 26 + }, + "flags": {}, + "order": 16, + "mode": 0, + "inputs": [ + { + "name": "mask", + "type": "MASK", + "link": 1 + } + ], + "outputs": [ + { + "name": "IMAGE", + "type": "IMAGE", + "links": [ + 3 + ], + "shape": 3, + "slot_index": 0 + } + ], + "properties": { + "Node name for S&R": "MaskToImage" + } + }, + { + "id": 18, + "type": "comfy-qr-quantify-match", + "pos": [ + 1253, + 129 + ], + "size": { + "0": 317.4000244140625, + "1": 238 + }, + "flags": {}, + "order": 28, + "mode": 0, + "inputs": [ + { + "name": "source_qr", + "type": "IMAGE", + "link": 25 + }, + { + "name": "modified_qr", + "type": "IMAGE", + "link": 24 + } + ], + "outputs": [ + { + "name": "PERCENT_ERROR", + "type": "FLOAT", + "links": null, + "shape": 3 + }, + { + "name": "CORRELATION", + "type": "FLOAT", + "links": null, + "shape": 3 + }, + { + "name": "RMSE", + "type": "FLOAT", + "links": null, + "shape": 3 + }, + { + "name": "QR_ERROR_MASK", + "type": "MASK", + "links": [ + 26 + ], + "shape": 3, + "slot_index": 3 + } + ], + "properties": { + "Node name for S&R": "comfy-qr-quantify-match" + }, + "widgets_values": [ + 16, + "mean", + "mean", + "module_pattern", + 0, + "False" + ] + }, + { + "id": 3, + "type": "comfy-qr-quantify-match", + "pos": [ + 97.9219000000007, + -111.63269999999997 + ], + "size": { + "0": 317.4000244140625, + "1": 238 + }, + "flags": {}, + "order": 20, + "mode": 0, + "inputs": [ + { + "name": "source_qr", + "type": "IMAGE", + "link": 4 + }, + { + "name": "modified_qr", + "type": "IMAGE", + "link": 5 + } + ], + "outputs": [ + { + "name": "PERCENT_ERROR", + "type": "FLOAT", + "links": null, + "shape": 3 + }, + { + "name": "CORRELATION", + "type": "FLOAT", + "links": null, + "shape": 3 + }, + { + "name": "RMSE", + "type": "FLOAT", + "links": null, + "shape": 3 + }, + { + "name": "QR_ERROR_MASK", + "type": "MASK", + "links": [ + 91 + ], + "shape": 3, + "slot_index": 3 + } + ], + "properties": { + "Node name for S&R": "comfy-qr-quantify-match" + }, + "widgets_values": [ + 16, + "mean", + "mean", + "module_pattern", + 0.5, + "False" + ] + }, + { + "id": 2, + "type": "LoadImage", + "pos": [ + -288, + 115 + ], + "size": { + "0": 315, + "1": 314.0000305175781 + }, + "flags": {}, + "order": 5, + "mode": 0, + "outputs": [ + { + "name": "IMAGE", + "type": "IMAGE", + "links": [ + 5, + 87 + ], + "shape": 3, + "slot_index": 0 + }, + { + "name": "MASK", + "type": "MASK", + "links": null, + "shape": 3 + } + ], + "properties": { + "Node name for S&R": "LoadImage" + }, + "widgets_values": [ + "unscannable_00001_ (1).png", + "image" + ] + }, + { + "id": 27, + "type": "comfy-qr-validate", + "pos": [ + 1357, + 494 + ], + "size": { + "0": 400, + "1": 200 + }, + "flags": {}, + "order": 33, + "mode": 0, + "inputs": [ + { + "name": "image", + "type": "IMAGE", + "link": 44 + }, + { + "name": "extracted_text", + "type": "STRING", + "link": 45 + }, + { + "name": "protocol", + "type": "Http,Https,None", + "link": 48, + "widget": { + "name": "protocol", + "config": [ + [ + "Http", + "Https", + "None" + ], + { + "default": "Https" + } + ] + } + }, + { + "name": "text", + "type": "STRING", + "link": 85, + "widget": { + "name": "text", + "config": [ + "STRING", + { + "multiline": true + } + ] + } + } + ], + "outputs": [ + { + "name": "IMAGE", + "type": "IMAGE", + "links": [ + 43 + ], + "shape": 3, + "slot_index": 0 + }, + { + "name": "VALIDATION_CODE", + "type": "INT", + "links": null, + "shape": 3 + } + ], + "properties": { + "Node name for S&R": "comfy-qr-validate" + }, + "widgets_values": [ + "Https", + "gitlab.com/sofuego-comfy-nodes/comfy-qr-validation-nodes", + "False" + ] + }, + { + "id": 23, + "type": "SaveImage", + "pos": [ + 1841, + 506 + ], + "size": { + "0": 315, + "1": 270 + }, + "flags": {}, + "order": 34, + "mode": 0, + "inputs": [ + { + "name": "images", + "type": "IMAGE", + "link": 43 + } + ], + "properties": {}, + "widgets_values": [ + "ComfyUI" + ] + }, + { + "id": 47, + "type": "PrimitiveNode", + "pos": [ + -1252, + 425 + ], + "size": { + "0": 210, + "1": 82 + }, + "flags": {}, + "order": 6, + "mode": 0, + "outputs": [ + { + "name": "INT", + "type": "INT", + "links": [ + 104, + 106 + ], + "slot_index": 0, + "widget": { + "name": "height", + "config": [ + "INT", + { + "default": 512, + "min": 1, + "max": 8192, + "step": 1 + } + ] + } + } + ], + "title": "Height", + "properties": {}, + "widgets_values": [ + 768, + "fixed" + ] + }, + { + "id": 46, + "type": "PrimitiveNode", + "pos": [ + -1250, + 278 + ], + "size": { + "0": 210, + "1": 82 + }, + "flags": {}, + "order": 7, + "mode": 0, + "outputs": [ + { + "name": "INT", + "type": "INT", + "links": [ + 103, + 105 + ], + "slot_index": 0, + "widget": { + "name": "width", + "config": [ + "INT", + { + "default": 512, + "min": 1, + "max": 8192, + "step": 1 + } + ] + } + } + ], + "title": "Width", + "properties": {}, + "widgets_values": [ + 768, + "fixed" + ] + }, + { + "id": 49, + "type": "PrimitiveNode", + "pos": [ + -1149, + -41 + ], + "size": { + "0": 210, + "1": 82 + }, + "flags": {}, + "order": 8, + "mode": 0, + "outputs": [ + { + "name": "INT", + "type": "INT", + "links": [ + 107, + 109 + ], + "slot_index": 0, + "widget": { + "name": "x", + "config": [ + "INT", + { + "default": 0, + "min": 0, + "max": 8192, + "step": 1 + } + ] + } + } + ], + "title": "X Offset", + "properties": {}, + "widgets_values": [ + 16, + "fixed" + ] + }, + { + "id": 50, + "type": "PrimitiveNode", + "pos": [ + -1146, + 94 + ], + "size": { + "0": 210, + "1": 82 + }, + "flags": {}, + "order": 9, + "mode": 0, + "outputs": [ + { + "name": "INT", + "type": "INT", + "links": [ + 108, + 110 + ], + "slot_index": 0, + "widget": { + "name": "y", + "config": [ + "INT", + { + "default": 0, + "min": 0, + "max": 8192, + "step": 1 + } + ] + } + } + ], + "title": "Y Offset", + "properties": {}, + "widgets_values": [ + 16, + "fixed" + ] + }, + { + "id": 37, + "type": "SolidMask", + "pos": [ + -994, + 455 + ], + "size": [ + 315, + 106 + ], + "flags": {}, + "order": 15, + "mode": 0, + "inputs": [ + { + "name": "width", + "type": "INT", + "link": 105, + "widget": { + "name": "width", + "config": [ + "INT", + { + "default": 512, + "min": 1, + "max": 8192, + "step": 1 + } + ] + } + }, + { + "name": "height", + "type": "INT", + "link": 106, + "widget": { + "name": "height", + "config": [ + "INT", + { + "default": 512, + "min": 1, + "max": 8192, + "step": 1 + } + ] + } + } + ], + "outputs": [ + { + "name": "MASK", + "type": "MASK", + "links": [ + 88 + ], + "shape": 3, + "slot_index": 0 + } + ], + "title": "Mask (Black)", + "properties": { + "Node name for S&R": "SolidMask" + }, + "widgets_values": [ + 0, + 768, + 768 + ] + }, + { + "id": 38, + "type": "MaskComposite", + "pos": [ + -635, + 413 + ], + "size": [ + 315, + 126 + ], + "flags": {}, + "order": 17, + "mode": 0, + "inputs": [ + { + "name": "destination", + "type": "MASK", + "link": 88 + }, + { + "name": "source", + "type": "MASK", + "link": 89 + }, + { + "name": "x", + "type": "INT", + "link": 109, + "widget": { + "name": "x", + "config": [ + "INT", + { + "default": 0, + "min": 0, + "max": 8192, + "step": 1 + } + ] + } + }, + { + "name": "y", + "type": "INT", + "link": 110, + "widget": { + "name": "y", + "config": [ + "INT", + { + "default": 0, + "min": 0, + "max": 8192, + "step": 1 + } + ] + } + } + ], + "outputs": [ + { + "name": "MASK", + "type": "MASK", + "links": [ + 101 + ], + "shape": 3, + "slot_index": 0 + } + ], + "title": "Function Mask Expanded", + "properties": { + "Node name for S&R": "MaskComposite" + }, + "widgets_values": [ + 16, + 16, + "add" + ] + }, + { + "id": 45, + "type": "GrowMask", + "pos": [ + 150, + 353 + ], + "size": { + "0": 315, + "1": 82 + }, + "flags": {}, + "order": 19, + "mode": 0, + "inputs": [ + { + "name": "mask", + "type": "MASK", + "link": 101 + } + ], + "outputs": [ + { + "name": "MASK", + "type": "MASK", + "links": [ + 102 + ], + "shape": 3, + "slot_index": 0 + } + ], + "properties": { + "Node name for S&R": "GrowMask" + }, + "widgets_values": [ + 16, + false + ] + } + ], + "links": [ + [ + 1, + 4, + 0, + 5, + 0, + "MASK" + ], + [ + 2, + 1, + 0, + 6, + 1, + "IMAGE" + ], + [ + 3, + 5, + 0, + 6, + 0, + "IMAGE" + ], + [ + 4, + 6, + 0, + 3, + 0, + "IMAGE" + ], + [ + 5, + 2, + 0, + 3, + 1, + "IMAGE" + ], + [ + 7, + 8, + 0, + 7, + 0, + "IMAGE" + ], + [ + 8, + 9, + 1, + 10, + 0, + "CLIP" + ], + [ + 9, + 9, + 1, + 11, + 0, + "CLIP" + ], + [ + 10, + 10, + 0, + 13, + 0, + "CONDITIONING" + ], + [ + 11, + 11, + 0, + 13, + 1, + "CONDITIONING" + ], + [ + 12, + 6, + 0, + 13, + 3, + "IMAGE" + ], + [ + 13, + 12, + 0, + 13, + 2, + "CONTROL_NET" + ], + [ + 16, + 9, + 0, + 14, + 0, + "MODEL" + ], + [ + 17, + 14, + 0, + 17, + 0, + "LATENT" + ], + [ + 18, + 9, + 2, + 17, + 1, + "VAE" + ], + [ + 24, + 17, + 0, + 18, + 1, + "IMAGE" + ], + [ + 25, + 6, + 0, + 18, + 0, + "IMAGE" + ], + [ + 26, + 18, + 3, + 20, + 0, + "MASK" + ], + [ + 27, + 20, + 0, + 19, + 0, + "IMAGE" + ], + [ + 31, + 22, + 0, + 21, + 0, + "LATENT" + ], + [ + 32, + 21, + 0, + 14, + 3, + "LATENT" + ], + [ + 33, + 9, + 2, + 22, + 1, + "VAE" + ], + [ + 35, + 13, + 0, + 14, + 1, + "CONDITIONING" + ], + [ + 36, + 13, + 1, + 14, + 2, + "CONDITIONING" + ], + [ + 37, + 17, + 0, + 24, + 0, + "IMAGE" + ], + [ + 38, + 25, + 0, + 24, + 1, + "INT" + ], + [ + 39, + 25, + 0, + 24, + 2, + "INT" + ], + [ + 40, + 25, + 0, + 24, + 3, + "INT" + ], + [ + 41, + 25, + 0, + 24, + 4, + "INT" + ], + [ + 42, + 24, + 0, + 26, + 0, + "IMAGE" + ], + [ + 43, + 27, + 0, + 23, + 0, + "IMAGE" + ], + [ + 44, + 17, + 0, + 27, + 0, + "IMAGE" + ], + [ + 45, + 26, + 0, + 27, + 1, + "STRING" + ], + [ + 46, + 28, + 0, + 1, + 0, + "Http,Https,None" + ], + [ + 47, + 29, + 0, + 1, + 1, + "STRING" + ], + [ + 48, + 28, + 0, + 27, + 2, + "Http,Https,None" + ], + [ + 85, + 29, + 0, + 27, + 3, + "STRING" + ], + [ + 87, + 2, + 0, + 22, + 0, + "IMAGE" + ], + [ + 88, + 37, + 0, + 38, + 0, + "MASK" + ], + [ + 89, + 1, + 3, + 38, + 1, + "MASK" + ], + [ + 91, + 3, + 3, + 39, + 0, + "MASK" + ], + [ + 92, + 39, + 0, + 8, + 0, + "MASK" + ], + [ + 93, + 39, + 0, + 21, + 1, + "MASK" + ], + [ + 101, + 38, + 0, + 45, + 0, + "MASK" + ], + [ + 102, + 45, + 0, + 39, + 1, + "MASK" + ], + [ + 103, + 46, + 0, + 4, + 0, + "INT" + ], + [ + 104, + 47, + 0, + 4, + 1, + "INT" + ], + [ + 105, + 46, + 0, + 37, + 0, + "INT" + ], + [ + 106, + 47, + 0, + 37, + 1, + "INT" + ], + [ + 107, + 49, + 0, + 6, + 3, + "INT" + ], + [ + 108, + 50, + 0, + 6, + 4, + "INT" + ], + [ + 109, + 49, + 0, + 38, + 2, + "INT" + ], + [ + 110, + 50, + 0, + 38, + 3, + "INT" + ] + ], + "groups": [], + "config": {}, + "extra": {}, + "version": 0.4 +} \ No newline at end of file diff --git a/examples/img2img-on-errors.json b/examples/img2img-on-errors.json new file mode 100644 index 0000000..a6a8d91 --- /dev/null +++ b/examples/img2img-on-errors.json @@ -0,0 +1,1891 @@ +{ + "last_node_id": 50, + "last_link_id": 111, + "nodes": [ + { + "id": 12, + "type": "ControlNetLoader", + "pos": [ + -692.0780999999998, + 618.3673000000019 + ], + "size": { + "0": 315, + "1": 58 + }, + "flags": {}, + "order": 0, + "mode": 0, + "outputs": [ + { + "name": "CONTROL_NET", + "type": "CONTROL_NET", + "links": [ + 13 + ], + "shape": 3, + "slot_index": 0 + } + ], + "properties": { + "Node name for S&R": "ControlNetLoader" + }, + "widgets_values": [ + "control_v1p_sd15_qrcode_monster_v2.safetensors" + ] + }, + { + "id": 6, + "type": "ImageCompositeMasked", + "pos": [ + -332.0780999999991, + -91.63270000000001 + ], + "size": [ + 315, + 146 + ], + "flags": {}, + "order": 17, + "mode": 0, + "inputs": [ + { + "name": "destination", + "type": "IMAGE", + "link": 3 + }, + { + "name": "source", + "type": "IMAGE", + "link": 2 + }, + { + "name": "mask", + "type": "MASK", + "link": null + }, + { + "name": "x", + "type": "INT", + "link": 107, + "widget": { + "name": "x", + "config": [ + "INT", + { + "default": 0, + "min": 0, + "max": 8192, + "step": 1 + } + ] + } + }, + { + "name": "y", + "type": "INT", + "link": 108, + "widget": { + "name": "y", + "config": [ + "INT", + { + "default": 0, + "min": 0, + "max": 8192, + "step": 1 + } + ] + } + } + ], + "outputs": [ + { + "name": "IMAGE", + "type": "IMAGE", + "links": [ + 4, + 12, + 25 + ], + "shape": 3, + "slot_index": 0 + } + ], + "properties": { + "Node name for S&R": "ImageCompositeMasked" + }, + "widgets_values": [ + 16, + 16, + false + ] + }, + { + "id": 7, + "type": "PreviewImage", + "pos": [ + 877.9219000000076, + 18.367300000000753 + ], + "size": { + "0": 210, + "1": 246 + }, + "flags": {}, + "order": 24, + "mode": 0, + "inputs": [ + { + "name": "images", + "type": "IMAGE", + "link": 7 + } + ], + "properties": { + "Node name for S&R": "PreviewImage" + } + }, + { + "id": 19, + "type": "PreviewImage", + "pos": [ + 1937.9218999999898, + 158.36729999999957 + ], + "size": { + "0": 210, + "1": 246 + }, + "flags": {}, + "order": 30, + "mode": 0, + "inputs": [ + { + "name": "images", + "type": "IMAGE", + "link": 27 + } + ], + "properties": { + "Node name for S&R": "PreviewImage" + } + }, + { + "id": 10, + "type": "CLIPTextEncode", + "pos": [ + -242.0780999999985, + 498.3673000000002 + ], + "size": { + "0": 400, + "1": 200 + }, + "flags": {}, + "order": 9, + "mode": 0, + "inputs": [ + { + "name": "clip", + "type": "CLIP", + "link": 8 + } + ], + "outputs": [ + { + "name": "CONDITIONING", + "type": "CONDITIONING", + "links": [ + 10 + ], + "shape": 3, + "slot_index": 0 + } + ], + "title": "Positive Prompt", + "properties": { + "Node name for S&R": "CLIPTextEncode" + }, + "widgets_values": [ + "An evil robot inside a factory destroys paperwork by firing a laser beam with his eyes, futuristic, colorful" + ] + }, + { + "id": 11, + "type": "CLIPTextEncode", + "pos": [ + -242.0780999999985, + 768.3673000000036 + ], + "size": { + "0": 400, + "1": 200 + }, + "flags": {}, + "order": 10, + "mode": 0, + "inputs": [ + { + "name": "clip", + "type": "CLIP", + "link": 9 + } + ], + "outputs": [ + { + "name": "CONDITIONING", + "type": "CONDITIONING", + "links": [ + 11 + ], + "shape": 3, + "slot_index": 0 + } + ], + "title": "Negative Prompt", + "properties": { + "Node name for S&R": "CLIPTextEncode" + }, + "widgets_values": [ + "worst quality" + ] + }, + { + "id": 9, + "type": "CheckpointLoaderSimple", + "pos": [ + -692.0780999999998, + 748.3673000000032 + ], + "size": { + "0": 315, + "1": 98 + }, + "flags": {}, + "order": 1, + "mode": 0, + "outputs": [ + { + "name": "MODEL", + "type": "MODEL", + "links": [ + 16 + ], + "shape": 3, + "slot_index": 0 + }, + { + "name": "CLIP", + "type": "CLIP", + "links": [ + 8, + 9 + ], + "shape": 3, + "slot_index": 1 + }, + { + "name": "VAE", + "type": "VAE", + "links": [ + 18, + 33 + ], + "shape": 3, + "slot_index": 2 + } + ], + "properties": { + "Node name for S&R": "CheckpointLoaderSimple" + }, + "widgets_values": [ + "1.5/Deliberate-inpainting.safetensors" + ] + }, + { + "id": 28, + "type": "PrimitiveNode", + "pos": [ + -783.0781000000013, + -277.6327000000003 + ], + "size": { + "0": 210, + "1": 82 + }, + "flags": {}, + "order": 2, + "mode": 0, + "outputs": [ + { + "name": "COMBO", + "type": "Http,Https,None", + "links": [ + 46 + ], + "slot_index": 0, + "widget": { + "name": "protocol", + "config": [ + [ + "Http", + "Https", + "None" + ], + { + "default": "Https" + } + ] + } + } + ], + "title": "Protocol", + "properties": {}, + "widgets_values": [ + "Https", + "fixed" + ] + }, + { + "id": 29, + "type": "PrimitiveNode", + "pos": [ + -535.078099999998, + -266.6327000000004 + ], + "size": { + "0": 210, + "1": 76 + }, + "flags": {}, + "order": 3, + "mode": 0, + "outputs": [ + { + "name": "STRING", + "type": "STRING", + "links": [ + 47 + ], + "slot_index": 0, + "widget": { + "name": "text", + "config": [ + "STRING", + { + "multiline": true + } + ] + } + } + ], + "title": "URL", + "properties": {}, + "widgets_values": [ + "gitlab.com/sofuego-comfy-nodes/comfy-qr-validation-nodes" + ] + }, + { + "id": 22, + "type": "VAEEncode", + "pos": [ + 208, + 231 + ], + "size": { + "0": 210, + "1": 46 + }, + "flags": {}, + "order": 12, + "mode": 0, + "inputs": [ + { + "name": "pixels", + "type": "IMAGE", + "link": 87 + }, + { + "name": "vae", + "type": "VAE", + "link": 33 + } + ], + "outputs": [ + { + "name": "LATENT", + "type": "LATENT", + "links": [ + 31 + ], + "shape": 3, + "slot_index": 0 + } + ], + "properties": { + "Node name for S&R": "VAEEncode" + } + }, + { + "id": 14, + "type": "KSampler", + "pos": [ + 647.9219000000045, + 458.3673000000005 + ], + "size": [ + 315, + 474.00000762939453 + ], + "flags": {}, + "order": 25, + "mode": 0, + "inputs": [ + { + "name": "model", + "type": "MODEL", + "link": 16 + }, + { + "name": "positive", + "type": "CONDITIONING", + "link": 35 + }, + { + "name": "negative", + "type": "CONDITIONING", + "link": 36 + }, + { + "name": "latent_image", + "type": "LATENT", + "link": 32 + } + ], + "outputs": [ + { + "name": "LATENT", + "type": "LATENT", + "links": [ + 17 + ], + "shape": 3, + "slot_index": 0 + } + ], + "properties": { + "Node name for S&R": "KSampler" + }, + "widgets_values": [ + 927301192808348, + "randomize", + 25, + 8, + "euler_ancestral", + "normal", + 0.9600000000000002 + ] + }, + { + "id": 8, + "type": "MaskToImage", + "pos": [ + 558, + -42 + ], + "size": { + "0": 210, + "1": 26 + }, + "flags": {}, + "order": 22, + "mode": 0, + "inputs": [ + { + "name": "mask", + "type": "MASK", + "link": 92 + } + ], + "outputs": [ + { + "name": "IMAGE", + "type": "IMAGE", + "links": [ + 7 + ], + "shape": 3, + "slot_index": 0 + } + ], + "properties": { + "Node name for S&R": "MaskToImage" + } + }, + { + "id": 4, + "type": "SolidMask", + "pos": [ + -970, + 294 + ], + "size": [ + 315, + 106 + ], + "flags": {}, + "order": 13, + "mode": 0, + "inputs": [ + { + "name": "width", + "type": "INT", + "link": 103, + "widget": { + "name": "width", + "config": [ + "INT", + { + "default": 512, + "min": 1, + "max": 8192, + "step": 1 + } + ] + } + }, + { + "name": "height", + "type": "INT", + "link": 104, + "widget": { + "name": "height", + "config": [ + "INT", + { + "default": 512, + "min": 1, + "max": 8192, + "step": 1 + } + ] + } + } + ], + "outputs": [ + { + "name": "MASK", + "type": "MASK", + "links": [ + 1 + ], + "shape": 3, + "slot_index": 0 + } + ], + "title": "Mask (Gray)", + "properties": { + "Node name for S&R": "SolidMask" + }, + "widgets_values": [ + 0.5, + 768, + 768 + ] + }, + { + "id": 1, + "type": "comfy-qr-by-module-split", + "pos": [ + -842.0781000000012, + -131.63269999999977 + ], + "size": { + "0": 400, + "1": 344 + }, + "flags": {}, + "order": 11, + "mode": 0, + "inputs": [ + { + "name": "protocol", + "type": "Http,Https,None", + "link": 46, + "widget": { + "name": "protocol", + "config": [ + [ + "Http", + "Https", + "None" + ], + { + "default": "Https" + } + ] + } + }, + { + "name": "text", + "type": "STRING", + "link": 47, + "widget": { + "name": "text", + "config": [ + "STRING", + { + "multiline": true + } + ] + } + } + ], + "outputs": [ + { + "name": "QR_FLATTENED", + "type": "IMAGE", + "links": [ + 2 + ], + "shape": 3, + "slot_index": 0 + }, + { + "name": "MODULE_LAYER", + "type": "IMAGE", + "links": [], + "shape": 3, + "slot_index": 1 + }, + { + "name": "FUNCTION_LAYER", + "type": "IMAGE", + "links": [], + "shape": 3, + "slot_index": 2 + }, + { + "name": "FUNCTION_MASK", + "type": "MASK", + "links": [ + 89 + ], + "shape": 3, + "slot_index": 3 + }, + { + "name": "QR_VERSION", + "type": "INT", + "links": null, + "shape": 3 + }, + { + "name": "IMAGE_SIZE", + "type": "INT", + "links": null, + "shape": 3 + } + ], + "properties": { + "Node name for S&R": "comfy-qr-by-module-split" + }, + "widgets_values": [ + "Https", + "gitlab.com/sofuego-comfy-nodes/comfy-qr-validation-nodes", + 16, + 768, + "#000000", + "#FFFFFF", + "High", + 1 + ] + }, + { + "id": 21, + "type": "SetLatentNoiseMask", + "pos": [ + 551, + 238 + ], + "size": { + "0": 210, + "1": 46 + }, + "flags": {}, + "order": 23, + "mode": 0, + "inputs": [ + { + "name": "samples", + "type": "LATENT", + "link": 31 + }, + { + "name": "mask", + "type": "MASK", + "link": 93 + } + ], + "outputs": [ + { + "name": "LATENT", + "type": "LATENT", + "links": [ + 32 + ], + "shape": 3, + "slot_index": 0 + } + ], + "properties": { + "Node name for S&R": "SetLatentNoiseMask" + } + }, + { + "id": 13, + "type": "ControlNetApplyAdvanced", + "pos": [ + 247.9219000000045, + 508.36730000000017 + ], + "size": { + "0": 315, + "1": 166 + }, + "flags": {}, + "order": 20, + "mode": 0, + "inputs": [ + { + "name": "positive", + "type": "CONDITIONING", + "link": 10 + }, + { + "name": "negative", + "type": "CONDITIONING", + "link": 11 + }, + { + "name": "control_net", + "type": "CONTROL_NET", + "link": 13 + }, + { + "name": "image", + "type": "IMAGE", + "link": 12 + } + ], + "outputs": [ + { + "name": "positive", + "type": "CONDITIONING", + "links": [ + 35 + ], + "shape": 3, + "slot_index": 0 + }, + { + "name": "negative", + "type": "CONDITIONING", + "links": [ + 36 + ], + "shape": 3, + "slot_index": 1 + } + ], + "properties": { + "Node name for S&R": "ControlNetApplyAdvanced" + }, + "widgets_values": [ + 5, + 0, + 1 + ] + }, + { + "id": 39, + "type": "MaskComposite", + "pos": [ + 483, + 50 + ], + "size": { + "0": 315, + "1": 126 + }, + "flags": {}, + "order": 21, + "mode": 0, + "inputs": [ + { + "name": "destination", + "type": "MASK", + "link": 91 + }, + { + "name": "source", + "type": "MASK", + "link": 102 + } + ], + "outputs": [ + { + "name": "MASK", + "type": "MASK", + "links": [ + 92, + 93 + ], + "shape": 3, + "slot_index": 0 + } + ], + "properties": { + "Node name for S&R": "MaskComposite" + }, + "widgets_values": [ + 0, + 0, + "add" + ] + }, + { + "id": 5, + "type": "MaskToImage", + "pos": [ + -592, + 305 + ], + "size": { + "0": 210, + "1": 26 + }, + "flags": {}, + "order": 15, + "mode": 0, + "inputs": [ + { + "name": "mask", + "type": "MASK", + "link": 1 + } + ], + "outputs": [ + { + "name": "IMAGE", + "type": "IMAGE", + "links": [ + 3 + ], + "shape": 3, + "slot_index": 0 + } + ], + "properties": { + "Node name for S&R": "MaskToImage" + } + }, + { + "id": 18, + "type": "comfy-qr-quantify-match", + "pos": [ + 1253, + 129 + ], + "size": { + "0": 317.4000244140625, + "1": 238 + }, + "flags": {}, + "order": 27, + "mode": 0, + "inputs": [ + { + "name": "source_qr", + "type": "IMAGE", + "link": 25 + }, + { + "name": "modified_qr", + "type": "IMAGE", + "link": 24 + } + ], + "outputs": [ + { + "name": "PERCENT_ERROR", + "type": "FLOAT", + "links": null, + "shape": 3 + }, + { + "name": "CORRELATION", + "type": "FLOAT", + "links": null, + "shape": 3 + }, + { + "name": "RMSE", + "type": "FLOAT", + "links": null, + "shape": 3 + }, + { + "name": "QR_ERROR_MASK", + "type": "MASK", + "links": [ + 26 + ], + "shape": 3, + "slot_index": 3 + } + ], + "properties": { + "Node name for S&R": "comfy-qr-quantify-match" + }, + "widgets_values": [ + 16, + "mean", + "mean", + "module_pattern", + 0, + "False" + ] + }, + { + "id": 3, + "type": "comfy-qr-quantify-match", + "pos": [ + 97.9219000000007, + -111.63269999999997 + ], + "size": { + "0": 317.4000244140625, + "1": 238 + }, + "flags": {}, + "order": 19, + "mode": 0, + "inputs": [ + { + "name": "source_qr", + "type": "IMAGE", + "link": 4 + }, + { + "name": "modified_qr", + "type": "IMAGE", + "link": 5 + } + ], + "outputs": [ + { + "name": "PERCENT_ERROR", + "type": "FLOAT", + "links": null, + "shape": 3 + }, + { + "name": "CORRELATION", + "type": "FLOAT", + "links": null, + "shape": 3 + }, + { + "name": "RMSE", + "type": "FLOAT", + "links": null, + "shape": 3 + }, + { + "name": "QR_ERROR_MASK", + "type": "MASK", + "links": [ + 91 + ], + "shape": 3, + "slot_index": 3 + } + ], + "properties": { + "Node name for S&R": "comfy-qr-quantify-match" + }, + "widgets_values": [ + 16, + "mean", + "mean", + "module_pattern", + 0.5, + "False" + ] + }, + { + "id": 2, + "type": "LoadImage", + "pos": [ + -288, + 115 + ], + "size": { + "0": 315, + "1": 314.0000305175781 + }, + "flags": {}, + "order": 4, + "mode": 0, + "outputs": [ + { + "name": "IMAGE", + "type": "IMAGE", + "links": [ + 5, + 87 + ], + "shape": 3, + "slot_index": 0 + }, + { + "name": "MASK", + "type": "MASK", + "links": null, + "shape": 3 + } + ], + "properties": { + "Node name for S&R": "LoadImage" + }, + "widgets_values": [ + "unscannable_00001_ (1).png", + "image" + ] + }, + { + "id": 47, + "type": "PrimitiveNode", + "pos": [ + -1252, + 425 + ], + "size": { + "0": 210, + "1": 82 + }, + "flags": {}, + "order": 5, + "mode": 0, + "outputs": [ + { + "name": "INT", + "type": "INT", + "links": [ + 104, + 106 + ], + "slot_index": 0, + "widget": { + "name": "height", + "config": [ + "INT", + { + "default": 512, + "min": 1, + "max": 8192, + "step": 1 + } + ] + } + } + ], + "title": "Height", + "properties": {}, + "widgets_values": [ + 768, + "fixed" + ] + }, + { + "id": 46, + "type": "PrimitiveNode", + "pos": [ + -1250, + 278 + ], + "size": { + "0": 210, + "1": 82 + }, + "flags": {}, + "order": 6, + "mode": 0, + "outputs": [ + { + "name": "INT", + "type": "INT", + "links": [ + 103, + 105 + ], + "slot_index": 0, + "widget": { + "name": "width", + "config": [ + "INT", + { + "default": 512, + "min": 1, + "max": 8192, + "step": 1 + } + ] + } + } + ], + "title": "Width", + "properties": {}, + "widgets_values": [ + 768, + "fixed" + ] + }, + { + "id": 49, + "type": "PrimitiveNode", + "pos": [ + -1149, + -41 + ], + "size": { + "0": 210, + "1": 82 + }, + "flags": {}, + "order": 7, + "mode": 0, + "outputs": [ + { + "name": "INT", + "type": "INT", + "links": [ + 107, + 109 + ], + "slot_index": 0, + "widget": { + "name": "x", + "config": [ + "INT", + { + "default": 0, + "min": 0, + "max": 8192, + "step": 1 + } + ] + } + } + ], + "title": "X Offset", + "properties": {}, + "widgets_values": [ + 16, + "fixed" + ] + }, + { + "id": 50, + "type": "PrimitiveNode", + "pos": [ + -1146, + 94 + ], + "size": { + "0": 210, + "1": 82 + }, + "flags": {}, + "order": 8, + "mode": 0, + "outputs": [ + { + "name": "INT", + "type": "INT", + "links": [ + 108, + 110 + ], + "slot_index": 0, + "widget": { + "name": "y", + "config": [ + "INT", + { + "default": 0, + "min": 0, + "max": 8192, + "step": 1 + } + ] + } + } + ], + "title": "Y Offset", + "properties": {}, + "widgets_values": [ + 16, + "fixed" + ] + }, + { + "id": 37, + "type": "SolidMask", + "pos": [ + -994, + 455 + ], + "size": [ + 315, + 106 + ], + "flags": {}, + "order": 14, + "mode": 0, + "inputs": [ + { + "name": "width", + "type": "INT", + "link": 105, + "widget": { + "name": "width", + "config": [ + "INT", + { + "default": 512, + "min": 1, + "max": 8192, + "step": 1 + } + ] + } + }, + { + "name": "height", + "type": "INT", + "link": 106, + "widget": { + "name": "height", + "config": [ + "INT", + { + "default": 512, + "min": 1, + "max": 8192, + "step": 1 + } + ] + } + } + ], + "outputs": [ + { + "name": "MASK", + "type": "MASK", + "links": [ + 88 + ], + "shape": 3, + "slot_index": 0 + } + ], + "title": "Mask (Black)", + "properties": { + "Node name for S&R": "SolidMask" + }, + "widgets_values": [ + 0, + 768, + 768 + ] + }, + { + "id": 38, + "type": "MaskComposite", + "pos": [ + -635, + 413 + ], + "size": [ + 315, + 126 + ], + "flags": {}, + "order": 16, + "mode": 0, + "inputs": [ + { + "name": "destination", + "type": "MASK", + "link": 88 + }, + { + "name": "source", + "type": "MASK", + "link": 89 + }, + { + "name": "x", + "type": "INT", + "link": 109, + "widget": { + "name": "x", + "config": [ + "INT", + { + "default": 0, + "min": 0, + "max": 8192, + "step": 1 + } + ] + } + }, + { + "name": "y", + "type": "INT", + "link": 110, + "widget": { + "name": "y", + "config": [ + "INT", + { + "default": 0, + "min": 0, + "max": 8192, + "step": 1 + } + ] + } + } + ], + "outputs": [ + { + "name": "MASK", + "type": "MASK", + "links": [ + 101 + ], + "shape": 3, + "slot_index": 0 + } + ], + "title": "Function Mask Expanded", + "properties": { + "Node name for S&R": "MaskComposite" + }, + "widgets_values": [ + 16, + 16, + "add" + ] + }, + { + "id": 45, + "type": "GrowMask", + "pos": [ + 150, + 353 + ], + "size": { + "0": 315, + "1": 82 + }, + "flags": {}, + "order": 18, + "mode": 0, + "inputs": [ + { + "name": "mask", + "type": "MASK", + "link": 101 + } + ], + "outputs": [ + { + "name": "MASK", + "type": "MASK", + "links": [ + 102 + ], + "shape": 3, + "slot_index": 0 + } + ], + "properties": { + "Node name for S&R": "GrowMask" + }, + "widgets_values": [ + 16, + false + ] + }, + { + "id": 20, + "type": "MaskToImage", + "pos": [ + 1644, + 142 + ], + "size": { + "0": 210, + "1": 26 + }, + "flags": {}, + "order": 29, + "mode": 0, + "inputs": [ + { + "name": "mask", + "type": "MASK", + "link": 26 + } + ], + "outputs": [ + { + "name": "IMAGE", + "type": "IMAGE", + "links": [ + 27 + ], + "shape": 3, + "slot_index": 0 + } + ], + "properties": { + "Node name for S&R": "MaskToImage" + } + }, + { + "id": 17, + "type": "VAEDecode", + "pos": [ + 1039, + 475 + ], + "size": { + "0": 210, + "1": 46 + }, + "flags": {}, + "order": 26, + "mode": 0, + "inputs": [ + { + "name": "samples", + "type": "LATENT", + "link": 17 + }, + { + "name": "vae", + "type": "VAE", + "link": 18 + } + ], + "outputs": [ + { + "name": "IMAGE", + "type": "IMAGE", + "links": [ + 24, + 111 + ], + "shape": 3, + "slot_index": 0 + } + ], + "properties": { + "Node name for S&R": "VAEDecode" + } + }, + { + "id": 23, + "type": "SaveImage", + "pos": [ + 1388, + 501 + ], + "size": { + "0": 315, + "1": 270 + }, + "flags": {}, + "order": 28, + "mode": 0, + "inputs": [ + { + "name": "images", + "type": "IMAGE", + "link": 111 + } + ], + "properties": {}, + "widgets_values": [ + "ComfyUI" + ] + } + ], + "links": [ + [ + 1, + 4, + 0, + 5, + 0, + "MASK" + ], + [ + 2, + 1, + 0, + 6, + 1, + "IMAGE" + ], + [ + 3, + 5, + 0, + 6, + 0, + "IMAGE" + ], + [ + 4, + 6, + 0, + 3, + 0, + "IMAGE" + ], + [ + 5, + 2, + 0, + 3, + 1, + "IMAGE" + ], + [ + 7, + 8, + 0, + 7, + 0, + "IMAGE" + ], + [ + 8, + 9, + 1, + 10, + 0, + "CLIP" + ], + [ + 9, + 9, + 1, + 11, + 0, + "CLIP" + ], + [ + 10, + 10, + 0, + 13, + 0, + "CONDITIONING" + ], + [ + 11, + 11, + 0, + 13, + 1, + "CONDITIONING" + ], + [ + 12, + 6, + 0, + 13, + 3, + "IMAGE" + ], + [ + 13, + 12, + 0, + 13, + 2, + "CONTROL_NET" + ], + [ + 16, + 9, + 0, + 14, + 0, + "MODEL" + ], + [ + 17, + 14, + 0, + 17, + 0, + "LATENT" + ], + [ + 18, + 9, + 2, + 17, + 1, + "VAE" + ], + [ + 24, + 17, + 0, + 18, + 1, + "IMAGE" + ], + [ + 25, + 6, + 0, + 18, + 0, + "IMAGE" + ], + [ + 26, + 18, + 3, + 20, + 0, + "MASK" + ], + [ + 27, + 20, + 0, + 19, + 0, + "IMAGE" + ], + [ + 31, + 22, + 0, + 21, + 0, + "LATENT" + ], + [ + 32, + 21, + 0, + 14, + 3, + "LATENT" + ], + [ + 33, + 9, + 2, + 22, + 1, + "VAE" + ], + [ + 35, + 13, + 0, + 14, + 1, + "CONDITIONING" + ], + [ + 36, + 13, + 1, + 14, + 2, + "CONDITIONING" + ], + [ + 46, + 28, + 0, + 1, + 0, + "Http,Https,None" + ], + [ + 47, + 29, + 0, + 1, + 1, + "STRING" + ], + [ + 87, + 2, + 0, + 22, + 0, + "IMAGE" + ], + [ + 88, + 37, + 0, + 38, + 0, + "MASK" + ], + [ + 89, + 1, + 3, + 38, + 1, + "MASK" + ], + [ + 91, + 3, + 3, + 39, + 0, + "MASK" + ], + [ + 92, + 39, + 0, + 8, + 0, + "MASK" + ], + [ + 93, + 39, + 0, + 21, + 1, + "MASK" + ], + [ + 101, + 38, + 0, + 45, + 0, + "MASK" + ], + [ + 102, + 45, + 0, + 39, + 1, + "MASK" + ], + [ + 103, + 46, + 0, + 4, + 0, + "INT" + ], + [ + 104, + 47, + 0, + 4, + 1, + "INT" + ], + [ + 105, + 46, + 0, + 37, + 0, + "INT" + ], + [ + 106, + 47, + 0, + 37, + 1, + "INT" + ], + [ + 107, + 49, + 0, + 6, + 3, + "INT" + ], + [ + 108, + 50, + 0, + 6, + 4, + "INT" + ], + [ + 109, + 49, + 0, + 38, + 2, + "INT" + ], + [ + 110, + 50, + 0, + 38, + 3, + "INT" + ], + [ + 111, + 17, + 0, + 23, + 0, + "IMAGE" + ] + ], + "groups": [], + "config": {}, + "extra": {}, + "version": 0.4 +} \ No newline at end of file diff --git a/examples/img2img-only.json b/examples/img2img-only.json index ffc3508..2e489b8 100644 --- a/examples/img2img-only.json +++ b/examples/img2img-only.json @@ -1,6 +1,6 @@ { - "last_node_id": 13, - "last_link_id": 13, + "last_node_id": 14, + "last_link_id": 14, "nodes": [ { "id": 7, @@ -59,7 +59,7 @@ { "name": "pixels", "type": "IMAGE", - "link": 4 + "link": 14 }, { "name": "vae", @@ -240,51 +240,6 @@ "1.5/v1-5-pruned-emaonly.safetensors" ] }, - { - "id": 4, - "type": "comfy-qr-by-image-size", - "pos": [ - 202, - 581 - ], - "size": { - "0": 400, - "1": 264 - }, - "flags": {}, - "order": 1, - "mode": 0, - "outputs": [ - { - "name": "IMAGE", - "type": "IMAGE", - "links": [ - 4 - ], - "shape": 3, - "slot_index": 0 - }, - { - "name": "QR_VERSION", - "type": "INT", - "links": null, - "shape": 3 - } - ], - "properties": { - "Node name for S&R": "comfy-qr-by-image-size" - }, - "widgets_values": [ - "Https", - "gitlab.com/sofuego-comfy-nodes/comfy-qr", - 512, - "#222222", - "#DDDDDD", - "High", - 1, - "Nearest" - ] - }, { "id": 11, "type": "KSampler", @@ -336,7 +291,7 @@ "Node name for S&R": "KSampler" }, "widgets_values": [ - 321184522216123, + 1077540641933383, "randomize", 20, 8, @@ -344,6 +299,51 @@ "normal", 0.6499999999999999 ] + }, + { + "id": 14, + "type": "comfy-qr-by-image-size", + "pos": [ + 264, + 659 + ], + "size": { + "0": 400, + "1": 264 + }, + "flags": {}, + "order": 1, + "mode": 0, + "outputs": [ + { + "name": "QR_CODE", + "type": "IMAGE", + "links": [ + 14 + ], + "shape": 3, + "slot_index": 0 + }, + { + "name": "QR_VERSION", + "type": "INT", + "links": null, + "shape": 3 + } + ], + "properties": { + "Node name for S&R": "comfy-qr-by-image-size" + }, + "widgets_values": [ + "Https", + "gitlab.com/sofuego-comfy-nodes/comfy-qr", + 512, + "#000000", + "#DDDDDD", + "High", + 1, + "Nearest" + ] } ], "links": [ @@ -363,14 +363,6 @@ 0, "CLIP" ], - [ - 4, - 4, - 0, - 9, - 0, - "IMAGE" - ], [ 5, 6, @@ -434,6 +426,14 @@ 11, 2, "CONDITIONING" + ], + [ + 14, + 14, + 0, + 9, + 0, + "IMAGE" ] ], "groups": [], diff --git a/examples/module-driven-control-net.json b/examples/module-driven-control-net.json index d2de4d4..c669dc3 100644 --- a/examples/module-driven-control-net.json +++ b/examples/module-driven-control-net.json @@ -1,6 +1,6 @@ { - "last_node_id": 18, - "last_link_id": 24, + "last_node_id": 19, + "last_link_id": 27, "nodes": [ { "id": 13, @@ -138,7 +138,7 @@ { "name": "width", "type": "INT", - "link": 14, + "link": 26, "widget": { "name": "width", "config": [ @@ -155,7 +155,7 @@ { "name": "height", "type": "INT", - "link": 15, + "link": 27, "widget": { "name": "height", "config": [ @@ -312,7 +312,7 @@ "Node name for S&R": "KSampler" }, "widgets_values": [ - 483918444996335, + 123662286732457, "randomize", 20, 8, @@ -360,61 +360,6 @@ "worst quality" ] }, - { - "id": 14, - "type": "comfy-qr-by-module-size", - "pos": [ - 169, - 540 - ], - "size": { - "0": 400, - "1": 284 - }, - "flags": {}, - "order": 2, - "mode": 0, - "outputs": [ - { - "name": "IMAGE", - "type": "IMAGE", - "links": [ - 20 - ], - "shape": 3, - "slot_index": 0 - }, - { - "name": "QR_VERSION", - "type": "INT", - "links": null, - "shape": 3 - }, - { - "name": "IMAGE_SIZE", - "type": "INT", - "links": [ - 14, - 15 - ], - "shape": 3, - "slot_index": 2 - } - ], - "properties": { - "Node name for S&R": "comfy-qr-by-module-size" - }, - "widgets_values": [ - "Https", - "gitlab.com/sofuego-comfy-nodes", - 16, - 512, - "#000000", - "#FFFFFF", - "Medium", - 1 - ] - }, { "id": 17, "type": "ControlNetApplyAdvanced", @@ -448,7 +393,7 @@ { "name": "image", "type": "IMAGE", - "link": 20 + "link": 25 } ], "outputs": [ @@ -479,6 +424,61 @@ 0, 1 ] + }, + { + "id": 19, + "type": "comfy-qr-by-module-size", + "pos": [ + 191, + 629 + ], + "size": { + "0": 400, + "1": 284 + }, + "flags": {}, + "order": 2, + "mode": 0, + "outputs": [ + { + "name": "QR_CODE", + "type": "IMAGE", + "links": [ + 25 + ], + "shape": 3, + "slot_index": 0 + }, + { + "name": "QR_VERSION", + "type": "INT", + "links": null, + "shape": 3 + }, + { + "name": "IMAGE_SIZE", + "type": "INT", + "links": [ + 26, + 27 + ], + "shape": 3, + "slot_index": 2 + } + ], + "properties": { + "Node name for S&R": "comfy-qr-by-module-size" + }, + "widgets_values": [ + "Https", + "gitlab.com/sofuego-comfy-nodes", + 16, + 512, + "#000000", + "#FFFFFF", + "Medium", + 1 + ] } ], "links": [ @@ -530,22 +530,6 @@ 0, "IMAGE" ], - [ - 14, - 14, - 2, - 15, - 0, - "INT" - ], - [ - 15, - 14, - 2, - 15, - 1, - "INT" - ], [ 16, 15, @@ -570,14 +554,6 @@ 2, "CONTROL_NET" ], - [ - 20, - 14, - 0, - 17, - 3, - "IMAGE" - ], [ 22, 17, @@ -601,6 +577,30 @@ 11, 1, "CONDITIONING" + ], + [ + 25, + 19, + 0, + 17, + 3, + "IMAGE" + ], + [ + 26, + 19, + 2, + 15, + 0, + "INT" + ], + [ + 27, + 19, + 2, + 15, + 1, + "INT" ] ], "groups": [], diff --git a/examples/simple-qr-error-mask.json b/examples/simple-qr-error-mask.json new file mode 100644 index 0000000..3ddbbb2 --- /dev/null +++ b/examples/simple-qr-error-mask.json @@ -0,0 +1,426 @@ +{ + "last_node_id": 11, + "last_link_id": 12, + "nodes": [ + { + "id": 5, + "type": "MaskToImage", + "pos": [ + 420, + 740 + ], + "size": { + "0": 210, + "1": 26 + }, + "flags": {}, + "order": 3, + "mode": 0, + "inputs": [ + { + "name": "mask", + "type": "MASK", + "link": 1 + } + ], + "outputs": [ + { + "name": "IMAGE", + "type": "IMAGE", + "links": [ + 3 + ], + "shape": 3, + "slot_index": 0 + } + ], + "properties": { + "Node name for S&R": "MaskToImage" + } + }, + { + "id": 6, + "type": "ImageCompositeMasked", + "pos": [ + 640, + 280 + ], + "size": { + "0": 315, + "1": 146 + }, + "flags": {}, + "order": 4, + "mode": 0, + "inputs": [ + { + "name": "destination", + "type": "IMAGE", + "link": 3 + }, + { + "name": "source", + "type": "IMAGE", + "link": 12 + }, + { + "name": "mask", + "type": "MASK", + "link": null + } + ], + "outputs": [ + { + "name": "IMAGE", + "type": "IMAGE", + "links": [ + 8 + ], + "shape": 3, + "slot_index": 0 + } + ], + "properties": { + "Node name for S&R": "ImageCompositeMasked" + }, + "widgets_values": [ + 16, + 16, + false + ] + }, + { + "id": 2, + "type": "LoadImage", + "pos": [ + 680, + 520 + ], + "size": { + "0": 315, + "1": 314.0000305175781 + }, + "flags": {}, + "order": 0, + "mode": 0, + "outputs": [ + { + "name": "IMAGE", + "type": "IMAGE", + "links": [ + 9 + ], + "shape": 3, + "slot_index": 0 + }, + { + "name": "MASK", + "type": "MASK", + "links": null, + "shape": 3 + } + ], + "properties": { + "Node name for S&R": "LoadImage" + }, + "widgets_values": [ + "unscannable_00001_.png", + "image" + ] + }, + { + "id": 9, + "type": "comfy-qr-mask_errors", + "pos": [ + 1030, + 310 + ], + "size": { + "0": 317.4000244140625, + "1": 238 + }, + "flags": {}, + "order": 5, + "mode": 0, + "inputs": [ + { + "name": "source_qr", + "type": "IMAGE", + "link": 8 + }, + { + "name": "modified_qr", + "type": "IMAGE", + "link": 9 + } + ], + "outputs": [ + { + "name": "QR_ERROR_MASK", + "type": "MASK", + "links": [ + 10 + ], + "shape": 3, + "slot_index": 0 + }, + { + "name": "PERCENT_ERROR", + "type": "FLOAT", + "links": null, + "shape": 3 + }, + { + "name": "CORRELATION", + "type": "FLOAT", + "links": null, + "shape": 3 + }, + { + "name": "RMSE", + "type": "FLOAT", + "links": null, + "shape": 3 + } + ], + "properties": { + "Node name for S&R": "comfy-qr-mask_errors" + }, + "widgets_values": [ + 16, + "mean", + "mean", + "module_pattern", + 0, + false + ] + }, + { + "id": 8, + "type": "MaskToImage", + "pos": [ + 1399, + 333 + ], + "size": { + "0": 210, + "1": 26 + }, + "flags": {}, + "order": 6, + "mode": 0, + "inputs": [ + { + "name": "mask", + "type": "MASK", + "link": 10 + } + ], + "outputs": [ + { + "name": "IMAGE", + "type": "IMAGE", + "links": [ + 11 + ], + "shape": 3, + "slot_index": 0 + } + ], + "properties": { + "Node name for S&R": "MaskToImage" + } + }, + { + "id": 10, + "type": "SaveImage", + "pos": [ + 1574, + 474 + ], + "size": { + "0": 315, + "1": 270 + }, + "flags": {}, + "order": 7, + "mode": 0, + "inputs": [ + { + "name": "images", + "type": "IMAGE", + "link": 11 + } + ], + "properties": {}, + "widgets_values": [ + "ComfyUI" + ] + }, + { + "id": 4, + "type": "SolidMask", + "pos": [ + 38, + 733 + ], + "size": { + "0": 315, + "1": 106 + }, + "flags": {}, + "order": 1, + "mode": 0, + "outputs": [ + { + "name": "MASK", + "type": "MASK", + "links": [ + 1 + ], + "shape": 3, + "slot_index": 0 + } + ], + "properties": { + "Node name for S&R": "SolidMask" + }, + "widgets_values": [ + 0.5, + 768, + 768 + ] + }, + { + "id": 11, + "type": "comfy-qr-by-module-split", + "pos": [ + 111, + 258 + ], + "size": { + "0": 400, + "1": 344 + }, + "flags": {}, + "order": 2, + "mode": 0, + "outputs": [ + { + "name": "QR_CODE", + "type": "IMAGE", + "links": [ + 12 + ], + "shape": 3, + "slot_index": 0 + }, + { + "name": "MODULE_LAYER", + "type": "IMAGE", + "links": null, + "shape": 3 + }, + { + "name": "FINDER_LAYER", + "type": "IMAGE", + "links": null, + "shape": 3 + }, + { + "name": "FINDER_MASK", + "type": "MASK", + "links": null, + "shape": 3 + }, + { + "name": "QR_VERSION", + "type": "INT", + "links": null, + "shape": 3 + }, + { + "name": "IMAGE_SIZE", + "type": "INT", + "links": null, + "shape": 3 + } + ], + "properties": { + "Node name for S&R": "comfy-qr-by-module-split" + }, + "widgets_values": [ + "Https", + "gitlab.com/sofuego-comfy-nodes/comfy-qr-validation-nodes", + 16, + 768, + "#000000", + "#FFFFFF", + "High", + 1 + ] + } + ], + "links": [ + [ + 1, + 4, + 0, + 5, + 0, + "MASK" + ], + [ + 3, + 5, + 0, + 6, + 0, + "IMAGE" + ], + [ + 8, + 6, + 0, + 9, + 0, + "IMAGE" + ], + [ + 9, + 2, + 0, + 9, + 1, + "IMAGE" + ], + [ + 10, + 9, + 0, + 8, + 0, + "MASK" + ], + [ + 11, + 8, + 0, + 10, + 0, + "IMAGE" + ], + [ + 12, + 11, + 0, + 6, + 1, + "IMAGE" + ] + ], + "groups": [], + "config": {}, + "extra": {}, + "version": 0.4 +} \ No newline at end of file diff --git a/examples/simple-qr-export.json b/examples/simple-qr-export.json index 6847470..b2d4b20 100644 --- a/examples/simple-qr-export.json +++ b/examples/simple-qr-export.json @@ -1,6 +1,6 @@ { - "last_node_id": 2, - "last_link_id": 1, + "last_node_id": 3, + "last_link_id": 2, "nodes": [ { "id": 2, @@ -20,7 +20,7 @@ { "name": "images", "type": "IMAGE", - "link": 1 + "link": 2 } ], "properties": {}, @@ -29,11 +29,11 @@ ] }, { - "id": 1, + "id": 3, "type": "comfy-qr-by-image-size", "pos": [ - 429, - 492 + 516, + 479 ], "size": { "0": 400, @@ -44,10 +44,10 @@ "mode": 0, "outputs": [ { - "name": "IMAGE", + "name": "QR_CODE", "type": "IMAGE", "links": [ - 1 + 2 ], "shape": 3, "slot_index": 0 @@ -65,7 +65,7 @@ "widgets_values": [ "Https", "gitlab.com/sofuego-comfy-nodes/comfy-qr", - 768, + 512, "00F", "FF0", "High", @@ -76,8 +76,8 @@ ], "links": [ [ - 1, - 1, + 2, + 3, 0, 2, 0, diff --git a/img/node-mask-qr-errors.png b/img/node-mask-qr-errors.png new file mode 100644 index 0000000..45a77a2 Binary files /dev/null and b/img/node-mask-qr-errors.png differ diff --git a/img/node-qr-code-conformed.png b/img/node-qr-code-conformed.png new file mode 100644 index 0000000..b1b28be Binary files /dev/null and b/img/node-qr-code-conformed.png differ diff --git a/img/node-qr-code-split.png b/img/node-qr-code-split.png new file mode 100644 index 0000000..8b10288 Binary files /dev/null and b/img/node-qr-code-split.png differ diff --git a/qr_nodes.py b/qr_nodes.py index 2d754ab..1b4b25e 100644 --- a/qr_nodes.py +++ b/qr_nodes.py @@ -2,6 +2,7 @@ import numpy as np import qrcode from qrcode.compat.pil import Image import torch +import torch.nn.functional as F class QRBase: @@ -88,7 +89,7 @@ class QRByImageSize(QRBase): } RETURN_TYPES = ("IMAGE", "INT") - RETURN_NAMES = ("IMAGE", "QR_VERSION") + RETURN_NAMES = ("QR_CODE", "QR_VERSION") def _select_resampling_method(self, resampling_string): if resampling_string == "Nearest": @@ -148,7 +149,7 @@ class QRByModuleSize(QRBase): } RETURN_TYPES = ("IMAGE", "INT", "INT") - RETURN_NAMES = ("IMAGE", "QR_VERSION", "IMAGE_SIZE") + RETURN_NAMES = ("QR_CODE", "QR_VERSION", "IMAGE_SIZE") def generate_qr( self, @@ -191,7 +192,7 @@ class QRByModuleSizeSplitFunctionPatterns(QRBase): } RETURN_TYPES = ("IMAGE", "IMAGE", "IMAGE", "MASK", "INT", "INT") - RETURN_NAMES = ("QR_FLATTENED", "MODULE_LAYER", "FINDER_LAYER", "FINDER_MASK", "QR_VERSION", "IMAGE_SIZE") + RETURN_NAMES = ("QR_CODE", "MODULE_LAYER", "FINDER_LAYER", "FINDER_MASK", "QR_VERSION", "IMAGE_SIZE") def _generate_finder_pattern_ranges(self, module_size, border_size): outer = module_size * border_size @@ -254,10 +255,198 @@ class QRByModuleSizeSplitFunctionPatterns(QRBase): ) +class QRErrorMasker: + def __init__(self): + self.module_size = None + self.canvas_shape = None + self.qr_bounds = None + + FUNCTION = "find_qr_errors" + CATEGORY = "Comfy-QR" + RETURN_TYPES = ("MASK", "FLOAT", "FLOAT", "FLOAT") + RETURN_NAMES = ("QR_ERROR_MASK", "PERCENT_ERROR", "CORRELATION", "RMSE") + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "source_qr": ("IMAGE",), + "modified_qr": ("IMAGE",), + "module_size": ("INT", {"default": 16, "min": 1, "max": 64, "step": 1}), + "grayscale_method": (["mean",], {"default": "mean"}), + "aggregate_method": (["mean",], {"default": "mean"}), + "evaluate": (["full_qr", "module_pattern", "finder_pattern"], {"default": "module_pattern"}), + "error_difficulty": ("FLOAT", {"default": 0, "min": 0, "max": 1, "step": .01}), + "inverted_pattern": ("BOOLEAN", {"default": False}), + }, + } + + def _get_qr_bounds(self, tensor, invert): + module_color = 1.0 if invert else 0.0 + module_pixels = (tensor == module_color) + indices = torch.nonzero(module_pixels, as_tuple=True) + # The viewer patterns will guarentee a module pixel in the upper left + # The bottom right does not have that guarentee so max is used. + return indices[0][0], indices[0].max() + 1, indices[1][0], indices[1].max() + 1 + + def _extract_pattern_from_bounds(self, tensor): + return tensor[self.qr_bounds[0]:self.qr_bounds[1], self.qr_bounds[2]:self.qr_bounds[3]] + + def _trim_to_qr_area(self, source_qr, modified_qr, inverted_pattern): + self.qr_bounds = self._get_qr_bounds(source_qr, inverted_pattern) + self._check_bounds_and_module_size() + source_qr = self._extract_pattern_from_bounds(source_qr) + modified_qr = self._extract_pattern_from_bounds(modified_qr) + return source_qr, modified_qr + + def _reshape_tensor_to_modules(self, tensor): + if len(tensor.shape) != 2: + raise RuntimeError("Module reshaping requires a 2 dimensional array.") + length = tensor.shape[0] // self.module_size + reshaped_tensor = tensor.view(length, self.module_size, length, self.module_size) + rehaped_tensor = reshaped_tensor.permute(0, 2, 1, 3).contiguous() + return rehaped_tensor.view(length, length, self.module_size ** 2) + + def _check_bounds_and_module_size(self): + height = self.qr_bounds[1] - self.qr_bounds[0] + width = self.qr_bounds[3] - self.qr_bounds[2] + color_warning = "Make sure that qr_fill and back colors have exact #FFFFFFF and #000000 values (and that module color values do not occur outside the QR) and invert is set correctly." + if width != height: + raise RuntimeError(f"Source QR dimensions are {width} x {height}. They must be a perfect square. {color_warning}") + if width % self.module_size: + raise RuntimeError(f"QR width of {width} does not fit module_size of {self.module_size}. It must be perfectly divisible. {color_warning}") + + def _squeeze_by_mean(self, tensor): + return torch.mean(tensor, dim=-1) + + def _squeeze_to_modules(self, tensor, method): + tensor = self._reshape_tensor_to_modules(tensor) + if method == "mean": + return self._squeeze_by_mean(tensor) + raise RuntimeError("Module aggregation currently only supports the mean.") + + def _reduce_to_modules( + self, + source_qr, + modified_qr, + module_size, + grayscale_method, + aggregate_method, + inverted_pattern + ): + if source_qr.shape != modified_qr.shape: + raise ValueError("Source and modified QR must have the same dimensions.") + self.module_size = module_size + self.canvas_shape = (source_qr.shape[1], source_qr.shape[2]) + # Ignore batch dimension + source_qr, modified_qr = source_qr[0], modified_qr[0] + # Processed first for simplified indexing of QR bounds. + source_qr = self._squeeze_by_mean(source_qr) + source_qr, modified_qr = self._trim_to_qr_area(source_qr, + modified_qr, + inverted_pattern + ) + if grayscale_method == "mean": + modified_qr = self._squeeze_by_mean(modified_qr) + else: + raise ValueError("Currently only mean is supported for rgb to grayscale conversion.") + source_qr = self._squeeze_to_modules(source_qr, "mean") + modified_qr = self._squeeze_to_modules(modified_qr, aggregate_method) + return source_qr, modified_qr + + def _create_finder_pattern_mask(self, width, inverted): + mask = np.zeros((width, width), dtype=bool) + # When borders are trimmed and QR code has module size of 1, results + # are consistent. + finder_coords = [[0, 7, 0, 7], [0, 7, -7, None], [-7, None, 0, 7]] + for x_min, x_max, y_min, y_max in finder_coords: + mask[y_min:y_max, x_min:x_max] = True + return ~mask if inverted else mask + + def _create_qr_mask(self, tensor, evaluate): + if evaluate == "module_pattern": + return self._create_finder_pattern_mask(tensor, True) + if evaluate == "finder_pattern": + return self._create_finder_pattern_mask(tensor, False) + return None + + def _bin_tensor_to_threshold(self, tensor, contrast_difficulty): + tensor = tensor.clone() + threshold = contrast_difficulty / 2 + # Since we are only interested in value matches and there is a clear + # stable dividing line of .5, bringing in the other array is + # unneccessary and the binning process can be simplified. + bin_condition = (tensor + threshold <= .5) & (tensor != .5) + tensor[bin_condition] = 0.0 + bin_condition = (tensor - threshold >= .5) & (tensor != .5) + tensor[bin_condition] = 1.0 + return tensor + + def _replace_qr_to_canvas(self, tensor): + length = tensor.shape[0] * self.module_size + bounds = self.qr_bounds + tensor = F.interpolate(tensor.unsqueeze(0).unsqueeze(0), size=(length, length), mode='nearest') + canvas = torch.zeros(self.canvas_shape, dtype=torch.float32) + canvas[bounds[0]:bounds[1], bounds[2]:bounds[3]] = tensor.squeeze() + return canvas + + def _compare_modules( + self, + source_qr, + modified_qr, + mask, + error_difficulty + ): + modified_qr = self._bin_tensor_to_threshold(modified_qr, error_difficulty) + error = source_qr != modified_qr + percent_error = error[mask].sum().item() / error[mask].numel() + if mask is not None: + error[~mask] = False + return self._replace_qr_to_canvas((error).to(torch.float32)), percent_error + + def _qr_correlation(self, source_qr, modified_qr, mask): + source_qr = source_qr[mask].numpy().reshape((-1)) + modified_qr = modified_qr[mask].numpy().reshape((-1)) + return np.corrcoef(source_qr, modified_qr)[0, 1] + + def _qr_rmse(self, source_qr, modified_qr, mask): + diff = source_qr[mask].numpy() - modified_qr[mask].numpy() + return np.sqrt((diff ** 2).mean()) + + def find_qr_errors( + self, + source_qr, + modified_qr, + module_size, + grayscale_method, + aggregate_method, + evaluate, + error_difficulty, + inverted_pattern, + ): + source_qr, modified_qr = self._reduce_to_modules(source_qr, + modified_qr, + module_size, + grayscale_method, + aggregate_method, + inverted_pattern + ) + mask = self._create_qr_mask(source_qr.shape[0], evaluate) + error_mask, percent_error = self._compare_modules(source_qr, + modified_qr, + mask, + error_difficulty + ) + correlation = self._qr_correlation(source_qr, modified_qr, mask) + rmse = self._qr_rmse(source_qr, modified_qr, mask) + return (error_mask, percent_error, correlation, rmse) + + NODE_CLASS_MAPPINGS = { "comfy-qr-by-module-size": QRByModuleSize, "comfy-qr-by-image-size": QRByImageSize, "comfy-qr-by-module-split": QRByModuleSizeSplitFunctionPatterns, + "comfy-qr-mask_errors": QRErrorMasker, } @@ -265,4 +454,5 @@ NODE_DISPLAY_NAME_MAPPINGS = { "comfy-qr-by-module-size": "QR Code", "comfy-qr-by-image-size": "QR Code (Conformed to Image Size)", "comfy-qr-by-module-split": "QR Code (Split)", + "comfy-qr-mask_errors": "Mask QR Errors", }