14 Commits
23 changed files with 882 additions and 974 deletions
+22
View File
@@ -0,0 +1,22 @@
name: Publish to Comfy registry
on:
workflow_dispatch:
push:
branches:
- main
- master
paths:
- "pyproject.toml"
jobs:
publish-node:
name: Publish Custom Node to registry
runs-on: ubuntu-latest
steps:
- name: Check out code
uses: actions/checkout@v4
- name: Publish Custom Node
uses: Comfy-Org/publish-node-action@main
with:
## Add your own personal access token to your Github Repository secrets and reference it here.
personal_access_token: ${{ secrets.REGISTRY_ACCESS_TOKEN }}
+12
View File
@@ -35,6 +35,7 @@ A dynamically sized QR code without any resampling scaled by the text length, th
* `Quartile` - 25% error correction. * `Quartile` - 25% error correction.
* `High` - 30% error correction. * `High` - 30% error correction.
* `border` - The border size (In multiples of `module` widths) * `border` - The border size (In multiples of `module` widths)
* `module_drawer` - The shape the QR code modules should be. The default is `square`, but see [Alternate Module Drawers](#alternate-module-drawers) below for new examples.
#### Outputs #### Outputs
@@ -74,6 +75,7 @@ A QR code fixed to specific output dimensions through image resampling.
* `Hamming` - Hamming interpolation * `Hamming` - Hamming interpolation
* `Lanczos` - Lanczos interpolation * `Lanczos` - Lanczos interpolation
* `Nearest` - Nearest Neighbor interpolation * `Nearest` - Nearest Neighbor interpolation
* `module_drawer` - The shape the QR code modules should be. The default is `square`, but see [Alternate Module Drawers](#alternate-module-drawers) below for new examples.
#### Outputs #### Outputs
@@ -129,6 +131,16 @@ And then aggressively increasing the strength of the ControlNet on only the erro
Any workflow in the example that ends with "validated" (and a few image examples) assume the installation of the [scanning pack](https://gitlab.com/sofuego-comfy-nodes/ComfyQR-scanning-nodes) as well. Any workflow in the example that ends with "validated" (and a few image examples) assume the installation of the [scanning pack](https://gitlab.com/sofuego-comfy-nodes/ComfyQR-scanning-nodes) as well.
## Alternate Module Drawers
The QR generation nodes now support alternate module styles. Experiment using different ones for greater flexibility during generation.
| Square | Gapped Square | Circle | Rounded | Vertical bars | Horizontal bars |
|:---: | :---: | :---: | :---: | :---: | :---: |
| ![Example QR](/img/square.png) | ![Example QR](img/gapped_square.png) | ![Example QR](img/circle.png) | ![Example QR](img/rounded.png) | ![Example QR](img/vertical-bars.png) | ![Example QR](img/horizontal-bars.png) |
**Note** _Even unaltered non square styles will inherently register small deviations from RMSE values and possible minute deviations for correlations when using the [Mask QR Errors](#mask-qr-errors) node. When information is extracted, the `source_qr` values are rounded so that they can only be 0 or 1 (to allow alternate drawers to be used as inputs) and matched against the unrounded aggregation of the `modified_qr` values._
## Future updates ## Future updates
If the solution is clean enough and if it can definitively improve scannability, there may be additional plans for the seperation of alignment patterns (based on `module_size`, `border`, and `QR_VERSION`) for more advanced workflows. If the solution is clean enough and if it can definitively improve scannability, there may be additional plans for the seperation of alignment patterns (based on `module_size`, `border`, and `QR_VERSION`) for more advanced workflows.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 909 KiB

After

Width:  |  Height:  |  Size: 907 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 961 KiB

After

Width:  |  Height:  |  Size: 961 KiB

File diff suppressed because it is too large Load Diff
+257 -382
View File
@@ -1,6 +1,6 @@
{ {
"last_node_id": 51, "last_node_id": 52,
"last_link_id": 114, "last_link_id": 117,
"nodes": [ "nodes": [
{ {
"id": 19, "id": 19,
@@ -204,7 +204,7 @@
], ],
"size": { "size": {
"0": 210, "0": 210,
"1": 82 "1": 106
}, },
"flags": {}, "flags": {},
"order": 1, "order": 1,
@@ -212,31 +212,24 @@
"outputs": [ "outputs": [
{ {
"name": "COMBO", "name": "COMBO",
"type": "Http,Https,None", "type": "COMBO",
"links": [ "links": [
46 46
], ],
"slot_index": 0, "slot_index": 0,
"widget": { "widget": {
"name": "protocol", "name": "protocol"
"config": [
[
"Http",
"Https",
"None"
],
{
"default": "Https"
}
]
} }
} }
], ],
"title": "Protocol", "title": "Protocol",
"properties": {}, "properties": {
"Run widget replace on values": false
},
"widgets_values": [ "widgets_values": [
"Https", "Https",
"fixed" "fixed",
""
] ]
}, },
{ {
@@ -262,18 +255,14 @@
], ],
"slot_index": 0, "slot_index": 0,
"widget": { "widget": {
"name": "text", "name": "text"
"config": [
"STRING",
{
"multiline": true
}
]
} }
} }
], ],
"title": "URL", "title": "URL",
"properties": {}, "properties": {
"Run widget replace on values": false
},
"widgets_values": [ "widgets_values": [
"gitlab.com/sofuego-comfy-nodes/comfy-qr-validation-nodes" "gitlab.com/sofuego-comfy-nodes/comfy-qr-validation-nodes"
] ]
@@ -290,7 +279,7 @@
"1": 46 "1": 46
}, },
"flags": {}, "flags": {},
"order": 12, "order": 14,
"mode": 0, "mode": 0,
"inputs": [ "inputs": [
{ {
@@ -326,10 +315,10 @@
647.9219000000045, 647.9219000000045,
458.3673000000005 458.3673000000005
], ],
"size": [ "size": {
315, "0": 315,
474.00000762939453 "1": 474
], },
"flags": {}, "flags": {},
"order": 25, "order": 25,
"mode": 0, "mode": 0,
@@ -427,7 +416,7 @@
"1": 106 "1": 106
}, },
"flags": {}, "flags": {},
"order": 13, "order": 12,
"mode": 0, "mode": 0,
"inputs": [ "inputs": [
{ {
@@ -435,16 +424,7 @@
"type": "INT", "type": "INT",
"link": 103, "link": 103,
"widget": { "widget": {
"name": "width", "name": "width"
"config": [
"INT",
{
"default": 512,
"min": 1,
"max": 8192,
"step": 1
}
]
} }
}, },
{ {
@@ -452,16 +432,7 @@
"type": "INT", "type": "INT",
"link": 104, "link": 104,
"widget": { "widget": {
"name": "height", "name": "height"
"config": [
"INT",
{
"default": 512,
"min": 1,
"max": 8192,
"step": 1
}
]
} }
} }
], ],
@@ -503,20 +474,10 @@
"inputs": [ "inputs": [
{ {
"name": "protocol", "name": "protocol",
"type": "Http,Https,None", "type": "COMBO",
"link": 46, "link": 46,
"widget": { "widget": {
"name": "protocol", "name": "protocol"
"config": [
[
"Http",
"Https",
"None"
],
{
"default": "Https"
}
]
} }
}, },
{ {
@@ -524,13 +485,7 @@
"type": "STRING", "type": "STRING",
"link": 47, "link": 47,
"widget": { "widget": {
"name": "text", "name": "text"
"config": [
"STRING",
{
"multiline": true
}
]
} }
} }
], ],
@@ -591,7 +546,8 @@
"#000000", "#000000",
"#FFFFFF", "#FFFFFF",
"High", "High",
1 1,
"Square"
] ]
}, },
{ {
@@ -647,7 +603,7 @@
"1": 166 "1": 166
}, },
"flags": {}, "flags": {},
"order": 20, "order": 19,
"mode": 0, "mode": 0,
"inputs": [ "inputs": [
{ {
@@ -718,7 +674,7 @@
{ {
"name": "destination", "name": "destination",
"type": "MASK", "type": "MASK",
"link": 91 "link": 115
}, },
{ {
"name": "source", "name": "source",
@@ -783,113 +739,6 @@
"Node name for S&R": "MaskToImage" "Node name for S&R": "MaskToImage"
} }
}, },
{
"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": 3,
"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, "id": 47,
"type": "PrimitiveNode", "type": "PrimitiveNode",
@@ -902,7 +751,7 @@
"1": 82 "1": 82
}, },
"flags": {}, "flags": {},
"order": 4, "order": 3,
"mode": 0, "mode": 0,
"outputs": [ "outputs": [
{ {
@@ -914,21 +763,14 @@
], ],
"slot_index": 0, "slot_index": 0,
"widget": { "widget": {
"name": "height", "name": "height"
"config": [
"INT",
{
"default": 512,
"min": 1,
"max": 8192,
"step": 1
}
]
} }
} }
], ],
"title": "Height", "title": "Height",
"properties": {}, "properties": {
"Run widget replace on values": false
},
"widgets_values": [ "widgets_values": [
768, 768,
"fixed" "fixed"
@@ -946,7 +788,7 @@
"1": 82 "1": 82
}, },
"flags": {}, "flags": {},
"order": 5, "order": 4,
"mode": 0, "mode": 0,
"outputs": [ "outputs": [
{ {
@@ -958,21 +800,14 @@
], ],
"slot_index": 0, "slot_index": 0,
"widget": { "widget": {
"name": "width", "name": "width"
"config": [
"INT",
{
"default": 512,
"min": 1,
"max": 8192,
"step": 1
}
]
} }
} }
], ],
"title": "Width", "title": "Width",
"properties": {}, "properties": {
"Run widget replace on values": false
},
"widgets_values": [ "widgets_values": [
768, 768,
"fixed" "fixed"
@@ -990,7 +825,7 @@
"1": 82 "1": 82
}, },
"flags": {}, "flags": {},
"order": 6, "order": 5,
"mode": 0, "mode": 0,
"outputs": [ "outputs": [
{ {
@@ -1002,21 +837,14 @@
], ],
"slot_index": 0, "slot_index": 0,
"widget": { "widget": {
"name": "x", "name": "x"
"config": [
"INT",
{
"default": 0,
"min": 0,
"max": 8192,
"step": 1
}
]
} }
} }
], ],
"title": "X Offset", "title": "X Offset",
"properties": {}, "properties": {
"Run widget replace on values": false
},
"widgets_values": [ "widgets_values": [
16, 16,
"fixed" "fixed"
@@ -1034,7 +862,7 @@
"1": 82 "1": 82
}, },
"flags": {}, "flags": {},
"order": 7, "order": 6,
"mode": 0, "mode": 0,
"outputs": [ "outputs": [
{ {
@@ -1046,21 +874,14 @@
], ],
"slot_index": 0, "slot_index": 0,
"widget": { "widget": {
"name": "y", "name": "y"
"config": [
"INT",
{
"default": 0,
"min": 0,
"max": 8192,
"step": 1
}
]
} }
} }
], ],
"title": "Y Offset", "title": "Y Offset",
"properties": {}, "properties": {
"Run widget replace on values": false
},
"widgets_values": [ "widgets_values": [
16, 16,
"fixed" "fixed"
@@ -1078,7 +899,7 @@
"1": 106 "1": 106
}, },
"flags": {}, "flags": {},
"order": 14, "order": 13,
"mode": 0, "mode": 0,
"inputs": [ "inputs": [
{ {
@@ -1086,16 +907,7 @@
"type": "INT", "type": "INT",
"link": 105, "link": 105,
"widget": { "widget": {
"name": "width", "name": "width"
"config": [
"INT",
{
"default": 512,
"min": 1,
"max": 8192,
"step": 1
}
]
} }
}, },
{ {
@@ -1103,16 +915,7 @@
"type": "INT", "type": "INT",
"link": 106, "link": 106,
"widget": { "widget": {
"name": "height", "name": "height"
"config": [
"INT",
{
"default": 512,
"min": 1,
"max": 8192,
"step": 1
}
]
} }
} }
], ],
@@ -1167,16 +970,7 @@
"type": "INT", "type": "INT",
"link": 109, "link": 109,
"widget": { "widget": {
"name": "x", "name": "x"
"config": [
"INT",
{
"default": 0,
"min": 0,
"max": 8192,
"step": 1
}
]
} }
}, },
{ {
@@ -1184,16 +978,7 @@
"type": "INT", "type": "INT",
"link": 110, "link": 110,
"widget": { "widget": {
"name": "y", "name": "y"
"config": [
"INT",
{
"default": 0,
"min": 0,
"max": 8192,
"step": 1
}
]
} }
} }
], ],
@@ -1283,93 +1068,6 @@
"Node name for S&R": "PreviewImage" "Node name for S&R": "PreviewImage"
} }
}, },
{
"id": 6,
"type": "ImageCompositeMasked",
"pos": [
-332.0780999999991,
-91.63270000000001
],
"size": {
"0": 315,
"1": 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,
111
],
"shape": 3,
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "ImageCompositeMasked"
},
"widgets_values": [
16,
16,
false
]
},
{ {
"id": 17, "id": 17,
"type": "VAEDecode", "type": "VAEDecode",
@@ -1492,7 +1190,7 @@
"1": 58 "1": 58
}, },
"flags": {}, "flags": {},
"order": 8, "order": 7,
"mode": 0, "mode": 0,
"outputs": [ "outputs": [
{ {
@@ -1537,6 +1235,183 @@
"widgets_values": [ "widgets_values": [
"ComfyUI" "ComfyUI"
] ]
},
{
"id": 6,
"type": "ImageCompositeMasked",
"pos": [
-332.0780999999991,
-91.63270000000001
],
"size": {
"0": 315,
"1": 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"
}
},
{
"name": "y",
"type": "INT",
"link": 108,
"widget": {
"name": "y"
}
}
],
"outputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"links": [
12,
111,
116
],
"shape": 3,
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "ImageCompositeMasked"
},
"widgets_values": [
16,
16,
false
]
},
{
"id": 2,
"type": "LoadImage",
"pos": [
-288,
115
],
"size": {
"0": 315,
"1": 314.0000305175781
},
"flags": {},
"order": 8,
"mode": 0,
"outputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"links": [
87,
117
],
"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": 52,
"type": "comfy-qr-mask_errors",
"pos": [
120,
-146
],
"size": {
"0": 317.4000244140625,
"1": 262
},
"flags": {},
"order": 20,
"mode": 0,
"inputs": [
{
"name": "source_qr",
"type": "IMAGE",
"link": 116
},
{
"name": "modified_qr",
"type": "IMAGE",
"link": 117
}
],
"outputs": [
{
"name": "QR_ERROR_MASK",
"type": "MASK",
"links": [
115
],
"shape": 3,
"slot_index": 0
},
{
"name": "PERCENT_ERROR",
"type": "FLOAT",
"links": null,
"shape": 6
},
{
"name": "CORRELATION",
"type": "FLOAT",
"links": null,
"shape": 6
},
{
"name": "RMSE",
"type": "FLOAT",
"links": null,
"shape": 6
}
],
"properties": {
"Node name for S&R": "comfy-qr-mask_errors"
},
"widgets_values": [
16,
"mean",
"mean",
"module_pattern",
0.5,
false,
2.2
]
} }
], ],
"links": [ "links": [
@@ -1564,22 +1439,6 @@
0, 0,
"IMAGE" "IMAGE"
], ],
[
4,
6,
0,
3,
0,
"IMAGE"
],
[
5,
2,
0,
3,
1,
"IMAGE"
],
[ [
7, 7,
8, 8,
@@ -1714,7 +1573,7 @@
0, 0,
1, 1,
0, 0,
"Http,Https,None" "COMBO"
], ],
[ [
47, 47,
@@ -1748,14 +1607,6 @@
1, 1,
"MASK" "MASK"
], ],
[
91,
3,
3,
39,
0,
"MASK"
],
[ [
92, 92,
39, 39,
@@ -1883,6 +1734,30 @@
23, 23,
0, 0,
"IMAGE" "IMAGE"
],
[
115,
52,
0,
39,
0,
"MASK"
],
[
116,
6,
0,
52,
0,
"IMAGE"
],
[
117,
2,
0,
52,
1,
"IMAGE"
] ]
], ],
"groups": [], "groups": [],
+3 -2
View File
@@ -342,7 +342,8 @@
"#DDDDDD", "#DDDDDD",
"High", "High",
1, 1,
"Nearest" "Nearest",
"Square"
] ]
} }
], ],
@@ -440,4 +441,4 @@
"config": {}, "config": {},
"extra": {}, "extra": {},
"version": 0.4 "version": 0.4
} }
+2 -1
View File
@@ -477,7 +477,8 @@
"#000000", "#000000",
"#FFFFFF", "#FFFFFF",
"Medium", "Medium",
1 1,
"Square"
] ]
} }
], ],
@@ -524,7 +524,8 @@
"#000000", "#000000",
"#FFFFFF", "#FFFFFF",
"High", "High",
1 1,
"Square"
] ]
}, },
{ {
+2 -1
View File
@@ -394,7 +394,8 @@
"#000000", "#000000",
"#FFFFFF", "#FFFFFF",
"High", "High",
1 1,
"Square"
] ]
}, },
{ {
+3 -2
View File
@@ -357,7 +357,8 @@
"#000000", "#000000",
"#FFFFFF", "#FFFFFF",
"High", "High",
1 1,
"Square"
] ]
} }
], ],
@@ -423,4 +424,4 @@
"config": {}, "config": {},
"extra": {}, "extra": {},
"version": 0.4 "version": 0.4
} }
+3 -2
View File
@@ -70,7 +70,8 @@
"FF0", "FF0",
"High", "High",
1, 1,
"Nearest" "Nearest",
"Circle"
] ]
} }
], ],
@@ -88,4 +89,4 @@
"config": {}, "config": {},
"extra": {}, "extra": {},
"version": 0.4 "version": 0.4
} }
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

After

Width:  |  Height:  |  Size: 59 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

+16
View File
@@ -0,0 +1,16 @@
[project]
name = "comfyqr"
description = "QR generation within ComfyUI. Contains nodes suitable for workflows from generating basic QR images to techniques with advanced QR masking."
version = "0.4.1"
license = "LICENSE"
dependencies = ["qrcode >= 7.1"]
[project.urls]
Repository = "https://github.com/coreyryanhanson/ComfyQR"
# Used by Comfy Registry https://comfyregistry.org
[tool.comfy]
PublisherId = "coreyryanhanson"
DisplayName = "ComfyQR"
Icon = ""
+241 -82
View File
@@ -1,6 +1,13 @@
import numpy as np import numpy as np
import qrcode import qrcode
from qrcode.compat.pil import Image from qrcode.image.styles.moduledrawers import (GappedSquareModuleDrawer,
CircleModuleDrawer,
RoundedModuleDrawer,
VerticalBarsDrawer,
HorizontalBarsDrawer)
from qrcode.image.styledpil import StyledPilImage
from qrcode.image.styles.colormasks import SolidFillColorMask
from PIL import Image
import torch import torch
import torch.nn.functional as F import torch.nn.functional as F
@@ -27,11 +34,19 @@ class QRBase:
out_image = np.array(img, dtype=np.uint8).astype(np.float32) / 255 out_image = np.array(img, dtype=np.uint8).astype(np.float32) / 255
return torch.from_numpy(out_image).unsqueeze(0) return torch.from_numpy(out_image).unsqueeze(0)
def _make_qr(self, qr, fill_hexcolor, back_hexcolor): def _make_qr(self, qr, fill_hexcolor, back_hexcolor, module_drawer):
self.fill = self._parse_hexcolor_string(fill_hexcolor, "fill_hexcolor") self.fill = self._parse_hexcolor_string(fill_hexcolor, "fill_hexcolor")
self.back = self._parse_hexcolor_string(back_hexcolor, "back_hexcolor") self.back = self._parse_hexcolor_string(back_hexcolor, "back_hexcolor")
qr.make(fit=True) qr.make(fit=True)
return qr.make_image(fill_color=self.fill, back_color=self.back) if module_drawer == "Square":
# Keeps using Square QR generation the old way for faster speeds.
return qr.make_image(fill_color=self.fill, back_color=self.back)
color_mask = SolidFillColorMask(back_color=self.back,
front_color=self.fill)
module_drawing_method = self._select_module_drawer(module_drawer)
return qr.make_image(image_factory=StyledPilImage,
color_mask=color_mask,
module_drawer=module_drawing_method)
def _parse_hexcolor_string(self, s, parameter): def _parse_hexcolor_string(self, s, parameter):
if s.startswith("#"): if s.startswith("#"):
@@ -45,11 +60,31 @@ class QRBase:
try: try:
return tuple(int(channel, 16) for channel in rgb) return tuple(int(channel, 16) for channel in rgb)
except ValueError: except ValueError:
raise ValueError(f"{parameter} contains invalid hexadecimal characters") raise ValueError(f"{parameter} contains invalid hexadecimal "
f"characters")
def _validate_qr_size(self, size, max_size): def _validate_qr_size(self, size, max_size):
if size > max_size: if size > max_size:
raise RuntimeError(f"QR dimensions of {size} exceed max size of {max_size}.") raise RuntimeError(f"QR dimensions of {size} exceed max size of "
f"{max_size}.")
def _select_module_drawer(self, module_drawer_string):
"""Square is not included in the results, for a speed optimization
applying color masks. Current version of python-qr code suffers a
slowdown when using custom colors combined with custom module drawers.
By bypassing square QRs, non standard colors will load faster."""
if module_drawer_string == "Gapped square":
return GappedSquareModuleDrawer()
if module_drawer_string == "Circle":
return CircleModuleDrawer()
if module_drawer_string == "Rounded":
return RoundedModuleDrawer()
if module_drawer_string == "Vertical bars":
return VerticalBarsDrawer()
if module_drawer_string == "Horizontal bars":
return HorizontalBarsDrawer()
raise ValueError(f"Module drawing method of {module_drawer_string} "
f"not supported")
def update_text(self, protocol, text): def update_text(self, protocol, text):
"""This function takes input from a text box and a chosen internet """This function takes input from a text box and a chosen internet
@@ -79,12 +114,34 @@ class QRByImageSize(QRBase):
"required": { "required": {
"protocol": (["Http", "Https", "None"], {"default": "Https"}), "protocol": (["Http", "Https", "None"], {"default": "Https"}),
"text": ("STRING", {"multiline": True}), "text": ("STRING", {"multiline": True}),
"image_size": ("INT", {"default": 512, "min": 64, "max": 4096, "step": 64}), "image_size": ("INT", {"default": 512,
"fill_hexcolor": ("STRING", {"multiline": False, "default": "#000000"}), "min": 64,
"back_hexcolor": ("STRING", {"multiline": False, "default": "#FFFFFF"}), "max": 4096,
"error_correction": (["Low", "Medium", "Quartile", "High"], {"default": "High"}), "step": 64}),
"border": ("INT", {"default": 1, "min": 0, "max": 100, "step": 1}), "fill_hexcolor": ("STRING", {"multiline": False,
"resampling": (["Bicubic", "Bilinear", "Box", "Hamming", "Lanczos", "Nearest"], {"default": "Nearest"}), "default": "#000000"}),
"back_hexcolor": ("STRING", {"multiline": False,
"default": "#FFFFFF"}),
"error_correction": (["Low", "Medium", "Quartile", "High"],
{"default": "High"}),
"border": ("INT", {"default": 1,
"min": 0,
"max": 100,
"step": 1}),
"resampling": (["Bicubic",
"Bilinear",
"Box",
"Hamming",
"Lanczos",
"Nearest"
], {"default": "Nearest"}),
"module_drawer": (["Square",
"Gapped square",
"Circle",
"Rounded",
"Vertical bars",
"Horizontal bars"
], {"default": "Square"})
}, },
} }
@@ -104,7 +161,8 @@ class QRByImageSize(QRBase):
return Image.BOX return Image.BOX
if resampling_string == "Hamming": if resampling_string == "Hamming":
return Image.HAMMING return Image.HAMMING
raise ValueError(f"Resampling method of {resampling_string} not supported") raise ValueError(f"Resampling method of {resampling_string} not "
f"supported")
def generate_qr( def generate_qr(
self, self,
@@ -115,18 +173,18 @@ class QRByImageSize(QRBase):
back_hexcolor, back_hexcolor,
error_correction, error_correction,
border, border,
resampling resampling,
module_drawer
): ):
resampling_method = self._select_resampling_method(resampling) resampling_method = self._select_resampling_method(resampling)
error_level = self._get_error_correction_constant(error_correction) error_level = self._get_error_correction_constant(error_correction)
self.update_text(protocol, text) self.update_text(protocol, text)
qr = qrcode.QRCode( qr = qrcode.QRCode(
error_correction=error_level, error_correction=error_level,
box_size=1, box_size=16,
border=border) border=border)
qr.add_data(self.text) qr.add_data(self.text)
img = self._make_qr(qr, fill_hexcolor, back_hexcolor) img = self._make_qr(qr, fill_hexcolor, back_hexcolor, module_drawer)
self._validate_qr_size(img.pixel_size, image_size)
img = img.resize((image_size, image_size), resample=resampling_method) img = img.resize((image_size, image_size), resample=resampling_method)
return (self._img_to_tensor(img), qr.version) return (self._img_to_tensor(img), qr.version)
@@ -139,12 +197,31 @@ class QRByModuleSize(QRBase):
"required": { "required": {
"protocol": (["Http", "Https", "None"], {"default": "Https"}), "protocol": (["Http", "Https", "None"], {"default": "Https"}),
"text": ("STRING", {"multiline": True}), "text": ("STRING", {"multiline": True}),
"module_size": ("INT", {"default": 16, "min": 1, "max": 64, "step": 1}), "module_size": ("INT", {"default": 16,
"max_image_size": ("INT", {"default": 512, "min": 64, "max": 4096, "step": 64}), "min": 1,
"fill_hexcolor": ("STRING", {"multiline": False, "default": "#000000"}), "max": 64,
"back_hexcolor": ("STRING", {"multiline": False, "default": "#FFFFFF"}), "step": 1}),
"error_correction": (["Low", "Medium", "Quartile", "High"], {"default": "High"}), "max_image_size": ("INT", {"default": 512,
"border": ("INT", {"default": 1, "min": 0, "max": 100, "step": 1}), "min": 64,
"max": 4096,
"step": 64}),
"fill_hexcolor": ("STRING", {"multiline": False,
"default": "#000000"}),
"back_hexcolor": ("STRING", {"multiline": False,
"default": "#FFFFFF"}),
"error_correction": (["Low", "Medium", "Quartile", "High"],
{"default": "High"}),
"border": ("INT", {"default": 1,
"min": 0,
"max": 100,
"step": 1}),
"module_drawer": (["Square",
"Gapped square",
"Circle",
"Rounded",
"Vertical bars",
"Horizontal bars"
], {"default": "Square"})
}, },
} }
@@ -160,7 +237,8 @@ class QRByModuleSize(QRBase):
fill_hexcolor, fill_hexcolor,
back_hexcolor, back_hexcolor,
error_correction, error_correction,
border border,
module_drawer
): ):
self.update_text(protocol, text) self.update_text(protocol, text)
error_level = self._get_error_correction_constant(error_correction) error_level = self._get_error_correction_constant(error_correction)
@@ -169,7 +247,7 @@ class QRByModuleSize(QRBase):
box_size=module_size, box_size=module_size,
border=border) border=border)
qr.add_data(self.text) qr.add_data(self.text)
img = self._make_qr(qr, fill_hexcolor, back_hexcolor) img = self._make_qr(qr, fill_hexcolor, back_hexcolor, module_drawer)
self._validate_qr_size(img.pixel_size, max_image_size) self._validate_qr_size(img.pixel_size, max_image_size)
return (self._img_to_tensor(img), qr.version, img.pixel_size) return (self._img_to_tensor(img), qr.version, img.pixel_size)
@@ -182,17 +260,41 @@ class QRByModuleSizeSplitFunctionPatterns(QRBase):
"required": { "required": {
"protocol": (["Http", "Https", "None"], {"default": "Https"}), "protocol": (["Http", "Https", "None"], {"default": "Https"}),
"text": ("STRING", {"multiline": True}), "text": ("STRING", {"multiline": True}),
"module_size": ("INT", {"default": 16, "min": 1, "max": 64, "step": 1}), "module_size": ("INT", {"default": 16,
"max_image_size": ("INT", {"default": 512, "min": 64, "max": 4096, "step": 64}), "min": 1,
"fill_hexcolor": ("STRING", {"multiline": False, "default": "#000000"}), "max": 64,
"back_hexcolor": ("STRING", {"multiline": False, "default": "#FFFFFF"}), "step": 1}),
"error_correction": (["Low", "Medium", "Quartile", "High"], {"default": "High"}), "max_image_size": ("INT", {"default": 512,
"border": ("INT", {"default": 1, "min": 0, "max": 100, "step": 1}), "min": 64,
"max": 4096,
"step": 64}),
"fill_hexcolor": ("STRING", {"multiline": False,
"default": "#000000"}),
"back_hexcolor": ("STRING", {"multiline": False,
"default": "#FFFFFF"}),
"error_correction": (["Low", "Medium", "Quartile", "High"],
{"default": "High"}),
"border": ("INT", {"default": 1,
"min": 0,
"max": 100,
"step": 1}),
"module_drawer": (["Square",
"Gapped square",
"Circle",
"Rounded",
"Vertical bars",
"Horizontal bars"
], {"default": "Square"})
}, },
} }
RETURN_TYPES = ("IMAGE", "IMAGE", "IMAGE", "MASK", "INT", "INT") RETURN_TYPES = ("IMAGE", "IMAGE", "IMAGE", "MASK", "INT", "INT")
RETURN_NAMES = ("QR_CODE", "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): def _generate_finder_pattern_ranges(self, module_size, border_size):
outer = module_size * border_size outer = module_size * border_size
@@ -205,9 +307,16 @@ class QRByModuleSizeSplitFunctionPatterns(QRBase):
(-inner, far_outer, outer, inner) (-inner, far_outer, outer, inner)
] ]
def _generate_finder_pattern_mask(self, pixel_size, module_size, border_size): def _generate_finder_pattern_mask(self,
pixel_size,
module_size,
border_size):
mask = np.zeros((pixel_size, pixel_size), dtype=bool) mask = np.zeros((pixel_size, pixel_size), dtype=bool)
for x_min, x_max, y_min, y_max in self._generate_finder_pattern_ranges(module_size, border_size): for (x_min,
x_max,
y_min,
y_max) in self._generate_finder_pattern_ranges(module_size,
border_size):
mask[y_min:y_max, x_min:x_max] = True mask[y_min:y_max, x_min:x_max] = True
return mask return mask
@@ -219,7 +328,7 @@ class QRByModuleSizeSplitFunctionPatterns(QRBase):
def _mask_to_tensor(self, mask): def _mask_to_tensor(self, mask):
out_image = mask.astype(np.float32) out_image = mask.astype(np.float32)
return torch.from_numpy(out_image) return torch.from_numpy(out_image).unsqueeze(0)
def generate_qr( def generate_qr(
self, self,
@@ -230,7 +339,8 @@ class QRByModuleSizeSplitFunctionPatterns(QRBase):
fill_hexcolor, fill_hexcolor,
back_hexcolor, back_hexcolor,
error_correction, error_correction,
border border,
module_drawer
): ):
self.update_text(protocol, text) self.update_text(protocol, text)
error_level = self._get_error_correction_constant(error_correction) error_level = self._get_error_correction_constant(error_correction)
@@ -239,10 +349,12 @@ class QRByModuleSizeSplitFunctionPatterns(QRBase):
box_size=module_size, box_size=module_size,
border=border) border=border)
qr.add_data(self.text) qr.add_data(self.text)
img = self._make_qr(qr, fill_hexcolor, back_hexcolor) img = self._make_qr(qr, fill_hexcolor, back_hexcolor, module_drawer)
pixel_size = img.pixel_size pixel_size = img.pixel_size
self._validate_qr_size(pixel_size, max_image_size) self._validate_qr_size(pixel_size, max_image_size)
mask = self._generate_finder_pattern_mask(pixel_size, module_size, border) mask = self._generate_finder_pattern_mask(pixel_size,
module_size,
border)
module_image = self._apply_fill_to_mask(img, mask) module_image = self._apply_fill_to_mask(img, mask)
function_image = self._apply_fill_to_mask(img, ~mask) function_image = self._apply_fill_to_mask(img, ~mask)
return ( return (
@@ -265,6 +377,7 @@ class QRErrorMasker:
CATEGORY = "ComfyQR" CATEGORY = "ComfyQR"
RETURN_TYPES = ("MASK", "FLOAT", "FLOAT", "FLOAT") RETURN_TYPES = ("MASK", "FLOAT", "FLOAT", "FLOAT")
RETURN_NAMES = ("QR_ERROR_MASK", "PERCENT_ERROR", "CORRELATION", "RMSE") RETURN_NAMES = ("QR_ERROR_MASK", "PERCENT_ERROR", "CORRELATION", "RMSE")
OUTPUT_IS_LIST = (False, True, True, True)
@classmethod @classmethod
def INPUT_TYPES(cls): def INPUT_TYPES(cls):
@@ -272,13 +385,24 @@ class QRErrorMasker:
"required": { "required": {
"source_qr": ("IMAGE",), "source_qr": ("IMAGE",),
"modified_qr": ("IMAGE",), "modified_qr": ("IMAGE",),
"module_size": ("INT", {"default": 16, "min": 1, "max": 64, "step": 1}), "module_size": ("INT", {"default": 16,
"grayscale_method": (["mean", "luminance"], {"default": "luminance"}), "min": 1,
"max": 64,
"step": 1}),
"grayscale_method": (["mean", "luminance"],
{"default": "luminance"}),
"aggregate_method": (["mean",], {"default": "mean"}), "aggregate_method": (["mean",], {"default": "mean"}),
"evaluate": (["full_qr", "module_pattern", "finder_pattern"], {"default": "module_pattern"}), "evaluate": (["full_qr", "module_pattern", "finder_pattern"],
"error_difficulty": ("FLOAT", {"default": 0, "min": 0, "max": 1, "step": .01}), {"default": "module_pattern"}),
"error_difficulty": ("FLOAT", {"default": 0,
"min": 0,
"max": 1,
"step": .01}),
"inverted_pattern": ("BOOLEAN", {"default": False}), "inverted_pattern": ("BOOLEAN", {"default": False}),
"gamma": ("FLOAT", {"default": 2.2, "min": .1, "max": 2.8, "step": .1}), "gamma": ("FLOAT", {"default": 2.2,
"min": .1,
"max": 2.8,
"step": .1}),
}, },
} }
@@ -288,10 +412,13 @@ class QRErrorMasker:
indices = torch.nonzero(module_pixels, as_tuple=True) indices = torch.nonzero(module_pixels, as_tuple=True)
# The viewer patterns will guarentee a module pixel in the upper left # The viewer patterns will guarentee a module pixel in the upper left
# The bottom right does not have that guarentee so max is used. # 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 return (indices[0][0],
indices[0].max() + 1,
indices[1][0], indices[1].max() + 1)
def _extract_pattern_from_bounds(self, tensor): 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]] 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): def _trim_to_qr_area(self, source_qr, modified_qr, inverted_pattern):
self.qr_bounds = self._get_qr_bounds(source_qr, inverted_pattern) self.qr_bounds = self._get_qr_bounds(source_qr, inverted_pattern)
@@ -302,20 +429,35 @@ class QRErrorMasker:
def _reshape_tensor_to_modules(self, tensor): def _reshape_tensor_to_modules(self, tensor):
if len(tensor.shape) != 2: if len(tensor.shape) != 2:
raise RuntimeError("Module reshaping requires a 2 dimensional array.") raise RuntimeError("Module reshaping requires a 2 dimensional "
"array.")
length = tensor.shape[0] // self.module_size length = tensor.shape[0] // self.module_size
reshaped_tensor = tensor.view(length, self.module_size, length, 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() rehaped_tensor = reshaped_tensor.permute(0, 2, 1, 3).contiguous()
return rehaped_tensor.view(length, length, self.module_size ** 2) return rehaped_tensor.view(length, length, self.module_size ** 2)
def _check_bounds_and_module_size(self): def _check_bounds_and_module_size(self):
height = self.qr_bounds[1] - self.qr_bounds[0] height = self.qr_bounds[1] - self.qr_bounds[0]
width = self.qr_bounds[3] - self.qr_bounds[2] 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." 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: if width != height:
raise RuntimeError(f"Source QR dimensions are {width} x {height}. They must be a perfect square. {color_warning}") raise RuntimeError(f"Source QR dimensions are {width} x {height}. "
f"They must be a perfect square. "
f"{color_warning}")
if width % self.module_size: 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}") raise RuntimeError(f"QR width of {width} does not fit module_size "
f"of {self.module_size}. It must be perfectly "
f"divisible. {color_warning}")
def _check_equal_shape(self, source_qr, modified_qr):
if source_qr.shape != modified_qr.shape:
raise ValueError("Source and modified QR must have the same batch "
"size and dimensions.")
def _squeeze_by_mean(self, tensor): def _squeeze_by_mean(self, tensor):
return torch.mean(tensor, dim=-1) return torch.mean(tensor, dim=-1)
@@ -324,14 +466,18 @@ class QRErrorMasker:
if gamma == 1: if gamma == 1:
return tensor return tensor
if gamma == 2.2: if gamma == 2.2:
return torch.where(tensor <= 0.04045, tensor / 12.92, ((tensor + 0.055) / 1.055) ** 2.4) return torch.where(tensor <= 0.04045,
tensor / 12.92,
(tensor + 0.055) / 1.055) ** 2.4
return tensor ** gamma return tensor ** gamma
def _gamma_compression(self, tensor, gamma): def _gamma_compression(self, tensor, gamma):
if gamma == 1: if gamma == 1:
return tensor return tensor
if gamma == 2.2: if gamma == 2.2:
return torch.where(tensor <= .0031308, tensor * 12.92, 1.055 * tensor ** (1/2.4) - 0.055) return torch.where(tensor <= .0031308,
tensor * 12.92,
1.055 * tensor ** (1/2.4) - 0.055)
return tensor ** (1/gamma) return tensor ** (1/gamma)
def _grayscale_by_luminance(self, tensor, gamma): def _grayscale_by_luminance(self, tensor, gamma):
@@ -346,7 +492,8 @@ class QRErrorMasker:
tensor = self._reshape_tensor_to_modules(tensor) tensor = self._reshape_tensor_to_modules(tensor)
if method == "mean": if method == "mean":
return self._squeeze_by_mean(tensor) return self._squeeze_by_mean(tensor)
raise RuntimeError("Module aggregation currently only supports the mean.") raise RuntimeError("Module aggregation currently only supports the "
"mean.")
def _reduce_to_modules( def _reduce_to_modules(
self, self,
@@ -358,12 +505,8 @@ class QRErrorMasker:
inverted_pattern, inverted_pattern,
gamma gamma
): ):
if source_qr.shape != modified_qr.shape:
raise ValueError("Source and modified QR must have the same dimensions.")
self.module_size = module_size self.module_size = module_size
self.canvas_shape = (source_qr.shape[1], source_qr.shape[2]) self.canvas_shape = (source_qr.shape[0], source_qr.shape[1])
# Ignore batch dimension
source_qr, modified_qr = source_qr[0], modified_qr[0]
# Processed first for simplified indexing of QR bounds. # Processed first for simplified indexing of QR bounds.
source_qr = self._squeeze_by_mean(source_qr) source_qr = self._squeeze_by_mean(source_qr)
source_qr, modified_qr = self._trim_to_qr_area(source_qr, source_qr, modified_qr = self._trim_to_qr_area(source_qr,
@@ -375,13 +518,14 @@ class QRErrorMasker:
elif grayscale_method == "luminance": elif grayscale_method == "luminance":
modified_qr = self._grayscale_by_luminance(modified_qr, gamma) modified_qr = self._grayscale_by_luminance(modified_qr, gamma)
else: else:
raise ValueError("Currently only mean is supported for rgb to grayscale conversion.") raise ValueError("Currently only mean is supported for rgb to "
source_qr = self._squeeze_to_modules(source_qr, "mean") "grayscale conversion.")
source_qr = torch.round(self._squeeze_to_modules(source_qr, "mean"))
modified_qr = self._squeeze_to_modules(modified_qr, aggregate_method) modified_qr = self._squeeze_to_modules(modified_qr, aggregate_method)
return source_qr, modified_qr return source_qr, modified_qr
def _create_finder_pattern_mask(self, width, inverted): def _create_finder_pattern_mask(self, width, inverted):
mask = np.zeros((width, width), dtype=bool) mask = torch.zeros((width, width), dtype=torch.bool)
# When borders are trimmed and QR code has module size of 1, results # When borders are trimmed and QR code has module size of 1, results
# are consistent. # are consistent.
finder_coords = [[0, 7, 0, 7], [0, 7, -7, None], [-7, None, 0, 7]] finder_coords = [[0, 7, 0, 7], [0, 7, -7, None], [-7, None, 0, 7]]
@@ -411,7 +555,9 @@ class QRErrorMasker:
def _replace_qr_to_canvas(self, tensor): def _replace_qr_to_canvas(self, tensor):
length = tensor.shape[0] * self.module_size length = tensor.shape[0] * self.module_size
bounds = self.qr_bounds bounds = self.qr_bounds
tensor = F.interpolate(tensor.unsqueeze(0).unsqueeze(0), size=(length, length), mode='nearest') tensor = F.interpolate(tensor.unsqueeze(0).unsqueeze(0),
size=(length, length),
mode='nearest')
canvas = torch.zeros(self.canvas_shape, dtype=torch.float32) canvas = torch.zeros(self.canvas_shape, dtype=torch.float32)
canvas[bounds[0]:bounds[1], bounds[2]:bounds[3]] = tensor.squeeze() canvas[bounds[0]:bounds[1], bounds[2]:bounds[3]] = tensor.squeeze()
return canvas return canvas
@@ -423,12 +569,14 @@ class QRErrorMasker:
mask, mask,
error_difficulty error_difficulty
): ):
modified_qr = self._bin_tensor_to_threshold(modified_qr, error_difficulty) modified_qr = self._bin_tensor_to_threshold(modified_qr,
error_difficulty)
error = source_qr != modified_qr error = source_qr != modified_qr
percent_error = error[mask].sum().item() / error[mask].numel() percent_error = error[mask].sum().item() / error[mask].numel()
if mask is not None: if mask is not None:
error[~mask] = False error[~mask] = False
return self._replace_qr_to_canvas((error).to(torch.float32)), percent_error return (self._replace_qr_to_canvas((error).to(torch.float32)),
percent_error)
def _qr_correlation(self, source_qr, modified_qr, mask): def _qr_correlation(self, source_qr, modified_qr, mask):
source_qr = source_qr[mask].numpy().reshape((-1)) source_qr = source_qr[mask].numpy().reshape((-1))
@@ -451,36 +599,47 @@ class QRErrorMasker:
inverted_pattern, inverted_pattern,
gamma, gamma,
): ):
source_qr, modified_qr = self._reduce_to_modules(source_qr, self._check_equal_shape(source_qr, modified_qr)
modified_qr, error_masks, error_percents, correlations, rmses = [], [], [], []
module_size, for i in range(source_qr.shape[0]):
grayscale_method, qr_s, qr_m = source_qr[i], modified_qr[i]
aggregate_method, qr_s, qr_m = self._reduce_to_modules(qr_s,
inverted_pattern, qr_m,
gamma module_size,
) grayscale_method,
mask = self._create_qr_mask(source_qr.shape[0], evaluate) aggregate_method,
error_mask, percent_error = self._compare_modules(source_qr, inverted_pattern,
modified_qr, gamma
mask, )
error_difficulty mask = self._create_qr_mask(qr_s.shape[0], evaluate)
) error_mask, percent_error = self._compare_modules(qr_s,
correlation = self._qr_correlation(source_qr, modified_qr, mask) qr_m,
rmse = self._qr_rmse(source_qr, modified_qr, mask) mask,
return (error_mask, percent_error, correlation, rmse) error_difficulty
)
correlation = self._qr_correlation(qr_s, qr_m, mask)
rmse = self._qr_rmse(qr_s, qr_m, mask)
error_masks.append(error_mask)
error_percents.append(percent_error)
correlations.append(correlation)
rmses.append(rmse)
error_masks = torch.stack(error_masks, dim=0)
return (error_masks, error_percents, correlations, rmses)
NODE_CLASS_MAPPINGS = { NODE_CLASS_MAPPINGS = {
"comfy-qr-by-module-size": QRByModuleSize, "comfy-qr-by-module-size": QRByModuleSize,
"comfy-qr-by-image-size": QRByImageSize, "comfy-qr-by-image-size": QRByImageSize,
"comfy-qr-by-module-split": QRByModuleSizeSplitFunctionPatterns, "comfy-qr-by-module-split":
QRByModuleSizeSplitFunctionPatterns,
"comfy-qr-mask_errors": QRErrorMasker, "comfy-qr-mask_errors": QRErrorMasker,
} }
NODE_DISPLAY_NAME_MAPPINGS = { NODE_DISPLAY_NAME_MAPPINGS = {
"comfy-qr-by-module-size": "QR Code", "comfy-qr-by-module-size": "QR Code",
"comfy-qr-by-image-size": "QR Code (Conformed to Image Size)", "comfy-qr-by-image-size": "QR Code (Conformed "
"to Image Size)",
"comfy-qr-by-module-split": "QR Code (Split)", "comfy-qr-by-module-split": "QR Code (Split)",
"comfy-qr-mask_errors": "Mask QR Errors", "comfy-qr-mask_errors": "Mask QR Errors",
} }
+2 -1
View File
@@ -1 +1,2 @@
qrcode >= 7.1 qrcode >= 7.1
pillow >= 9.0