From fafaa60f372adbd65a2cea7c93a626d5e0c2d7f8 Mon Sep 17 00:00:00 2001 From: Maoxie Date: Fri, 19 Apr 2024 18:41:07 +0800 Subject: [PATCH 01/95] when selected, display links between GetNodes & SetNode --- web/js/setgetnodes.js | 83 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 82 insertions(+), 1 deletion(-) diff --git a/web/js/setgetnodes.js b/web/js/setgetnodes.js index d3035f7..cb7f6d3 100644 --- a/web/js/setgetnodes.js +++ b/web/js/setgetnodes.js @@ -41,6 +41,7 @@ app.registerExtension({ class SetNode { defaultVisibility = true; serialize_widgets = true; + drawConnection = false; constructor() { if (!this.properties) { this.properties = { @@ -213,6 +214,49 @@ app.registerExtension({ } }) } + onSelected() { + this.drawConnection = true; + } + onDeselected() { + this.drawConnection = false; + } + onDrawForeground(ctx, lGraphCanvas) { + if (this.drawConnection) { + this._drawVirtualLinks(lGraphCanvas, ctx); + } + } + onDrawCollapsed(ctx, lGraphCanvas) { + if (this.drawConnection) { + this._drawVirtualLinks(lGraphCanvas, ctx); + } + } + _drawVirtualLinks(lGraphCanvas, ctx) { + const getters = this.findGetters(this.graph); + if (!getters?.length) return; + // draw the virtual connection from SetNode to GetNode + let start_node_slotpos = [ + this.size[0], + LiteGraph.NODE_TITLE_HEIGHT * 0.5, + ]; + for (const getter of getters) { + let end_node_slotpos = this.getConnectionPos(false, 0); + end_node_slotpos = [ + getter.pos[0] - end_node_slotpos[0] + this.size[0], + getter.pos[1] - end_node_slotpos[1], + ]; + lGraphCanvas.renderLink( + ctx, + start_node_slotpos, + end_node_slotpos, + null, + false, + null, + "#FFF", + LiteGraph.RIGHT, + LiteGraph.LEFT + ); + } + } } LiteGraph.registerNodeType( @@ -233,6 +277,7 @@ app.registerExtension({ defaultVisibility = true; serialize_widgets = true; + drawConnection = false; constructor() { if (!this.properties) { @@ -266,7 +311,7 @@ app.registerExtension({ ) { this.validateLinks(); } - + this.setName = function(name) { node.widgets[0].value = name; node.onRename(); @@ -337,6 +382,42 @@ app.registerExtension({ } onAdded(graph) { } + onSelected() { + this.drawConnection = true; + } + onDeselected() { + this.drawConnection = false; + } + onDrawForeground(ctx, lGraphCanvas) { + if (this.drawConnection) { + this._drawVirtualLink(lGraphCanvas, ctx); + } + } + onDrawCollapsed(ctx, lGraphCanvas) { + if (this.drawConnection) { + this._drawVirtualLink(lGraphCanvas, ctx); + } + } + _drawVirtualLink(lGraphCanvas, ctx) { + const setter = this.findSetter(this.graph); + if (!setter) return; + // draw the virtual connection from SetNode to GetNode + let start_node_slotpos = setter.getConnectionPos(false, 0); + start_node_slotpos = [ + start_node_slotpos[0] - this.pos[0], + start_node_slotpos[1] - this.pos[1], + ]; + let end_node_slotpos = [0, -LiteGraph.NODE_TITLE_HEIGHT * 0.5]; + lGraphCanvas.renderLink( + ctx, + start_node_slotpos, + end_node_slotpos, + null, + false, + null, + "#FFF" + ); + } } LiteGraph.registerNodeType( From 53959a053592fdf5c3edc4b9335b94859c49741d Mon Sep 17 00:00:00 2001 From: kijai <40791699+kijai@users.noreply.github.com> Date: Fri, 19 Apr 2024 22:59:06 +0300 Subject: [PATCH 02/95] Update camera pose visualizer --- nodes.py | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/nodes.py b/nodes.py index 863bcdd..44ebbf3 100644 --- a/nodes.py +++ b/nodes.py @@ -4319,6 +4319,9 @@ class CameraPoseVisualizer: "z_max": ("FLOAT", {"default": 5.0,"min": -100, "max": 100, "step": 0.01}), "use_viewer": ("BOOLEAN", {"default": False}), }, + "optional": { + "cameractrl_poses": ("CAMERACTRL_POSES", {"default": None}), + } } RETURN_TYPES = ("IMAGE",) @@ -4329,7 +4332,7 @@ Visualizes the camera poses from a .txt file with RealEstate camera intrinsics and coordinates in a 3D plot. """ - def plot(self, pose_file_path, sample_stride, frames, base_xval, zval, use_exact_fx, relative_c2w, x_min, x_max, y_min, y_max, z_min, z_max, use_viewer): + def plot(self, pose_file_path, sample_stride, frames, base_xval, zval, use_exact_fx, relative_c2w, x_min, x_max, y_min, y_max, z_min, z_max, use_viewer, cameractrl_poses=None): import matplotlib as mpl import matplotlib.pyplot as plt import io @@ -4345,10 +4348,18 @@ RealEstate camera intrinsics and coordinates in a 3D plot. self.ax.set_ylabel('y') self.ax.set_zlabel('z') print('initialize camera pose visualizer') - with open(pose_file_path, 'r') as f: - poses = f.readlines() - w2cs = [np.asarray([float(p) for p in pose.strip().split(' ')[7:]]).reshape(3, 4) for pose in poses[1:]] - fxs = [float(pose.strip().split(' ')[1]) for pose in poses[1:]] + if pose_file_path != "": + with open(pose_file_path, 'r') as f: + poses = f.readlines() + w2cs = [np.asarray([float(p) for p in pose.strip().split(' ')[7:]]).reshape(3, 4) for pose in poses[1:]] + fxs = [float(pose.strip().split(' ')[1]) for pose in poses[1:]] + print(poses) + elif cameractrl_poses is not None: + poses = cameractrl_poses + w2cs = [np.array(pose[7:]).reshape(3, 4) for pose in cameractrl_poses] + fxs = [pose[1] for pose in cameractrl_poses] + else: + raise ValueError("Please provide either pose_file_path or cameractrl_poses") cropped_length = frames * sample_stride total_frames = len(w2cs) From 645949608eaebe6b8dd7a236c05888334290e262 Mon Sep 17 00:00:00 2001 From: kijai <40791699+kijai@users.noreply.github.com> Date: Sat, 20 Apr 2024 11:57:36 +0300 Subject: [PATCH 03/95] Add custom sigmas node --- nodes.py | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/nodes.py b/nodes.py index 44ebbf3..efb822a 100644 --- a/nodes.py +++ b/nodes.py @@ -3123,7 +3123,26 @@ class FlipSigmasAdjusted: array_string = np.array2string(sigma_np_array, precision=2, separator=', ', threshold=np.inf) adjusted_sigmas = adjusted_sigmas / divide_by return (adjusted_sigmas, array_string,) - + +class CustomSigmas: + @classmethod + def INPUT_TYPES(s): + return {"required": + { + "sigmas_string" :("STRING", {"default": "14.615, 6.475, 3.861, 2.697, 1.886, 1.396, 0.963, 0.652, 0.399, 0.152, 0.029","multiline": True}), + } + } + RETURN_TYPES = ("SIGMAS",) + RETURN_NAMES = ("SIGMAS",) + CATEGORY = "KJNodes/noise" + FUNCTION = "customsigmas" + + def customsigmas(self, sigmas_string): + sigmas_list = sigmas_string.split(', ') + sigmas_float_list = [float(sigma) for sigma in sigmas_list] + sigmas_tensor = torch.tensor(sigmas_float_list) + + return (sigmas_tensor,) class InjectNoiseToLatent: @classmethod @@ -4956,7 +4975,8 @@ NODE_CLASS_MAPPINGS = { "ImageAndMaskPreview": ImageAndMaskPreview, "StabilityAPI_SD3": StabilityAPI_SD3, "MaskOrImageToWeight": MaskOrImageToWeight, - "FloatToMask": FloatToMask + "FloatToMask": FloatToMask, + "CustomSigmas": CustomSigmas } NODE_DISPLAY_NAME_MAPPINGS = { "INTConstant": "INT Constant", @@ -5041,4 +5061,5 @@ NODE_DISPLAY_NAME_MAPPINGS = { "StabilityAPI_SD3": "Stability API SD3", "MaskOrImageToWeight": "Mask Or Image To Weight", "FloatToMask": "Float To Mask", + "CustomSigmas": "Custom Sigmas", } \ No newline at end of file From f8888f5ea6d1f5c9750e7b9df8b4487fcf39d521 Mon Sep 17 00:00:00 2001 From: kijai <40791699+kijai@users.noreply.github.com> Date: Sat, 20 Apr 2024 12:02:39 +0300 Subject: [PATCH 04/95] Update nodes.py --- nodes.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/nodes.py b/nodes.py index efb822a..f553057 100644 --- a/nodes.py +++ b/nodes.py @@ -3136,7 +3136,17 @@ class CustomSigmas: RETURN_NAMES = ("SIGMAS",) CATEGORY = "KJNodes/noise" FUNCTION = "customsigmas" - + DESCRIPTION = """ +Creates a sigmas tensor from a string of comma separated values. +Examples: + +Nvidia's optimized AYS 10 step schedule for SD 1.5: +14.615, 6.475, 3.861, 2.697, 1.886, 1.396, 0.963, 0.652, 0.399, 0.152, 0.029 +SDXL: +14.615, 6.315, 3.771, 2.181, 1.342, 0.862, 0.555, 0.380, 0.234, 0.113, 0.029 +SVD: +700.00, 54.5, 15.886, 7.977, 4.248, 1.789, 0.981, 0.403, 0.173, 0.034, 0.002 +""" def customsigmas(self, sigmas_string): sigmas_list = sigmas_string.split(', ') sigmas_float_list = [float(sigma) for sigma in sigmas_list] From aebdb88df87eeccacb56557a1e7580396a2e7780 Mon Sep 17 00:00:00 2001 From: kijai <40791699+kijai@users.noreply.github.com> Date: Sat, 20 Apr 2024 12:33:51 +0300 Subject: [PATCH 05/95] Update nodes.py --- nodes.py | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/nodes.py b/nodes.py index f553057..ea5f015 100644 --- a/nodes.py +++ b/nodes.py @@ -3130,6 +3130,7 @@ class CustomSigmas: return {"required": { "sigmas_string" :("STRING", {"default": "14.615, 6.475, 3.861, 2.697, 1.886, 1.396, 0.963, 0.652, 0.399, 0.152, 0.029","multiline": True}), + "interpolate_to_steps": ("INT", {"default": 10,"min": 0, "max": 255, "step": 1}), } } RETURN_TYPES = ("SIGMAS",) @@ -3147,12 +3148,30 @@ SDXL: SVD: 700.00, 54.5, 15.886, 7.977, 4.248, 1.789, 0.981, 0.403, 0.173, 0.034, 0.002 """ - def customsigmas(self, sigmas_string): + def customsigmas(self, sigmas_string, interpolate_to_steps): sigmas_list = sigmas_string.split(', ') sigmas_float_list = [float(sigma) for sigma in sigmas_list] sigmas_tensor = torch.tensor(sigmas_float_list) + if len(sigmas_tensor) < interpolate_to_steps: + sigmas_tensor = self.loglinear_interp(sigmas_tensor, interpolate_to_steps) + return (sigmas_tensor,) + + def loglinear_interp(self, t_steps, num_steps): + """ + Performs log-linear interpolation of a given array of decreasing numbers. + """ + t_steps_np = t_steps.numpy() + + xs = np.linspace(0, 1, len(t_steps_np)) + ys = np.log(t_steps_np[::-1]) - return (sigmas_tensor,) + new_xs = np.linspace(0, 1, num_steps) + new_ys = np.interp(new_xs, xs, ys) + + interped_ys = np.exp(new_ys)[::-1].copy() + interped_ys_tensor = torch.tensor(interped_ys) + return interped_ys_tensor + class InjectNoiseToLatent: @classmethod From 6d24bf322f18376469725ca88620606869ef0f37 Mon Sep 17 00:00:00 2001 From: kijai <40791699+kijai@users.noreply.github.com> Date: Sat, 20 Apr 2024 16:38:08 +0300 Subject: [PATCH 06/95] Update nodes.py --- nodes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nodes.py b/nodes.py index ea5f015..3f38913 100644 --- a/nodes.py +++ b/nodes.py @@ -3152,7 +3152,7 @@ SVD: sigmas_list = sigmas_string.split(', ') sigmas_float_list = [float(sigma) for sigma in sigmas_list] sigmas_tensor = torch.tensor(sigmas_float_list) - if len(sigmas_tensor) < interpolate_to_steps: + if len(sigmas_tensor) != interpolate_to_steps: sigmas_tensor = self.loglinear_interp(sigmas_tensor, interpolate_to_steps) return (sigmas_tensor,) From 1959a4de55cc6644484648fdbbddbf2bbd8f79ca Mon Sep 17 00:00:00 2001 From: kijai <40791699+kijai@users.noreply.github.com> Date: Sat, 20 Apr 2024 21:22:44 +0300 Subject: [PATCH 07/95] Squashed commit of the following: commit d5e37ff797ecfe975bed982f25cbfbfd1e60c7ee Author: kijai <40791699+kijai@users.noreply.github.com> Date: Sat Apr 20 21:22:29 2024 +0300 Update spline_editor.js commit bdade789ee3650cbd2b42bb844b16e175267575d Author: kijai <40791699+kijai@users.noreply.github.com> Date: Sat Apr 20 21:21:34 2024 +0300 spline editor fixes commit 1aef2b8e43397eddc49dca304d59bd7a6674f55d Author: kijai <40791699+kijai@users.noreply.github.com> Date: Sat Apr 20 18:20:02 2024 +0300 spline editor updates commit cb1e98abf1d9fcacc9b4912971df175c5a8ea9ac Author: kijai <40791699+kijai@users.noreply.github.com> Date: Sat Apr 20 11:27:50 2024 +0300 Update spline_editor.js commit 891daeb4389318e9880f1ff68f53eba068f46739 Merge: d9712b0 6e1fa8d Author: kijai <40791699+kijai@users.noreply.github.com> Date: Fri Apr 19 18:40:44 2024 +0300 Merge branch 'main' into develop commit d9712b0e04c70c6299fa61ce5c128ba56228ea11 Author: kijai <40791699+kijai@users.noreply.github.com> Date: Thu Apr 18 01:45:32 2024 +0300 spline editor work commit 92711dc7625da578cf59520a41462a845069ac56 Author: Kijai <40791699+kijai@users.noreply.github.com> Date: Wed Apr 17 19:43:49 2024 +0300 Update spline_editor.js commit 6f256423afa6d3e8a0ca2add461328d9f9b4ce82 Author: Kijai <40791699+kijai@users.noreply.github.com> Date: Wed Apr 17 19:21:32 2024 +0300 Update spline_editor.js commit 47c23d5a19c84d22d94668f5b5ba2ceab5b94988 Author: Kijai <40791699+kijai@users.noreply.github.com> Date: Tue Apr 16 19:19:16 2024 +0300 reworking spline editor (not functional yet) --- nodes.py | 11 +- web/js/jsnodes.js | 3 - web/js/spline_editor.js | 433 +++++++++++++++++++--------------------- 3 files changed, 213 insertions(+), 234 deletions(-) diff --git a/nodes.py b/nodes.py index 3f38913..28479a8 100644 --- a/nodes.py +++ b/nodes.py @@ -4626,7 +4626,8 @@ class SplineEditor: def INPUT_TYPES(cls): return { "required": { - "coordinates": ("STRING", {"multiline": True}), + "points_store": ("STRING", {"multiline": False}), + "coordinates": ("STRING", {"multiline": False}), "mask_width": ("INT", {"default": 512, "min": 8, "max": MAX_RESOLUTION, "step": 8}), "mask_height": ("INT", {"default": 512, "min": 8, "max": MAX_RESOLUTION, "step": 18}), "points_to_sample": ("INT", {"default": 4, "min": 2, "max": 1000, "step": 1}), @@ -4638,10 +4639,14 @@ class SplineEditor: 'linear', 'step-before', 'step-after', + 'polar', + 'polar-reverse', ], { "default": 'cardinal' }), + "tension": ("FLOAT", {"default": 0.5, "min": 0.0, "max": 1.0, "step": 0.01}), + "segmented": ("BOOLEAN", {"default": False}), }, } @@ -4650,8 +4655,8 @@ class SplineEditor: CATEGORY = "KJNodes/experimental" - def splinedata(self, mask_width, mask_height, coordinates, interpolation, points_to_sample): - + def splinedata(self, mask_width, mask_height, coordinates, interpolation, points_to_sample, points_store, tension, segmented): + print(coordinates) coordinates = json.loads(coordinates) print(coordinates) diff --git a/web/js/jsnodes.js b/web/js/jsnodes.js index 2349636..3ab7d4c 100644 --- a/web/js/jsnodes.js +++ b/web/js/jsnodes.js @@ -5,9 +5,6 @@ app.registerExtension({ async beforeRegisterNodeDef(nodeType, nodeData, app) { switch (nodeData.name) { case "ConditioningMultiCombine": - nodeType.prototype.onNodeMoved = function () { - console.log(this.pos[0]) - } nodeType.prototype.onNodeCreated = function () { //this.inputs_offset = nodeData.name.includes("selective")?1:0 this.cond_type = "CONDITIONING" diff --git a/web/js/spline_editor.js b/web/js/spline_editor.js index 8bea430..531aebe 100644 --- a/web/js/spline_editor.js +++ b/web/js/spline_editor.js @@ -1,5 +1,15 @@ import { app } from '../../../scripts/app.js' +//from melmass +export function makeUUID() { + let dt = new Date().getTime() + const uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => { + const r = ((dt + Math.random() * 16) % 16) | 0 + dt = Math.floor(dt / 16) + return (c === 'x' ? r : (r & 0x3) | 0x8).toString(16) + }) + return uuid +} export const loadScript = ( FILE_URL, @@ -48,20 +58,14 @@ export const loadScript = ( styleTag.id = tag styleTag.innerHTML = ` .spline-editor { - background: var(--comfy-menu-bg); + position: absolute; - color: var(--fg-color); + font: 12px monospace; line-height: 1.5em; padding: 10px; - z-index: 5; + z-index: 0; overflow: hidden; - border-radius: 10px; - border-style: solid; - border-width: medium; - border-color: var(--border-color); - height: 544px; - width: 544px; } ` document.head.appendChild(styleTag) @@ -76,238 +80,187 @@ loadScript('/kjweb_async/protovis.min.js').catch((e) => { }) create_documentation_stylesheet() +function chainCallback(object, property, callback) { + if (object == undefined) { + //This should not happen. + console.error("Tried to add callback to non-existant object") + return; + } + if (property in object) { + const callback_orig = object[property] + object[property] = function () { + const r = callback_orig.apply(this, arguments); + callback.apply(this, arguments); + return r + }; + } else { + object[property] = callback; + } +} app.registerExtension({ - name: 'KJNodes.curves', + name: 'KJNodes.SplineEditor', async beforeRegisterNodeDef(nodeType, nodeData) { - if (nodeData.name == 'SplineEditor') { - addElement(nodeData, nodeType); - } - }, -}) + if (nodeData?.name == 'SplineEditor') { + chainCallback(nodeType.prototype, "onNodeCreated", function () { + hideWidgetForGood(this, this.widgets.find(w => w.name === "coordinates")) -export const addElement = (nodeData,nodeType) => { - console.log("Creating spline editor") - const iconSize = 24 - const iconMargin = 4 - - let splineEditor = null - let vis = null - - const drawFg = nodeType.prototype.onDrawForeground - nodeType.prototype.onNodeCreated = function () { - console.log("Node created") - this.coordWidget = this.widgets.find(w => w.name === "coordinates"); - this.interpolationWidget = this.widgets.find(w => w.name === "interpolation"); - this.pointsWidget = this.widgets.find(w => w.name === "points_to_sample"); - } - nodeType.prototype.onRemoved = function () { - console.log("Node removed") - if (splineEditor !== null) { - splineEditor.parentNode.removeChild(splineEditor) - splineEditor = null - } - } - nodeType.prototype.onDrawForeground = function (ctx) { - console.log("Drawing foreground") - const r = drawFg ? drawFg.apply(this, arguments) : undefined - if (this.flags.collapsed) return r - - const x = this.size[0] - iconSize - iconMargin + var element = document.createElement("div"); + this.uuid = makeUUID() + element.id = `spline-editor-${this.uuid}` - if (this.show_doc && splineEditor === null) { - console.log("Drawing spline editor") - splineEditor = document.createElement('div'); - splineEditor.classList.add('spline-editor'); - - // close button - const closeButton = document.createElement('div'); - closeButton.textContent = '❌'; - closeButton.style.position = 'absolute'; - closeButton.style.top = '0'; - closeButton.style.right = '0'; - closeButton.style.cursor = 'pointer'; - closeButton.style.padding = '5px'; - closeButton.style.color = 'red'; - closeButton.style.fontSize = '12px'; - closeButton.addEventListener('mousedown', (e) => { - e.stopPropagation(); - this.show_doc = !this.show_doc - splineEditor.parentNode.removeChild(splineEditor) - splineEditor = null - }); - - splineEditor.appendChild(closeButton) - - var w = 512 - var h = 512 - var i = 3 - - if (points == null) { - var points = pv.range(1, 5).map(i => ({ - x: i * w / 5, - y: 50 + Math.random() * (h - 100) - })); - } - - var segmented = false - vis = new pv.Panel() - .width(w) - .height(h) - .fillStyle("var(--comfy-menu-bg)") - .strokeStyle("orange") - .lineWidth(0) - .antialias(false) - .margin(10) - .event("mousedown", function() { - if (pv.event.shiftKey) { // Use pv.event to access the event object - i = points.push(this.mouse()) - 1; - return this; - } - }); - vis.add(pv.Rule) - .data(pv.range(0, 8, .5)) - .bottom(d => d * 70 + 9.5) - .strokeStyle("gray") - .lineWidth(1) - - vis.add(pv.Line) - .data(() => points) - .left(d => d.x) - .top(d => d.y) - .interpolate(() => this.interpolationWidget.value) - .segmented(() => segmented) - .strokeStyle(pv.Colors.category10().by(pv.index)) - .tension(0.5) - .lineWidth(3) - - vis.add(pv.Dot) - .data(() => points) - .left(d => d.x) - .top(d => d.y) - .radius(7) - .cursor("move") - .strokeStyle(function() { return i == this.index ? "#ff7f0e" : "#1f77b4"; }) - .fillStyle(function() { return "rgba(100, 100, 100, 0.2)"; }) - .event("mousedown", pv.Behavior.drag()) - .event("dragstart", function() { - i = this.index; - return this; - }) - .event("drag", vis) - .anchor("top").add(pv.Label) - .font(d => Math.sqrt(d[2]) * 32 + "px sans-serif") - //.text(d => `(${Math.round(d.x)}, ${Math.round(d.y)})`) - .text(d => { - // Normalize y to range 0.0 to 1.0, considering the inverted y-axis - var normalizedY = 1.0 - (d.y / h); - return `${normalizedY.toFixed(2)}`; - }) - .textStyle("orange") - - //disable context menu on right click - document.addEventListener('contextmenu', function(e) { - if (e.button === 2) { // Right mouse button - e.preventDefault(); - e.stopPropagation(); - } - }) - //right click remove dot - pv.listen(window, "mousedown", () => { - window.focus(); - if (pv.event.button === 2) { - points.splice(i--, 1); - vis.render(); + this.splineEditor = this.addDOMWidget(nodeData.name, "SplineEditorWidget", element, { + serialize: false, + hideOnZoom: false, + }); + this.addWidget("button", "New spline", null, () => { + + if (!this.properties || !("points" in this.properties)) { + createSplineEditor(this) + this.addProperty("points", this.constructor.type, "string"); + } + else { + createSplineEditor(this, true) } }); - //send coordinates to node on mouseup - pv.listen(window, "mouseup", () => { - if (pathElements !== null) { - let coords = samplePoints(pathElements[0], this.pointsWidget.value); - let coordsString = JSON.stringify(coords); - if (this.coordWidget) { - this.coordWidget.value = coordsString; + this.setSize([550, 800]) + this.splineEditor.parentEl = document.createElement("div"); + this.splineEditor.parentEl.className = "spline-editor"; + this.splineEditor.parentEl.id = `spline-editor-${this.uuid}` + element.appendChild(this.splineEditor.parentEl); + + //disable context menu on right click + document.addEventListener('contextmenu', function(e) { + if (e.button === 2) { // Right mouse button + e.preventDefault(); + e.stopPropagation(); } - } - }); - - vis.render(); - var svgElement = vis.canvas(); - splineEditor.appendChild(svgElement); - // this.addDOMWidget("videopreview", "preview", splineEditor, { - // serialize: false, - // hideOnZoom: false, - - // }); - document.body.appendChild(splineEditor) - var pathElements = svgElement.getElementsByTagName('path'); // Get all path elements - } - // close the popup - else if (!this.show_doc && splineEditor !== null) { - splineEditor.parentNode.removeChild(splineEditor) - splineEditor = null - } - - if (this.show_doc && splineEditor !== null && vis !== null) { - const rect = ctx.canvas.getBoundingClientRect() - const scaleX = rect.width / ctx.canvas.width - const scaleY = rect.height / ctx.canvas.height - - const transform = new DOMMatrix() - .scaleSelf(scaleX, scaleY) - .translateSelf(this.size[0] * scaleX, 0) - .multiplySelf(ctx.getTransform()) - .translateSelf(10, -32) - - const scale = new DOMMatrix() - .scaleSelf(transform.a, transform.d); + }) + chainCallback(this, "onGraphConfigured", function() { + createSplineEditor(this) + }); + }); // onAfterGraphConfigured + }//node created + } //before register +})//register - const styleObject = { - transformOrigin: '0 0', - transform: scale, - left: `${transform.a + transform.e}px`, - top: `${transform.d + transform.f}px`, - }; - Object.assign(splineEditor.style, styleObject); - } - ctx.save() - ctx.translate(x - 2, iconSize - 45) - ctx.scale(iconSize / 32, iconSize / 32) - ctx.strokeStyle = 'rgba(255,255,255,0.3)' - ctx.lineCap = 'round' - ctx.lineJoin = 'round' - ctx.lineWidth = 2.4 - ctx.font = 'bold 36px monospace' - ctx.fillStyle = 'orange'; - ctx.fillText('📈', 0, 24) - ctx.restore() - return r + +function createSplineEditor(context, reset=false) { + console.log("creatingSplineEditor") + + if (reset && context.splineEditor.element) { + context.splineEditor.element.innerHTML = ''; // Clear the container + } + const coordWidget = context.widgets.find(w => w.name === "coordinates"); + const interpolationWidget = context.widgets.find(w => w.name === "interpolation"); + const pointsWidget = context.widgets.find(w => w.name === "points_to_sample"); + const pointsStoreWidget = context.widgets.find(w => w.name === "points_store"); + const tensionWidget = context.widgets.find(w => w.name === "tension"); + const segmentedWidget = context.widgets.find(w => w.name === "segmented"); + + // Initialize or reset points array + var w = 512 + var h = 512 + var i = 3 + let points = []; + if (!reset && pointsStoreWidget.value != "") { + points = JSON.parse(pointsStoreWidget.value); + } else { + points = pv.range(1, 4).map((i, index) => { + if (index === 0) { + // First point at the bottom-left corner + return { x: 0, y: h }; + } else if (index === 2) { + // Last point at the top-right corner + return { x: w, y: 0 }; + } else { + // Other points remain as they were + return { + x: i * w / 5, + y: 50 + Math.random() * (h - 100) + }; } - // handle clicking of the icon - const mouseDown = nodeType.prototype.onMouseDown - nodeType.prototype.onMouseDown = function (e, localPos, canvas) { - const r = mouseDown ? mouseDown.apply(this, arguments) : undefined - const iconX = this.size[0] - iconSize - iconMargin - const iconY = iconSize - 45 - if ( - localPos[0] > iconX && - localPos[0] < iconX + iconSize && - localPos[1] > iconY && - localPos[1] < iconY + iconSize - ) { - if (this.show_doc === undefined) { - this.show_doc = true - } else { - this.show_doc = !this.show_doc + }); + pointsStoreWidget.value = JSON.stringify(points); + } + + var vis = new pv.Panel() + .width(w) + .height(h) + .fillStyle("var(--comfy-menu-bg)") + .strokeStyle("gray") + .lineWidth(2) + .antialias(false) + .margin(10) + .event("mousedown", function() { + if (pv.event.shiftKey) { // Use pv.event to access the event object + i = points.push(this.mouse()) - 1; + return this; + } + }) + .event("mouseup", function() { + if (this.pathElements !== null) { + let coords = samplePoints(pathElements[0], pointsWidget.value); + let coordsString = JSON.stringify(coords); + pointsStoreWidget.value = JSON.stringify(points); + if (coordWidget) { + coordWidget.value = coordsString; } - return true; - } - return r; } + }); + + vis.add(pv.Rule) + .data(pv.range(0, 8, .5)) + .bottom(d => d * 64 + 0) + .strokeStyle("gray") + .lineWidth(1) + + vis.add(pv.Line) + .data(() => points) + .left(d => d.x) + .top(d => d.y) + .interpolate(() => interpolationWidget.value) + .tension(() => tensionWidget.value) + .segmented(() => segmentedWidget.value) + .strokeStyle(pv.Colors.category10().by(pv.index)) + .lineWidth(3) + + vis.add(pv.Dot) + .data(() => points) + .left(d => d.x) + .top(d => d.y) + .radius(8) + .cursor("move") + .strokeStyle(function() { return i == this.index ? "#ff7f0e" : "#1f77b4"; }) + .fillStyle(function() { return "rgba(100, 100, 100, 0.2)"; }) + .event("mousedown", pv.Behavior.drag()) + .event("dragstart", function() { + i = this.index; + if (pv.event.button === 2) { + points.splice(i--, 1); + vis.render(); + } + return this; + }) + .event("drag", vis) + .anchor("top").add(pv.Label) + .font(d => Math.sqrt(d[2]) * 32 + "px sans-serif") + //.text(d => `(${Math.round(d.x)}, ${Math.round(d.y)})`) + .text(d => { + // Normalize y to range 0.0 to 1.0, considering the inverted y-axis + var normalizedY = 1.0 - (d.y / h); + return `${normalizedY.toFixed(2)}`; + }) + .textStyle("orange") + + vis.render(); + var svgElement = vis.canvas(); + svgElement.style['zIndex'] = "2" + svgElement.style['position'] = "relative" + context.splineEditor.element.appendChild(svgElement); + var pathElements = svgElement.getElementsByTagName('path'); // Get all path elements + } - - - function samplePoints(svgPathElement, numSamples) { var pathLength = svgPathElement.getTotalLength(); var points = []; @@ -324,4 +277,28 @@ function samplePoints(svgPathElement, numSamples) { } //console.log(points); return points; +} + +//from melmass +export function hideWidgetForGood(node, widget, suffix = '') { + widget.origType = widget.type + widget.origComputeSize = widget.computeSize + widget.origSerializeValue = widget.serializeValue + widget.computeSize = () => [0, -4] // -4 is due to the gap litegraph adds between widgets automatically + widget.type = "converted-widget" + suffix + // widget.serializeValue = () => { + // // Prevent serializing the widget if we have no input linked + // const w = node.inputs?.find((i) => i.widget?.name === widget.name); + // if (w?.link == null) { + // return undefined; + // } + // return widget.origSerializeValue ? widget.origSerializeValue() : widget.value; + // }; + + // Hide any linked widgets, e.g. seed+seedControl + if (widget.linkedWidgets) { + for (const w of widget.linkedWidgets) { + hideWidgetForGood(node, w, ':' + widget.name) + } + } } \ No newline at end of file From d1a1bb67a608d4f92fd235d57840c17765c93123 Mon Sep 17 00:00:00 2001 From: kijai <40791699+kijai@users.noreply.github.com> Date: Sat, 20 Apr 2024 21:24:54 +0300 Subject: [PATCH 08/95] Update spline_editor.js --- web/js/spline_editor.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/js/spline_editor.js b/web/js/spline_editor.js index 531aebe..9a40a15 100644 --- a/web/js/spline_editor.js +++ b/web/js/spline_editor.js @@ -236,7 +236,7 @@ function createSplineEditor(context, reset=false) { .event("mousedown", pv.Behavior.drag()) .event("dragstart", function() { i = this.index; - if (pv.event.button === 2) { + if (pv.event.button === 2 && i !== 0 && i !== points.length - 1) { points.splice(i--, 1); vis.render(); } From 874ae4b87fb16785054ea796e75a8dfa291bc00a Mon Sep 17 00:00:00 2001 From: kijai <40791699+kijai@users.noreply.github.com> Date: Sat, 20 Apr 2024 21:39:34 +0300 Subject: [PATCH 09/95] Update spline_editor.js --- web/js/spline_editor.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/web/js/spline_editor.js b/web/js/spline_editor.js index 9a40a15..f469996 100644 --- a/web/js/spline_editor.js +++ b/web/js/spline_editor.js @@ -138,9 +138,12 @@ app.registerExtension({ }) chainCallback(this, "onGraphConfigured", function() { createSplineEditor(this) + this.setSize([550, 800]) }); + }); // onAfterGraphConfigured }//node created + } //before register })//register From 469d932019df7205dde99624562f24a141c174ff Mon Sep 17 00:00:00 2001 From: kijai <40791699+kijai@users.noreply.github.com> Date: Sun, 21 Apr 2024 03:08:09 +0300 Subject: [PATCH 10/95] Update nodes.py --- nodes.py | 65 ++++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 58 insertions(+), 7 deletions(-) diff --git a/nodes.py b/nodes.py index 28479a8..4881072 100644 --- a/nodes.py +++ b/nodes.py @@ -4629,7 +4629,7 @@ class SplineEditor: "points_store": ("STRING", {"multiline": False}), "coordinates": ("STRING", {"multiline": False}), "mask_width": ("INT", {"default": 512, "min": 8, "max": MAX_RESOLUTION, "step": 8}), - "mask_height": ("INT", {"default": 512, "min": 8, "max": MAX_RESOLUTION, "step": 18}), + "mask_height": ("INT", {"default": 512, "min": 8, "max": MAX_RESOLUTION, "step": 8}), "points_to_sample": ("INT", {"default": 4, "min": 2, "max": 1000, "step": 1}), "interpolation": ( [ @@ -4647,24 +4647,72 @@ class SplineEditor: }), "tension": ("FLOAT", {"default": 0.5, "min": 0.0, "max": 1.0, "step": 0.01}), "segmented": ("BOOLEAN", {"default": False}), + "float_output_type": ( + [ + 'list', + 'list of lists', + 'pandas series', + 'tensor', + ], + { + "default": 'list' + }), }, } RETURN_TYPES = ("MASK", "STRING", "FLOAT") FUNCTION = "splinedata" - CATEGORY = "KJNodes/experimental" + DESCRIPTION = """ +# WORK IN PROGRESS +Do not count on this as part of your workflow yet, +probably contains lots of bugs and stability is not +guaranteed!! + +## Graphical editor to create values for various +## schedules and/or mask batches. - def splinedata(self, mask_width, mask_height, coordinates, interpolation, points_to_sample, points_store, tension, segmented): - print(coordinates) +**points_to_sample** value sets the number of samples +returned from the **drawn spline itself**, this is independent from the +actual control points, so the interpolation type matters. + +Changing interpolation type and tension value takes effect on +interaction with the graph. + +output types: + - mask batch + example compatible nodes: anything that takes masks + - list of floats + example compatible nodes: IPAdapter weights + - list of lists + example compatible nodes: unknown + - pandas series + example compatible nodes: anything that takes Fizz' + nodes Batch Value Schedule + - torch tensor + example compatible nodes: unknown +""" + + def splinedata(self, mask_width, mask_height, coordinates, float_output_type, interpolation, points_to_sample, points_store, tension, segmented): + coordinates = json.loads(coordinates) - print(coordinates) normalized_y_values = [ 1.0 - (point['y'] / 512) for point in coordinates ] - + if float_output_type == 'list': + out_floats = normalized_y_values + elif float_output_type == 'list of lists': + out_floats = [[value] for value in normalized_y_values], + elif float_output_type == 'pandas series': + try: + import pandas as pd + except: + raise Exception("MaskOrImageToWeight: pandas is not installed. Please install pandas to use this output_type") + out_floats = pd.Series(normalized_y_values), + elif float_output_type == 'tensor': + out_floats = torch.tensor(normalized_y_values, dtype=torch.float32) # Create a color map for grayscale intensities color_map = lambda y: torch.full((mask_height, mask_width, 3), y, dtype=torch.float32) @@ -4675,7 +4723,7 @@ class SplineEditor: masks_out = torch.stack(image_tensors) masks_out = masks_out.mean(dim=-1) print(masks_out.shape) - return (masks_out, coordinates, normalized_y_values,) + return (masks_out, coordinates, out_floats,) class StabilityAPI_SD3: @@ -4842,6 +4890,7 @@ class MaskOrImageToWeight: 'list', 'list of lists', 'pandas series', + 'tensor', ], { "default": 'list' @@ -4884,6 +4933,8 @@ and returns it as a float value. except: raise Exception("MaskOrImageToWeight: pandas is not installed. Please install pandas to use this output_type") return pd.Series(mean_values), + elif output_type == 'tensor': + return torch.tensor(mean_values, dtype=torch.float32) else: raise ValueError(f"Unsupported output_type: {output_type}") class FloatToMask: From 38646a82b20a65360eb47c0ec2299c6d7c06cc89 Mon Sep 17 00:00:00 2001 From: kijai <40791699+kijai@users.noreply.github.com> Date: Sun, 21 Apr 2024 16:16:21 +0300 Subject: [PATCH 11/95] Add menu entry to jump to Set node from Get node --- web/js/setgetnodes.js | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/web/js/setgetnodes.js b/web/js/setgetnodes.js index d3035f7..dfd8711 100644 --- a/web/js/setgetnodes.js +++ b/web/js/setgetnodes.js @@ -315,9 +315,29 @@ app.registerExtension({ this.findSetter = function(graph) { const name = this.widgets[0].value; - return graph._nodes.find(otherNode => otherNode.type === 'SetNode' && otherNode.widgets[0].value === name && name !== ''); + const foundNode = graph._nodes.find(otherNode => otherNode.type === 'SetNode' && otherNode.widgets[0].value === name && name !== ''); + return foundNode; }; + this.goToSetter = function() { + const setter = this.findSetter(this.graph); + const canvas = app.canvas; + console.log(canvas) + console.log(canvas.highlighted_links) + if (canvas?.ds?.offset) { + const nodeCenterX = setter.pos[0] + (setter.size[0] / 2); + const nodeCenterY = setter.pos[1] + (setter.size[1] / 2); + + canvas.ds.offset[0] = -nodeCenterX + canvas.mouse[0]; + canvas.ds.offset[1] = -nodeCenterY + canvas.mouse[1]; + } + if (canvas?.ds?.scale != null) { + canvas.ds.scale = Number(1); + } + canvas.selectNode(setter, false) + canvas.setDirty(true, true); + }; + // This node is purely frontend and does not impact the resulting prompt so should not be serialized this.isVirtualNode = true; } @@ -337,6 +357,16 @@ app.registerExtension({ } onAdded(graph) { } + getExtraMenuOptions(_, options) { + options.unshift( + { + content: "Go to setter", + callback: () => { + this.goToSetter(); + }, + }, + ); + } } LiteGraph.registerNodeType( From e9b8af8fd0263c65d8a7665c5b9d5589226e7fd0 Mon Sep 17 00:00:00 2001 From: kijai <40791699+kijai@users.noreply.github.com> Date: Sun, 21 Apr 2024 21:13:07 +0300 Subject: [PATCH 12/95] Update setgetnodes.js --- web/js/setgetnodes.js | 79 ++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 74 insertions(+), 5 deletions(-) diff --git a/web/js/setgetnodes.js b/web/js/setgetnodes.js index dfd8711..2f0e81b 100644 --- a/web/js/setgetnodes.js +++ b/web/js/setgetnodes.js @@ -50,6 +50,8 @@ app.registerExtension({ this.properties.showOutputText = SetNode.defaultVisibility; const node = this; + const canvas = app.canvas; + const ctx = canvas.ctx this.addWidget( "text", @@ -201,9 +203,35 @@ app.registerExtension({ return graph._nodes.filter(otherNode => otherNode.type === 'GetNode' && otherNode.widgets[0].value === name && name !== ''); } + this.lineToGetters = function() { + var scale = canvas.ds.scale; + const getterNodes = this.findGetters(this.graph); + var setPosX = (this.pos[0] + canvas.ds.offset[0]) * scale; + var setPosY = (this.pos[1] + canvas.ds.offset[1]) * scale; + + getterNodes.forEach((getter) => { + var getPosX = (getter.pos[0] + canvas.ds.offset[0]) * scale; + var getPosY = (getter.pos[1] + canvas.ds.offset[1]) * scale; + console.log(getPosX, getPosY, setPosX, setPosY); + + canvas.renderLink( + ctx, + [setPosX, setPosY], + [getPosX, getPosY], + undefined, + true, + 0, + "orange", + LiteGraph.RIGHT, + LiteGraph.LEFT + ); + }); + }; + // This node is purely frontend and does not impact the resulting prompt so should not be serialized this.isVirtualNode = true; } + onRemoved() { const allGetters = this.graph._nodes.filter((otherNode) => otherNode.type == "GetNode"); @@ -213,6 +241,17 @@ app.registerExtension({ } }) } + getExtraMenuOptions(_, options) { + options.unshift( + { + content: "Line to Getters", + callback: () => { + this.lineToGetters(); + }, + }, + ); + } + } LiteGraph.registerNodeType( @@ -239,7 +278,8 @@ app.registerExtension({ this.properties = {}; } this.properties.showOutputText = GetNode.defaultVisibility; - + const canvas = app.canvas; + const ctx = canvas.ctx const node = this; this.addWidget( "combo", @@ -321,9 +361,6 @@ app.registerExtension({ this.goToSetter = function() { const setter = this.findSetter(this.graph); - const canvas = app.canvas; - console.log(canvas) - console.log(canvas.highlighted_links) if (canvas?.ds?.offset) { const nodeCenterX = setter.pos[0] + (setter.size[0] / 2); const nodeCenterY = setter.pos[1] + (setter.size[1] / 2); @@ -337,11 +374,37 @@ app.registerExtension({ canvas.selectNode(setter, false) canvas.setDirty(true, true); }; + this.isLineVisible = false; + + this.lineToSetter = function() { + this.isLineVisible = !this.isLineVisible; + + console.log(this) + var scale = canvas.ds.scale + const setter = this.findSetter(this.graph); + var getPosX = (this.pos[0] + canvas.ds.offset[0]) * scale + var getPosY = (this.pos[1] + canvas.ds.offset[1]) * scale + var setPosX = (setter.pos[0] + canvas.ds.offset[0]) * scale + var setPosY = (setter.pos[1] + canvas.ds.offset[1]) * scale + console.log(getPosX, getPosY, setPosX, setPosY) + + canvas.renderLink( + ctx, + [getPosX, getPosY], + [setPosX, setPosY], + undefined, + true, + 0, + "orange", + LiteGraph.RIGHT, + LiteGraph.LEFT + ); + }; // This node is purely frontend and does not impact the resulting prompt so should not be serialized this.isVirtualNode = true; } - + getInputLink(slot) { const setter = this.findSetter(this.graph); @@ -365,6 +428,12 @@ app.registerExtension({ this.goToSetter(); }, }, + { + content: "Line to setter", + callback: () => { + this.lineToSetter(); + }, + }, ); } } From 9b2514606493eaf8634ec29f8f38dfc8da6c36cb Mon Sep 17 00:00:00 2001 From: kijai <40791699+kijai@users.noreply.github.com> Date: Mon, 22 Apr 2024 09:50:15 +0300 Subject: [PATCH 13/95] Add WeightScheduleConvert --- nodes.py | 82 ++++++++++++++++++++++++++++++++++++++--- web/js/spline_editor.js | 2 +- 2 files changed, 77 insertions(+), 7 deletions(-) diff --git a/nodes.py b/nodes.py index 4881072..ca51c0e 100644 --- a/nodes.py +++ b/nodes.py @@ -386,8 +386,8 @@ and interpolating from that to fully black at the 16th frame. "points_string": ("STRING", {"default": "0:(0.0),\n7:(1.0),\n15:(0.0)\n", "multiline": True}), "invert": ("BOOLEAN", {"default": False}), "frames": ("INT", {"default": 16,"min": 2, "max": 255, "step": 1}), - "width": ("INT", {"default": 512,"min": 16, "max": 4096, "step": 1}), - "height": ("INT", {"default": 512,"min": 16, "max": 4096, "step": 1}), + "width": ("INT", {"default": 512,"min": 1, "max": 4096, "step": 1}), + "height": ("INT", {"default": 512,"min": 1, "max": 4096, "step": 1}), "interpolation": (["linear", "ease_in", "ease_out", "ease_in_out"],), }, } @@ -4906,8 +4906,7 @@ class MaskOrImageToWeight: FUNCTION = "execute" CATEGORY = "KJNodes" DESCRIPTION = """ -Gets the mean value of mask or image -and returns it as a float value. +Converts different value lists/series to another type. """ def execute(self, output_type, images=None, masks=None): @@ -4915,7 +4914,6 @@ and returns it as a float value. if masks is not None and images is None: for mask in masks: mean_values.append(mask.mean().item()) - print(mean_values) elif masks is None and images is not None: for image in images: mean_values.append(image.mean().item()) @@ -4934,9 +4932,79 @@ and returns it as a float value. raise Exception("MaskOrImageToWeight: pandas is not installed. Please install pandas to use this output_type") return pd.Series(mean_values), elif output_type == 'tensor': - return torch.tensor(mean_values, dtype=torch.float32) + return torch.tensor(mean_values, dtype=torch.float32), else: raise ValueError(f"Unsupported output_type: {output_type}") + +class WeightScheduleConvert: + + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "input_values": ("FLOAT", {"default": 0.0, "forceInput": True}), + "output_type": ( + [ + 'list', + 'list of lists', + 'pandas series', + 'tensor', + ], + { + "default": 'list' + }), + }, + + + } + RETURN_TYPES = ("FLOAT",) + FUNCTION = "execute" + CATEGORY = "KJNodes" + DESCRIPTION = """ +Gets the mean value of mask or image +and returns it as a float value. +""" + + def detect_input_type(self, input_values): + import pandas as pd + if isinstance(input_values, list): + return 'list' + elif isinstance(input_values, pd.Series): + return 'pandas series' + elif isinstance(input_values, torch.Tensor): + return 'tensor' + elif isinstance(input_values, list) and all(isinstance(sub, list) for sub in input_values): + return 'list of lists' + else: + raise ValueError("Unsupported input type") + + def execute(self, input_values, output_type): + import pandas as pd + # Detect the input type + input_type = self.detect_input_type(input_values) + + # Convert input_values to a list of floats + if input_type == 'list of lists': + float_values = [item for sublist in input_values for item in sublist] + elif input_type == 'pandas series': + float_values = input_values.tolist() + elif input_type == 'tensor': + float_values = input_values + else: + float_values = input_values + + if output_type == 'list': + return float_values, + elif output_type == 'list of lists': + return [[value] for value in float_values], + elif output_type == 'pandas series': + return pd.Series(float_values), + elif output_type == 'tensor': + if input_type == 'pandas series': + return torch.tensor(input_values.values, dtype=torch.float32), + else: + raise ValueError(f"Unsupported output_type: {output_type}") + class FloatToMask: @classmethod @@ -5060,6 +5128,7 @@ NODE_CLASS_MAPPINGS = { "ImageAndMaskPreview": ImageAndMaskPreview, "StabilityAPI_SD3": StabilityAPI_SD3, "MaskOrImageToWeight": MaskOrImageToWeight, + "WeightScheduleConvert": WeightScheduleConvert, "FloatToMask": FloatToMask, "CustomSigmas": CustomSigmas } @@ -5145,6 +5214,7 @@ NODE_DISPLAY_NAME_MAPPINGS = { "ImageAndMaskPreview": "Image & Mask Preview", "StabilityAPI_SD3": "Stability API SD3", "MaskOrImageToWeight": "Mask Or Image To Weight", + "WeightScheduleConvert": "Weight Schedule Convert", "FloatToMask": "Float To Mask", "CustomSigmas": "Custom Sigmas", } \ No newline at end of file diff --git a/web/js/spline_editor.js b/web/js/spline_editor.js index f469996..e327f66 100644 --- a/web/js/spline_editor.js +++ b/web/js/spline_editor.js @@ -123,7 +123,7 @@ app.registerExtension({ createSplineEditor(this, true) } }); - this.setSize([550, 800]) + this.setSize([550, 850]) this.splineEditor.parentEl = document.createElement("div"); this.splineEditor.parentEl.className = "spline-editor"; this.splineEditor.parentEl.id = `spline-editor-${this.uuid}` From a1d759c3cddb4f96804ee9b2c196f1bf8db0a27f Mon Sep 17 00:00:00 2001 From: kijai <40791699+kijai@users.noreply.github.com> Date: Mon, 22 Apr 2024 09:52:41 +0300 Subject: [PATCH 14/95] Update nodes.py --- nodes.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/nodes.py b/nodes.py index ca51c0e..0170901 100644 --- a/nodes.py +++ b/nodes.py @@ -4906,7 +4906,8 @@ class MaskOrImageToWeight: FUNCTION = "execute" CATEGORY = "KJNodes" DESCRIPTION = """ -Converts different value lists/series to another type. +Gets the mean values from mask or image batch +and returns that as the selected output type. """ def execute(self, output_type, images=None, masks=None): @@ -4961,8 +4962,7 @@ class WeightScheduleConvert: FUNCTION = "execute" CATEGORY = "KJNodes" DESCRIPTION = """ -Gets the mean value of mask or image -and returns it as a float value. +Converts different value lists/series to another type. """ def detect_input_type(self, input_values): From 76c536d1560699b1c476ede2d2f48cc80c9c971f Mon Sep 17 00:00:00 2001 From: Kijai <40791699+kijai@users.noreply.github.com> Date: Mon, 22 Apr 2024 13:18:04 +0300 Subject: [PATCH 15/95] Update nodes.py --- nodes.py | 74 ++++++++++++++++++++++++++++++++++---------------------- 1 file changed, 45 insertions(+), 29 deletions(-) diff --git a/nodes.py b/nodes.py index 0170901..aa779cf 100644 --- a/nodes.py +++ b/nodes.py @@ -4352,19 +4352,12 @@ class CameraPoseVisualizer: @classmethod def INPUT_TYPES(s): return {"required": { - "pose_file_path": ("STRING", {"default": 'pose file path here', "multiline": False}), - "sample_stride": ("INT", {"default": 1,"min": 0, "max": 100, "step": 1}), - "frames": ("INT", {"default": 16,"min": 0, "max": 100, "step": 1}), - "base_xval": ("FLOAT", {"default": 0.5,"min": 0, "max": 100, "step": 0.01}), - "zval": ("FLOAT", {"default": 2.0,"min": 0, "max": 100, "step": 0.01}), - "use_exact_fx": ("BOOLEAN", {"default": True}), + "pose_file_path": ("STRING", {"default": '', "multiline": False}), + "base_xval": ("FLOAT", {"default": 0.2,"min": 0, "max": 100, "step": 0.01}), + "zval": ("FLOAT", {"default": 0.3,"min": 0, "max": 100, "step": 0.01}), + "scale": ("FLOAT", {"default": 1.0,"min": 0.01, "max": 10.0, "step": 0.01}), + "use_exact_fx": ("BOOLEAN", {"default": False}), "relative_c2w": ("BOOLEAN", {"default": True}), - "x_min": ("FLOAT", {"default": -5.0,"min": -100, "max": 100, "step": 0.01}), - "x_max": ("FLOAT", {"default": 5.0,"min": -100, "max": 100, "step": 0.01}), - "y_min": ("FLOAT", {"default": -5.0,"min": -100, "max": 100, "step": 0.01}), - "y_max": ("FLOAT", {"default": 5.0,"min": -100, "max": 100, "step": 0.01}), - "z_min": ("FLOAT", {"default": -5.0,"min": -100, "max": 100, "step": 0.01}), - "z_max": ("FLOAT", {"default": 5.0,"min": -100, "max": 100, "step": 0.01}), "use_viewer": ("BOOLEAN", {"default": False}), }, "optional": { @@ -4376,26 +4369,40 @@ class CameraPoseVisualizer: FUNCTION = "plot" CATEGORY = "KJNodes/misc" DESCRIPTION = """ -Visualizes the camera poses from a .txt file with -RealEstate camera intrinsics and coordinates in a 3D plot. +Visualizes the camera poses, from Animatediff-Evolved CameraCtrl Pose +or a .txt file with RealEstate camera intrinsics and coordinates, in a 3D plot. """ - def plot(self, pose_file_path, sample_stride, frames, base_xval, zval, use_exact_fx, relative_c2w, x_min, x_max, y_min, y_max, z_min, z_max, use_viewer, cameractrl_poses=None): + def plot(self, pose_file_path, scale, base_xval, zval, use_exact_fx, relative_c2w, use_viewer, cameractrl_poses=None): import matplotlib as mpl import matplotlib.pyplot as plt import io from torchvision.transforms import ToTensor + + x_min = -2.0 * scale + x_max = 2.0 * scale + y_min = -2.0 * scale + y_max = 2.0 * scale + z_min = -2.0 * scale + z_max = 2.0 * scale + plt.rcParams['text.color'] = '#999999' self.fig = plt.figure(figsize=(18, 7)) + self.fig.patch.set_facecolor('#353535') self.ax = self.fig.add_subplot(projection='3d') + self.ax.set_facecolor('#353535') # Set the background color here + self.ax.grid(color='#999999', linestyle='-', linewidth=0.5) self.plotly_data = None # plotly data traces self.ax.set_aspect("auto") self.ax.set_xlim(x_min, x_max) self.ax.set_ylim(y_min, y_max) self.ax.set_zlim(z_min, z_max) - self.ax.set_xlabel('x') - self.ax.set_ylabel('y') - self.ax.set_zlabel('z') + self.ax.set_xlabel('x', color='#999999') + self.ax.set_ylabel('y', color='#999999') + self.ax.set_zlabel('z', color='#999999') + for text in self.ax.get_xticklabels() + self.ax.get_yticklabels() + self.ax.get_zticklabels(): + text.set_color('#999999') print('initialize camera pose visualizer') + if pose_file_path != "": with open(pose_file_path, 'r') as f: poses = f.readlines() @@ -4409,26 +4416,35 @@ RealEstate camera intrinsics and coordinates in a 3D plot. else: raise ValueError("Please provide either pose_file_path or cameractrl_poses") - cropped_length = frames * sample_stride total_frames = len(w2cs) - start_frame_ind = random.randint(0, max(0, total_frames - cropped_length - 1)) - end_frame_ind = min(start_frame_ind + cropped_length, total_frames) - frame_ind = np.linspace(start_frame_ind, end_frame_ind - 1, frames, dtype=int) - w2cs = [w2cs[x] for x in frame_ind] transform_matrix = np.asarray([[1, 0, 0, 0], [0, 0, 1, 0], [0, -1, 0, 0], [0, 0, 0, 1]]).reshape(4, 4) last_row = np.zeros((1, 4)) last_row[0, -1] = 1.0 + w2cs = [np.concatenate((w2c, last_row), axis=0) for w2c in w2cs] c2ws = self.get_c2w(w2cs, transform_matrix, relative_c2w) for frame_idx, c2w in enumerate(c2ws): - self.extrinsic2pyramid(c2w, frame_idx / frames, hw_ratio=1/1, base_xval=base_xval, - zval=(fxs[frame_idx] if use_exact_fx else zval)) + self.extrinsic2pyramid(c2w, frame_idx / total_frames, hw_ratio=1/1, base_xval=base_xval, + zval=(fxs[frame_idx] if use_exact_fx else zval)) + # Create the colorbar cmap = mpl.cm.rainbow - norm = mpl.colors.Normalize(vmin=0, vmax=frames) - self.fig.colorbar(mpl.cm.ScalarMappable(norm=norm, cmap=cmap), ax=self.ax, orientation='vertical', label='Frame Number') - plt.title('Extrinsic Parameters') + norm = mpl.colors.Normalize(vmin=0, vmax=total_frames) + colorbar = self.fig.colorbar(mpl.cm.ScalarMappable(norm=norm, cmap=cmap), ax=self.ax, orientation='vertical') + + # Change the colorbar label + colorbar.set_label('Frame', color='#999999') # Change the label and its color + + # Change the tick colors + colorbar.ax.yaxis.set_tick_params(colors='#999999') # Change the tick color + + # Change the tick frequency + # Assuming you want to set the ticks at every 10th frame + ticks = np.arange(0, total_frames, 10) + colorbar.ax.yaxis.set_ticks(ticks) + + plt.title('') plt.draw() buf = io.BytesIO() plt.savefig(buf, format='png', bbox_inches='tight', pad_inches=0) @@ -4459,7 +4475,7 @@ RealEstate camera intrinsics and coordinates in a 3D plot. color = color_map if isinstance(color_map, str) else plt.cm.rainbow(color_map) self.ax.add_collection3d( - Poly3DCollection(meshes, facecolors=color, linewidths=0.3, edgecolors=color, alpha=0.35)) + Poly3DCollection(meshes, facecolors=color, linewidths=0.3, edgecolors=color, alpha=0.25)) def customize_legend(self, list_label): from matplotlib.patches import Patch From 171b70bfa51ff7202a62a4a4ee2f33e9211a28ba Mon Sep 17 00:00:00 2001 From: kijai <40791699+kijai@users.noreply.github.com> Date: Mon, 22 Apr 2024 19:50:34 +0300 Subject: [PATCH 16/95] Remake ColorToMask node that code was atrocious --- nodes.py | 50 ++++++++++++++++++++++++++++---------------------- 1 file changed, 28 insertions(+), 22 deletions(-) diff --git a/nodes.py b/nodes.py index aa779cf..be07541 100644 --- a/nodes.py +++ b/nodes.py @@ -1000,35 +1000,41 @@ Converts chosen RGB value to a mask "green": ("INT", {"default": 0,"min": 0, "max": 255, "step": 1}), "blue": ("INT", {"default": 0,"min": 0, "max": 255, "step": 1}), "threshold": ("INT", {"default": 10,"min": 0, "max": 255, "step": 1}), + "per_batch": ("INT", {"default": 16, "min": 1, "max": 4096, "step": 1}), }, } - def clip(self, images, red, green, blue, threshold, invert): - color = np.array([red, green, blue]) - images = 255. * images.cpu().numpy() - images = np.clip(images, 0, 255).astype(np.uint8) - images = [Image.fromarray(image) for image in images] - images = [np.array(image) for image in images] + def clip(self, images, red, green, blue, threshold, invert, per_batch): - black = [0, 0, 0] - white = [255, 255, 255] + color = torch.tensor([red, green, blue], dtype=torch.uint8) + black = torch.tensor([0, 0, 0], dtype=torch.uint8) + white = torch.tensor([255, 255, 255], dtype=torch.uint8) + if invert: - black, white = white, black + black, white = white, black - new_images = [] - for image in images: - new_image = np.full_like(image, black) + steps = images.shape[0] + pbar = comfy.utils.ProgressBar(steps) + tensors_out = [] + + for start_idx in range(0, images.shape[0], per_batch): - color_distances = np.linalg.norm(image - color, axis=-1) - complement_indexes = color_distances <= threshold + # Calculate color distances + color_distances = torch.norm(images[start_idx:start_idx+per_batch] * 255 - color, dim=-1) + + # Create a mask based on the threshold + mask = color_distances <= threshold + + # Apply the mask to create new images + mask_out = torch.where(mask.unsqueeze(-1), white, black).float() + mask_out = mask_out.mean(dim=-1) - new_image[complement_indexes] = white - - new_images.append(new_image) - - new_images = np.array(new_images).astype(np.float32) / 255.0 - new_images = torch.from_numpy(new_images).permute(3, 0, 1, 2) - return new_images + tensors_out.append(mask_out.cpu()) + batch_count = mask_out.shape[0] + pbar.update(batch_count) + + tensors_out = torch.cat(tensors_out, dim=0) + return tensors_out, class ConditioningMultiCombine: @classmethod @@ -5058,7 +5064,7 @@ Each mask is generated with the specified width and height. mask = torch.ones((height, width), dtype=torch.float32) * value masks.append(mask) masks_out = torch.stack(masks, dim=0) - print(masks_out.shape) + return(masks_out,) From b80ba59516ace0882e256e9e7a334b6b4320c2c5 Mon Sep 17 00:00:00 2001 From: kijai <40791699+kijai@users.noreply.github.com> Date: Mon, 22 Apr 2024 19:53:04 +0300 Subject: [PATCH 17/95] Update nodes.py --- nodes.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/nodes.py b/nodes.py index be07541..f4f3704 100644 --- a/nodes.py +++ b/nodes.py @@ -987,7 +987,9 @@ class ColorToMask: FUNCTION = "clip" CATEGORY = "KJNodes/masking" DESCRIPTION = """ -Converts chosen RGB value to a mask +Converts chosen RGB value to a mask. +With batch inputs, the **per_batch** +controls the number of images processed at once. """ @classmethod From 60cd6b555a5b7c0fc84f90b0c849d6f3100fbee6 Mon Sep 17 00:00:00 2001 From: kijai <40791699+kijai@users.noreply.github.com> Date: Tue, 23 Apr 2024 00:47:18 +0300 Subject: [PATCH 18/95] Update nodes.py --- nodes.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/nodes.py b/nodes.py index f4f3704..c4b9ea1 100644 --- a/nodes.py +++ b/nodes.py @@ -2472,9 +2472,11 @@ Segments an image or batch of images using CLIPSeg. tensor = torch.sigmoid(outputs[0]) tensor_thresholded = torch.where(tensor > threshold, tensor, torch.tensor(0, dtype=torch.float)) tensor_normalized = (tensor_thresholded - tensor_thresholded.min()) / (tensor_thresholded.max() - tensor_thresholded.min()) - tensor = tensor_normalized.unsqueeze(0).unsqueeze(0) + tensor = tensor_normalized # Resize the mask + if len(tensor.shape) == 3: + tensor = tensor.unsqueeze(0) resized_tensor = F.interpolate(tensor, size=(height, width), mode='nearest') # Remove the extra dimensions From 48d5a18bd42b6b4e8bae35e26c848e319b63e792 Mon Sep 17 00:00:00 2001 From: kijai <40791699+kijai@users.noreply.github.com> Date: Tue, 23 Apr 2024 00:52:48 +0300 Subject: [PATCH 19/95] Update nodes.py --- nodes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nodes.py b/nodes.py index c4b9ea1..5e0200d 100644 --- a/nodes.py +++ b/nodes.py @@ -2462,7 +2462,7 @@ Segments an image or batch of images using CLIPSeg. for image in images: image = (image* 255).type(torch.uint8) prompt = text - input_prc = processor(text=prompt, images=image, padding="max_length", return_tensors="pt") + input_prc = processor(text=prompt, images=image, return_tensors="pt") # Move the processed input to the device for key in input_prc: input_prc[key] = input_prc[key].to(device) From 1c3e7d0df7cf0cd19be2d2f980951c08d8dcf8f9 Mon Sep 17 00:00:00 2001 From: Kijai <40791699+kijai@users.noreply.github.com> Date: Tue, 23 Apr 2024 16:33:15 +0300 Subject: [PATCH 20/95] Caption input for AddLabel --- nodes.py | 47 ++++++++++++++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 13 deletions(-) diff --git a/nodes.py b/nodes.py index 5e0200d..00110ad 100644 --- a/nodes.py +++ b/nodes.py @@ -3248,9 +3248,11 @@ class AddLabel: ], { "default": 'up' - }), }, + "optional":{ + "caption": ("STRING", {"default": "", "forceInput": True}), + } } RETURN_TYPES = ("IMAGE",) @@ -3264,7 +3266,7 @@ Fonts are loaded from this folder: ComfyUI/custom_nodes/ComfyUI-KJNodes/fonts """ - def addlabel(self, image, text_x, text_y, text, height, font_size, font_color, label_color, font, direction): + def addlabel(self, image, text_x, text_y, text, height, font_size, font_color, label_color, font, direction, caption=""): batch_size = image.shape[0] width = image.shape[2] @@ -3272,18 +3274,37 @@ ComfyUI/custom_nodes/ComfyUI-KJNodes/fonts font_path = os.path.join(script_directory, "fonts", "TTNorms-Black.otf") else: font_path = folder_paths.get_full_path("kjnodes_fonts", font) - label_image = Image.new("RGB", (width, height), label_color) - draw = ImageDraw.Draw(label_image) - font = ImageFont.truetype(font_path, font_size) - try: - draw.text((text_x, text_y), text, font=font, fill=font_color, features=['-liga']) - except: - draw.text((text_x, text_y), text, font=font, fill=font_color) + + if caption == "": + label_image = Image.new("RGB", (width, height), label_color) + draw = ImageDraw.Draw(label_image) + font = ImageFont.truetype(font_path, font_size) + try: + draw.text((text_x, text_y), text, font=font, fill=font_color, features=['-liga']) + except: + draw.text((text_x, text_y), text, font=font, fill=font_color) - label_image = np.array(label_image).astype(np.float32) / 255.0 - label_image = torch.from_numpy(label_image)[None, :, :, :] - # Duplicate the label image for the entire batch - label_batch = label_image.repeat(batch_size, 1, 1, 1) + label_image = np.array(label_image).astype(np.float32) / 255.0 + label_image = torch.from_numpy(label_image)[None, :, :, :] + # Duplicate the label image for the entire batch + label_batch = label_image.repeat(batch_size, 1, 1, 1) + else: + label_list = [] + assert len(caption) == batch_size, "Number of captions does not match number of images" + for cap in caption: + label_image = Image.new("RGB", (width, height), label_color) + draw = ImageDraw.Draw(label_image) + font = ImageFont.truetype(font_path, font_size) + try: + draw.text((text_x, text_y), cap, font=font, fill=font_color, features=['-liga']) + except: + draw.text((text_x, text_y), cap, font=font, fill=font_color) + + label_image = np.array(label_image).astype(np.float32) / 255.0 + label_image = torch.from_numpy(label_image) + label_list.append(label_image) + label_batch = torch.stack(label_list) + print(label_batch.shape) if direction == 'down': combined_images = torch.cat((image, label_batch), dim=1) From 7c92f70927f6d58eb8639e79bcb95efe0aed5d79 Mon Sep 17 00:00:00 2001 From: Kijai <40791699+kijai@users.noreply.github.com> Date: Wed, 24 Apr 2024 14:23:24 +0300 Subject: [PATCH 21/95] Update nodes.py --- nodes.py | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/nodes.py b/nodes.py index 00110ad..1fe6f75 100644 --- a/nodes.py +++ b/nodes.py @@ -1410,6 +1410,24 @@ Converts any type to a string. else: return return (stringified,) + +class ImagePass: + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "image": ("IMAGE",), + }, + } + RETURN_TYPES = ("IMAGE",) + FUNCTION = "passthrough" + CATEGORY = "KJNodes/misc" + DESCRIPTION = """ +Passes the image through without modifying it. +""" + + def passthrough(self, image): + return image, class Sleep: @classmethod @@ -5177,7 +5195,8 @@ NODE_CLASS_MAPPINGS = { "MaskOrImageToWeight": MaskOrImageToWeight, "WeightScheduleConvert": WeightScheduleConvert, "FloatToMask": FloatToMask, - "CustomSigmas": CustomSigmas + "CustomSigmas": CustomSigmas, + "ImagePass": ImagePass } NODE_DISPLAY_NAME_MAPPINGS = { "INTConstant": "INT Constant", @@ -5264,4 +5283,5 @@ NODE_DISPLAY_NAME_MAPPINGS = { "WeightScheduleConvert": "Weight Schedule Convert", "FloatToMask": "Float To Mask", "CustomSigmas": "Custom Sigmas", + "ImagePass": "ImagePass", } \ No newline at end of file From a0ea96593d528247ac325892e6d7a7cf16ae10a8 Mon Sep 17 00:00:00 2001 From: Kijai <40791699+kijai@users.noreply.github.com> Date: Wed, 24 Apr 2024 14:34:11 +0300 Subject: [PATCH 22/95] Update setgetnodes.js --- web/js/setgetnodes.js | 76 ++++--------------------------------------- 1 file changed, 7 insertions(+), 69 deletions(-) diff --git a/web/js/setgetnodes.js b/web/js/setgetnodes.js index c46a02b..5c2f0a9 100644 --- a/web/js/setgetnodes.js +++ b/web/js/setgetnodes.js @@ -204,31 +204,6 @@ app.registerExtension({ return graph._nodes.filter(otherNode => otherNode.type === 'GetNode' && otherNode.widgets[0].value === name && name !== ''); } - this.lineToGetters = function() { - var scale = canvas.ds.scale; - const getterNodes = this.findGetters(this.graph); - var setPosX = (this.pos[0] + canvas.ds.offset[0]) * scale; - var setPosY = (this.pos[1] + canvas.ds.offset[1]) * scale; - - getterNodes.forEach((getter) => { - var getPosX = (getter.pos[0] + canvas.ds.offset[0]) * scale; - var getPosY = (getter.pos[1] + canvas.ds.offset[1]) * scale; - console.log(getPosX, getPosY, setPosX, setPosY); - - canvas.renderLink( - ctx, - [setPosX, setPosY], - [getPosX, getPosY], - undefined, - true, - 0, - "orange", - LiteGraph.RIGHT, - LiteGraph.LEFT - ); - }); - }; - // This node is purely frontend and does not impact the resulting prompt so should not be serialized this.isVirtualNode = true; } @@ -245,20 +220,14 @@ app.registerExtension({ getExtraMenuOptions(_, options) { options.unshift( { - content: "Line to Getters", + content: "Show connections", callback: () => { - this.lineToGetters(); + this.drawConnection = !this.drawConnection; }, }, ); } - onSelected() { - this.drawConnection = true; - } - onDeselected() { - this.drawConnection = false; - } onDrawForeground(ctx, lGraphCanvas) { if (this.drawConnection) { this._drawVirtualLinks(lGraphCanvas, ctx); @@ -290,7 +259,7 @@ app.registerExtension({ null, false, null, - "#FFF", + "orange", LiteGraph.RIGHT, LiteGraph.LEFT ); @@ -419,32 +388,6 @@ app.registerExtension({ canvas.selectNode(setter, false) canvas.setDirty(true, true); }; - this.isLineVisible = false; - - this.lineToSetter = function() { - this.isLineVisible = !this.isLineVisible; - - console.log(this) - var scale = canvas.ds.scale - const setter = this.findSetter(this.graph); - var getPosX = (this.pos[0] + canvas.ds.offset[0]) * scale - var getPosY = (this.pos[1] + canvas.ds.offset[1]) * scale - var setPosX = (setter.pos[0] + canvas.ds.offset[0]) * scale - var setPosY = (setter.pos[1] + canvas.ds.offset[1]) * scale - console.log(getPosX, getPosY, setPosX, setPosY) - - canvas.renderLink( - ctx, - [getPosX, getPosY], - [setPosX, setPosY], - undefined, - true, - 0, - "orange", - LiteGraph.RIGHT, - LiteGraph.LEFT - ); - }; // This node is purely frontend and does not impact the resulting prompt so should not be serialized this.isVirtualNode = true; @@ -474,19 +417,14 @@ app.registerExtension({ }, }, { - content: "Line to setter", + content: "Show connection", callback: () => { - this.lineToSetter(); + this.drawConnection = !this.drawConnection; }, }, ); } - onSelected() { - this.drawConnection = true; - } - onDeselected() { - this.drawConnection = false; - } + onDrawForeground(ctx, lGraphCanvas) { if (this.drawConnection) { this._drawVirtualLink(lGraphCanvas, ctx); @@ -514,7 +452,7 @@ app.registerExtension({ null, false, null, - "#FFF" + "orange" ); } } From 473ea60f78b98a937097f1e616feafe7bc3c880b Mon Sep 17 00:00:00 2001 From: Kijai <40791699+kijai@users.noreply.github.com> Date: Wed, 24 Apr 2024 17:18:51 +0300 Subject: [PATCH 23/95] Update setgetnodes.js --- web/js/setgetnodes.js | 178 +++++++++++++++++++++++++++++------------- 1 file changed, 124 insertions(+), 54 deletions(-) diff --git a/web/js/setgetnodes.js b/web/js/setgetnodes.js index 5c2f0a9..51e44bd 100644 --- a/web/js/setgetnodes.js +++ b/web/js/setgetnodes.js @@ -21,8 +21,6 @@ function setColorAndBgColor(type) { if (colors) { this.color = colors.color; this.bgcolor = colors.bgcolor; - } else { - // Handle the default case if needed } } let isAlertShown = false; @@ -42,6 +40,10 @@ app.registerExtension({ defaultVisibility = true; serialize_widgets = true; drawConnection = false; + currentGetters = null; + slotColor = "#FFF"; + canvas = app.canvas; + constructor() { if (!this.properties) { this.properties = { @@ -51,8 +53,6 @@ app.registerExtension({ this.properties.showOutputText = SetNode.defaultVisibility; const node = this; - const canvas = app.canvas; - const ctx = canvas.ctx this.addWidget( "text", @@ -204,6 +204,7 @@ app.registerExtension({ return graph._nodes.filter(otherNode => otherNode.type === 'GetNode' && otherNode.widgets[0].value === name && name !== ''); } + // This node is purely frontend and does not impact the resulting prompt so should not be serialized this.isVirtualNode = true; } @@ -218,51 +219,112 @@ app.registerExtension({ }) } getExtraMenuOptions(_, options) { + let menuEntry = this.drawConnection ? "Hide connections" : "Show connections"; options.unshift( { - content: "Show connections", + content: menuEntry, callback: () => { + this.currentGetters = this.findGetters(this.graph); + if (this.currentGetters.length == 0) return; + let linkType = (this.currentGetters[0].outputs[0].type); + this.slotColor = this.canvas.default_connection_color_byType[linkType] + menuEntry = this.drawConnection ? "Hide connections" : "Show connections"; this.drawConnection = !this.drawConnection; + this.canvas.setDirty(true, true); + }, }, ); + // Dynamically add a submenu for all getters + this.currentGetters = this.findGetters(this.graph); + if (this.currentGetters) { + + let gettersSubmenu = this.currentGetters.map(getter => ({ + + content: `${getter.title} id: ${getter.id}`, + callback: () => { + if (this.canvas?.ds?.offset) { + const nodeCenterX = getter.pos[0] + (getter.size[0] / 2); + const nodeCenterY = getter.pos[1] + (getter.size[1] / 2); + + this.canvas.ds.offset[0] = -nodeCenterX + this.canvas.mouse[0]; + this.canvas.ds.offset[1] = -nodeCenterY + this.canvas.mouse[1]; + } + if (this.canvas?.ds?.scale != null) { + this.canvas.ds.scale = Number(1); + } + this.canvas.selectNode(getter, false) + this.canvas.setDirty(true, true); + }, + })); + + options.unshift({ + content: "Getters", + has_submenu: true, + submenu: { + title: "GetNodes", + options: gettersSubmenu, + } + }); + } } + onDrawForeground(ctx, lGraphCanvas) { if (this.drawConnection) { this._drawVirtualLinks(lGraphCanvas, ctx); } } - onDrawCollapsed(ctx, lGraphCanvas) { - if (this.drawConnection) { - this._drawVirtualLinks(lGraphCanvas, ctx); - } - } + // onDrawCollapsed(ctx, lGraphCanvas) { + // if (this.drawConnection) { + // this._drawVirtualLinks(lGraphCanvas, ctx); + // } + // } _drawVirtualLinks(lGraphCanvas, ctx) { - const getters = this.findGetters(this.graph); - if (!getters?.length) return; - // draw the virtual connection from SetNode to GetNode - let start_node_slotpos = [ - this.size[0], - LiteGraph.NODE_TITLE_HEIGHT * 0.5, - ]; - for (const getter of getters) { - let end_node_slotpos = this.getConnectionPos(false, 0); - end_node_slotpos = [ - getter.pos[0] - end_node_slotpos[0] + this.size[0], - getter.pos[1] - end_node_slotpos[1], - ]; - lGraphCanvas.renderLink( - ctx, - start_node_slotpos, - end_node_slotpos, - null, - false, - null, - "orange", - LiteGraph.RIGHT, - LiteGraph.LEFT - ); + if (!this.currentGetters?.length) return; + var title = this.getTitle ? this.getTitle() : this.title; + var title_width = ctx.measureText(title).width; + if (!this.flags.collapsed) { + var start_node_slotpos = [ + this.size[0], + LiteGraph.NODE_TITLE_HEIGHT * 0.5, + ]; + } + else { + + var start_node_slotpos = [ + title_width + 55, + -15, + + ]; + } + + for (const getter of this.currentGetters) { + if (!this.flags.collapsed) { + var end_node_slotpos = this.getConnectionPos(false, 0); + end_node_slotpos = [ + getter.pos[0] - end_node_slotpos[0] + this.size[0], + getter.pos[1] - end_node_slotpos[1] + ]; + } + else { + var end_node_slotpos = this.getConnectionPos(false, 0); + end_node_slotpos = [ + getter.pos[0] - end_node_slotpos[0] + title_width + 50, + getter.pos[1] - end_node_slotpos[1] - 30 + ]; + } + lGraphCanvas.renderLink( + ctx, + start_node_slotpos, + end_node_slotpos, + null, + false, + null, + this.slotColor, + LiteGraph.RIGHT, + LiteGraph.LEFT + ); } } } @@ -286,14 +348,15 @@ app.registerExtension({ defaultVisibility = true; serialize_widgets = true; drawConnection = false; + slotColor = "#FFF"; + currentSetter = null; + canvas = app.canvas; constructor() { if (!this.properties) { this.properties = {}; } this.properties.showOutputText = GetNode.defaultVisibility; - const canvas = app.canvas; - const ctx = canvas.ctx const node = this; this.addWidget( "combo", @@ -375,18 +438,18 @@ app.registerExtension({ this.goToSetter = function() { const setter = this.findSetter(this.graph); - if (canvas?.ds?.offset) { + if (this.canvas?.ds?.offset) { const nodeCenterX = setter.pos[0] + (setter.size[0] / 2); const nodeCenterY = setter.pos[1] + (setter.size[1] / 2); - canvas.ds.offset[0] = -nodeCenterX + canvas.mouse[0]; - canvas.ds.offset[1] = -nodeCenterY + canvas.mouse[1]; + this.canvas.ds.offset[0] = -nodeCenterX + this.canvas.mouse[0]; + this.canvas.ds.offset[1] = -nodeCenterY + this.canvas.mouse[1]; } - if (canvas?.ds?.scale != null) { - canvas.ds.scale = Number(1); + if (this.canvas?.ds?.scale != null) { + this.canvas.ds.scale = Number(1); } - canvas.selectNode(setter, false) - canvas.setDirty(true, true); + this.canvas.selectNode(setter, false) + this.canvas.setDirty(true, true); }; // This node is purely frontend and does not impact the resulting prompt so should not be serialized @@ -409,6 +472,8 @@ app.registerExtension({ onAdded(graph) { } getExtraMenuOptions(_, options) { + let menuEntry = this.drawConnection ? "Hide connections" : "Show connections"; + options.unshift( { content: "Go to setter", @@ -417,9 +482,15 @@ app.registerExtension({ }, }, { - content: "Show connection", + content: menuEntry, callback: () => { + this.currentSetter = this.findSetter(this.graph); + if (this.currentSetter.length == 0) return; + let linkType = (this.currentSetter.inputs[0].type); this.drawConnection = !this.drawConnection; + this.slotColor = this.canvas.default_connection_color_byType[linkType] + menuEntry = this.drawConnection ? "Hide connections" : "Show connections"; + this.canvas.setDirty(true, true); }, }, ); @@ -430,16 +501,15 @@ app.registerExtension({ this._drawVirtualLink(lGraphCanvas, ctx); } } - onDrawCollapsed(ctx, lGraphCanvas) { - if (this.drawConnection) { - this._drawVirtualLink(lGraphCanvas, ctx); - } - } + // onDrawCollapsed(ctx, lGraphCanvas) { + // if (this.drawConnection) { + // this._drawVirtualLink(lGraphCanvas, ctx); + // } + // } _drawVirtualLink(lGraphCanvas, ctx) { - const setter = this.findSetter(this.graph); - if (!setter) return; - // draw the virtual connection from SetNode to GetNode - let start_node_slotpos = setter.getConnectionPos(false, 0); + if (!this.currentSetter) return; + + let start_node_slotpos = this.currentSetter.getConnectionPos(false, 0); start_node_slotpos = [ start_node_slotpos[0] - this.pos[0], start_node_slotpos[1] - this.pos[1], @@ -452,7 +522,7 @@ app.registerExtension({ null, false, null, - "orange" + this.slotColor ); } } From 6cb0e8e334c867f52968b4c8f71380618a55613a Mon Sep 17 00:00:00 2001 From: kijai <40791699+kijai@users.noreply.github.com> Date: Wed, 24 Apr 2024 21:30:07 +0300 Subject: [PATCH 24/95] set/get tweaks --- .gitignore | 3 ++- web/js/setgetnodes.js | 32 +++++++------------------------- 2 files changed, 9 insertions(+), 26 deletions(-) diff --git a/.gitignore b/.gitignore index 587d297..be53a8b 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,5 @@ __pycache__ *.ckpt *.pth types -models \ No newline at end of file +models +jsconfig.json \ No newline at end of file diff --git a/web/js/setgetnodes.js b/web/js/setgetnodes.js index 51e44bd..1601445 100644 --- a/web/js/setgetnodes.js +++ b/web/js/setgetnodes.js @@ -1,5 +1,5 @@ import { app } from "../../../scripts/app.js"; -import { ComfyWidgets } from '../../../scripts/widgets.js'; + //based on diffus3's SetGet: https://github.com/diffus3/ComfyUI-extensions // Nodes that allow you to tunnel connections for cleaner graphs @@ -243,18 +243,10 @@ app.registerExtension({ content: `${getter.title} id: ${getter.id}`, callback: () => { - if (this.canvas?.ds?.offset) { - const nodeCenterX = getter.pos[0] + (getter.size[0] / 2); - const nodeCenterY = getter.pos[1] + (getter.size[1] / 2); - - this.canvas.ds.offset[0] = -nodeCenterX + this.canvas.mouse[0]; - this.canvas.ds.offset[1] = -nodeCenterY + this.canvas.mouse[1]; - } - if (this.canvas?.ds?.scale != null) { - this.canvas.ds.scale = Number(1); - } - this.canvas.selectNode(getter, false) + this.canvas.centerOnNode(getter); + this.canvas.selectNode(getter, false); this.canvas.setDirty(true, true); + }, })); @@ -437,19 +429,9 @@ app.registerExtension({ }; this.goToSetter = function() { - const setter = this.findSetter(this.graph); - if (this.canvas?.ds?.offset) { - const nodeCenterX = setter.pos[0] + (setter.size[0] / 2); - const nodeCenterY = setter.pos[1] + (setter.size[1] / 2); - - this.canvas.ds.offset[0] = -nodeCenterX + this.canvas.mouse[0]; - this.canvas.ds.offset[1] = -nodeCenterY + this.canvas.mouse[1]; - } - if (this.canvas?.ds?.scale != null) { - this.canvas.ds.scale = Number(1); - } - this.canvas.selectNode(setter, false) - this.canvas.setDirty(true, true); + const setter = this.findSetter(this.graph); + this.canvas.centerOnNode(setter); + this.canvas.selectNode(setter, false); }; // This node is purely frontend and does not impact the resulting prompt so should not be serialized From 1f6dfab05d8a9bc0d52393e00d45575b34b075c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jukka=20Sepp=C3=A4nen?= <40791699+kijai@users.noreply.github.com> Date: Thu, 25 Apr 2024 02:21:19 +0300 Subject: [PATCH 25/95] Update README.md --- README.md | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index b4b0f52..a1589bf 100644 --- a/README.md +++ b/README.md @@ -2,9 +2,17 @@ Various quality of life and masking related -nodes and scripts made by combining functionality of existing nodes for ComfyUI. +I know I'm bad at documentation, especially this project that has grown from random practice nodes to... too many lines in one file. +I have however started to add descriptions to the nodes themselves, there's a small ? you can click for info what the node does. +This is still work in progress, like everything else. + # Installation 1. Clone this repo into `custom_nodes` folder. -2. Install dependencies: pip install -r requirements.txt +2. Install dependencies: `pip install -r requirements.txt` + or if you use the portable install, run this in ComfyUI_windows_portable -folder: + + `python_embeded\python.exe -m pip install -r ComfyUI\custom_nodes\ComfyUI-KJNodes\requirements.txt` + ## Javascript @@ -16,7 +24,12 @@ Sets the favicon to green circle when not processing anything, sets it to red wh ### Set/Get Javascript nodes to set and get constants to reduce unnecessary lines. Takes in and returns anything, purely visual nodes. -Could still be buggy, especially when loading workflow with missing nodes, use with precaution. +On the right click menu of these nodes there's now an options to visualize the paths, as well as option to jump to the corresponding node on the other end. + +**Known limitations**: + - Will not work with any node that dynamically sets it's outpute, such as reroute or other Set/Get node + - Will not work when directly connected to a bypassed node + - Other possible conflicts with javascript based nodes. ### ColorToMask From b4480e0ccce40d31e45ac1d82c3280b5903a1668 Mon Sep 17 00:00:00 2001 From: kijai <40791699+kijai@users.noreply.github.com> Date: Thu, 25 Apr 2024 12:25:35 +0300 Subject: [PATCH 26/95] Update setgetnodes.js --- web/js/setgetnodes.js | 36 +++++++++++++++++++++++++++++++++--- 1 file changed, 33 insertions(+), 3 deletions(-) diff --git a/web/js/setgetnodes.js b/web/js/setgetnodes.js index 1601445..ac35f23 100644 --- a/web/js/setgetnodes.js +++ b/web/js/setgetnodes.js @@ -43,6 +43,7 @@ app.registerExtension({ currentGetters = null; slotColor = "#FFF"; canvas = app.canvas; + menuEntry = "Show connections"; constructor() { if (!this.properties) { @@ -219,20 +220,49 @@ app.registerExtension({ }) } getExtraMenuOptions(_, options) { - let menuEntry = this.drawConnection ? "Hide connections" : "Show connections"; + this.menuEntry = this.drawConnection ? "Hide connections" : "Show connections"; options.unshift( { - content: menuEntry, + content: this.menuEntry, callback: () => { this.currentGetters = this.findGetters(this.graph); if (this.currentGetters.length == 0) return; let linkType = (this.currentGetters[0].outputs[0].type); this.slotColor = this.canvas.default_connection_color_byType[linkType] - menuEntry = this.drawConnection ? "Hide connections" : "Show connections"; + this.menuEntry = this.drawConnection ? "Hide connections" : "Show connections"; this.drawConnection = !this.drawConnection; this.canvas.setDirty(true, true); }, + has_submenu: true, + submenu: { + title: "Color", + options: [ + { + content: "Highlight", + callback: () => { + this.slotColor = "orange" + this.canvas.setDirty(true, true); + } + } + ], + }, + }, + { + content: "Hide all connections", + callback: () => { + const allGetters = this.graph._nodes.filter(otherNode => otherNode.type === "GetNode" || otherNode.type === "SetNode"); + allGetters.forEach(otherNode => { + otherNode.drawConnection = false; + console.log(otherNode); + }); + + this.menuEntry = "Show connections"; + this.drawConnection = false + this.canvas.setDirty(true, true); + + }, + }, ); // Dynamically add a submenu for all getters From 460902fdcea5d5d6f54320309a4c824e17d79cf7 Mon Sep 17 00:00:00 2001 From: kijai <40791699+kijai@users.noreply.github.com> Date: Thu, 25 Apr 2024 13:34:34 +0300 Subject: [PATCH 27/95] Update jsnodes.js --- web/js/jsnodes.js | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/web/js/jsnodes.js b/web/js/jsnodes.js index 3ab7d4c..43f8f7b 100644 --- a/web/js/jsnodes.js +++ b/web/js/jsnodes.js @@ -130,6 +130,18 @@ app.registerExtension({ }; break; - } + } + // to keep Set/Get node virtual connections visible when offscreen + const originalComputeVisibleNodes = LGraphCanvas.prototype.computeVisibleNodes; + LGraphCanvas.prototype.computeVisibleNodes = function (nodes, out) { + const visibleNodes = originalComputeVisibleNodes.apply(this, arguments); + const setAndGetNodes = this.graph._nodes.filter(node => node.type === "SetNode" || node.type === "GetNode"); + for (const node of setAndGetNodes) { + if (!visibleNodes.includes(node) && node.drawConnection) { + visibleNodes.push(node); + } + } + return visibleNodes; + }; }, }); \ No newline at end of file From 9b07de5f552f7d221f11605f154928fc5fb5fbc3 Mon Sep 17 00:00:00 2001 From: kijai <40791699+kijai@users.noreply.github.com> Date: Thu, 25 Apr 2024 14:49:20 +0300 Subject: [PATCH 28/95] Add ImageBatchMulti -node --- nodes.py | 32 ++++++++++++++++++++++++++++++++ web/js/jsnodes.js | 23 ++++++++++++++++++++++- 2 files changed, 54 insertions(+), 1 deletion(-) diff --git a/nodes.py b/nodes.py index 1fe6f75..bf67711 100644 --- a/nodes.py +++ b/nodes.py @@ -1066,6 +1066,36 @@ Combines multiple conditioning nodes into one cond = cond_combine_node.combine(new_cond, cond)[0] return (cond, inputcount,) +class ImageBatchMulti: + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "inputcount": ("INT", {"default": 2, "min": 2, "max": 1000, "step": 1}), + "image_1": ("IMAGE", ), + "image_2": ("IMAGE", ), + }, + } + + RETURN_TYPES = ("IMAGE",) + RETURN_NAMES = ("images",) + FUNCTION = "combine" + CATEGORY = "KJNodes/masking/conditioning" + DESCRIPTION = """ +Creates an image batch from multiple images. +You can set how many inputs the node has, +with the **inputcount** and clicking update. +""" + + def combine(self, inputcount, **kwargs): + from nodes import ImageBatch + image_batch_node = ImageBatch() + image = kwargs["image_1"] + for c in range(1, inputcount): + new_image = kwargs[f"image_{c + 1}"] + image, = image_batch_node.batch(new_image, image) + return (image, inputcount,) + class CondPassThrough: @classmethod def INPUT_TYPES(s): @@ -5114,6 +5144,7 @@ Each mask is generated with the specified width and height. NODE_CLASS_MAPPINGS = { "INTConstant": INTConstant, "FloatConstant": FloatConstant, + "ImageBatchMulti": ImageBatchMulti, "ConditioningMultiCombine": ConditioningMultiCombine, "ConditioningSetMaskAndCombine": ConditioningSetMaskAndCombine, "ConditioningSetMaskAndCombine3": ConditioningSetMaskAndCombine3, @@ -5201,6 +5232,7 @@ NODE_CLASS_MAPPINGS = { NODE_DISPLAY_NAME_MAPPINGS = { "INTConstant": "INT Constant", "FloatConstant": "Float Constant", + "ImageBatchMulti": "Image Batch Multi", "ConditioningMultiCombine": "Conditioning Multi Combine", "ConditioningSetMaskAndCombine": "ConditioningSetMaskAndCombine", "ConditioningSetMaskAndCombine3": "ConditioningSetMaskAndCombine3", diff --git a/web/js/jsnodes.js b/web/js/jsnodes.js index 43f8f7b..49e1ec2 100644 --- a/web/js/jsnodes.js +++ b/web/js/jsnodes.js @@ -6,7 +6,6 @@ app.registerExtension({ switch (nodeData.name) { case "ConditioningMultiCombine": nodeType.prototype.onNodeCreated = function () { - //this.inputs_offset = nodeData.name.includes("selective")?1:0 this.cond_type = "CONDITIONING" this.inputs_offset = nodeData.name.includes("selective")?1:0 this.addWidget("button", "Update inputs", null, () => { @@ -27,6 +26,28 @@ app.registerExtension({ }); } break; + case "ImageBatchMulti": + nodeType.prototype.onNodeCreated = function () { + this._type = "IMAGE" + this.inputs_offset = nodeData.name.includes("selective")?1:0 + this.addWidget("button", "Update inputs", null, () => { + if (!this.inputs) { + this.inputs = []; + } + const target_number_of_inputs = this.widgets.find(w => w.name === "inputcount")["value"]; + if(target_number_of_inputs===this.inputs.length)return; // already set, do nothing + + if(target_number_of_inputs < this.inputs.length){ + for(let i = this.inputs.length; i>=this.inputs_offset+target_number_of_inputs; i--) + this.removeInput(i) + } + else{ + for(let i = this.inputs.length+1-this.inputs_offset; i <= target_number_of_inputs; ++i) + this.addInput(`image_${i}`, this._type) + } + }); + } + break; case "SoundReactive": nodeType.prototype.onNodeCreated = function () { let audioContext; From b99d27311fe83d6c917f6f7248a4e6e82d8e6ce5 Mon Sep 17 00:00:00 2001 From: kijai <40791699+kijai@users.noreply.github.com> Date: Thu, 25 Apr 2024 15:34:16 +0300 Subject: [PATCH 29/95] Add MaskBatchMulti -node --- nodes.py | 36 ++++++++++++++++++++++++++++++++++-- web/js/jsnodes.js | 22 ++++++++++++++++++++++ 2 files changed, 56 insertions(+), 2 deletions(-) diff --git a/nodes.py b/nodes.py index bf67711..49d1aec 100644 --- a/nodes.py +++ b/nodes.py @@ -1080,7 +1080,7 @@ class ImageBatchMulti: RETURN_TYPES = ("IMAGE",) RETURN_NAMES = ("images",) FUNCTION = "combine" - CATEGORY = "KJNodes/masking/conditioning" + CATEGORY = "KJNodes/image" DESCRIPTION = """ Creates an image batch from multiple images. You can set how many inputs the node has, @@ -1094,7 +1094,37 @@ with the **inputcount** and clicking update. for c in range(1, inputcount): new_image = kwargs[f"image_{c + 1}"] image, = image_batch_node.batch(new_image, image) - return (image, inputcount,) + return (image,) + +class MaskBatchMulti: + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "inputcount": ("INT", {"default": 2, "min": 2, "max": 1000, "step": 1}), + "mask_1": ("MASK", ), + "mask_2": ("MASK", ), + }, + } + + RETURN_TYPES = ("MASK",) + RETURN_NAMES = ("masks",) + FUNCTION = "combine" + CATEGORY = "KJNodes/masking" + DESCRIPTION = """ +Creates an image batch from multiple masks. +You can set how many inputs the node has, +with the **inputcount** and clicking update. +""" + + def combine(self, inputcount, **kwargs): + mask = kwargs["mask_1"] + for c in range(1, inputcount): + new_mask = kwargs[f"mask_{c + 1}"] + if mask.shape[1:] != new_mask.shape[1:]: + new_mask = F.interpolate(new_mask.unsqueeze(1), size=(mask.shape[1], mask.shape[2]), mode="bicubic").squeeze(1) + mask = torch.cat((mask, new_mask), dim=0) + return (mask,) class CondPassThrough: @classmethod @@ -5145,6 +5175,7 @@ NODE_CLASS_MAPPINGS = { "INTConstant": INTConstant, "FloatConstant": FloatConstant, "ImageBatchMulti": ImageBatchMulti, + "MaskBatchMulti": MaskBatchMulti, "ConditioningMultiCombine": ConditioningMultiCombine, "ConditioningSetMaskAndCombine": ConditioningSetMaskAndCombine, "ConditioningSetMaskAndCombine3": ConditioningSetMaskAndCombine3, @@ -5233,6 +5264,7 @@ NODE_DISPLAY_NAME_MAPPINGS = { "INTConstant": "INT Constant", "FloatConstant": "Float Constant", "ImageBatchMulti": "Image Batch Multi", + "MaskBatchMulti": "Mask Batch Multi", "ConditioningMultiCombine": "Conditioning Multi Combine", "ConditioningSetMaskAndCombine": "ConditioningSetMaskAndCombine", "ConditioningSetMaskAndCombine3": "ConditioningSetMaskAndCombine3", diff --git a/web/js/jsnodes.js b/web/js/jsnodes.js index 49e1ec2..91ab055 100644 --- a/web/js/jsnodes.js +++ b/web/js/jsnodes.js @@ -48,6 +48,28 @@ app.registerExtension({ }); } break; + case "MaskBatchMulti": + nodeType.prototype.onNodeCreated = function () { + this._type = "MASK" + this.inputs_offset = nodeData.name.includes("selective")?1:0 + this.addWidget("button", "Update inputs", null, () => { + if (!this.inputs) { + this.inputs = []; + } + const target_number_of_inputs = this.widgets.find(w => w.name === "inputcount")["value"]; + if(target_number_of_inputs===this.inputs.length)return; // already set, do nothing + + if(target_number_of_inputs < this.inputs.length){ + for(let i = this.inputs.length; i>=this.inputs_offset+target_number_of_inputs; i--) + this.removeInput(i) + } + else{ + for(let i = this.inputs.length+1-this.inputs_offset; i <= target_number_of_inputs; ++i) + this.addInput(`mask_${i}`, this._type) + } + }); + } + break; case "SoundReactive": nodeType.prototype.onNodeCreated = function () { let audioContext; From ddc4753a03d275c522e740316adf7df9a7b7b22d Mon Sep 17 00:00:00 2001 From: kijai <40791699+kijai@users.noreply.github.com> Date: Thu, 25 Apr 2024 17:58:04 +0300 Subject: [PATCH 30/95] Update spline_editor.js --- web/js/spline_editor.js | 32 ++++++++++++++++++++++++++++---- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/web/js/spline_editor.js b/web/js/spline_editor.js index e327f66..c99149b 100644 --- a/web/js/spline_editor.js +++ b/web/js/spline_editor.js @@ -160,6 +160,30 @@ function createSplineEditor(context, reset=false) { const pointsStoreWidget = context.widgets.find(w => w.name === "points_store"); const tensionWidget = context.widgets.find(w => w.name === "tension"); const segmentedWidget = context.widgets.find(w => w.name === "segmented"); + + var interpolation = interpolationWidget.value + var tension = tensionWidget.value + var points_to_sample = pointsWidget.value + interpolationWidget.callback = () => { + interpolation = interpolationWidget.value + vis.render(); + } + + tensionWidget.callback = () => { + tension = tensionWidget.value + vis.render(); + } + + pointsWidget.callback = () => { + points_to_sample = pointsWidget.value + let coords = samplePoints(pathElements[0], points_to_sample); + let coordsString = JSON.stringify(coords); + pointsStoreWidget.value = JSON.stringify(points); + if (coordWidget) { + coordWidget.value = coordsString; + vis.render(); + } +} // Initialize or reset points array var w = 512 @@ -203,7 +227,7 @@ function createSplineEditor(context, reset=false) { }) .event("mouseup", function() { if (this.pathElements !== null) { - let coords = samplePoints(pathElements[0], pointsWidget.value); + let coords = samplePoints(pathElements[0], points_to_sample); let coordsString = JSON.stringify(coords); pointsStoreWidget.value = JSON.stringify(points); if (coordWidget) { @@ -222,9 +246,9 @@ function createSplineEditor(context, reset=false) { .data(() => points) .left(d => d.x) .top(d => d.y) - .interpolate(() => interpolationWidget.value) - .tension(() => tensionWidget.value) - .segmented(() => segmentedWidget.value) + .interpolate(() => interpolation) + .tension(() => tension) + .segmented(() => false) .strokeStyle(pv.Colors.category10().by(pv.index)) .lineWidth(3) From 785463387b5671181bcd5613cfbba5c17fbb192f Mon Sep 17 00:00:00 2001 From: kijai <40791699+kijai@users.noreply.github.com> Date: Thu, 25 Apr 2024 18:14:30 +0300 Subject: [PATCH 31/95] Update spline_editor.js --- web/js/spline_editor.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/web/js/spline_editor.js b/web/js/spline_editor.js index c99149b..b9611d2 100644 --- a/web/js/spline_editor.js +++ b/web/js/spline_editor.js @@ -171,8 +171,15 @@ function createSplineEditor(context, reset=false) { tensionWidget.callback = () => { tension = tensionWidget.value + points_to_sample = pointsWidget.value + let coords = samplePoints(pathElements[0], points_to_sample); + let coordsString = JSON.stringify(coords); + pointsStoreWidget.value = JSON.stringify(points); + if (coordWidget) { + coordWidget.value = coordsString; vis.render(); } +} pointsWidget.callback = () => { points_to_sample = pointsWidget.value From 6fa8990ba866faa434e172edfce40d992507999f Mon Sep 17 00:00:00 2001 From: kijai <40791699+kijai@users.noreply.github.com> Date: Thu, 25 Apr 2024 18:16:29 +0300 Subject: [PATCH 32/95] Update nodes.py --- nodes.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/nodes.py b/nodes.py index 49d1aec..0bdfa47 100644 --- a/nodes.py +++ b/nodes.py @@ -4797,6 +4797,10 @@ guaranteed!! ## Graphical editor to create values for various ## schedules and/or mask batches. +Shift + click to add control points. +Right click to delete control points. +Note that you can't delete from start/end. + **points_to_sample** value sets the number of samples returned from the **drawn spline itself**, this is independent from the actual control points, so the interpolation type matters. From 83d0f9ff451103b0322fb422ba3963746498f1f4 Mon Sep 17 00:00:00 2001 From: kijai <40791699+kijai@users.noreply.github.com> Date: Thu, 25 Apr 2024 19:35:13 +0300 Subject: [PATCH 33/95] Update spline_editor.js --- web/js/spline_editor.js | 32 ++++++++++++++++++++++++++++---- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/web/js/spline_editor.js b/web/js/spline_editor.js index b9611d2..8245798 100644 --- a/web/js/spline_editor.js +++ b/web/js/spline_editor.js @@ -259,6 +259,9 @@ function createSplineEditor(context, reset=false) { .strokeStyle(pv.Colors.category10().by(pv.index)) .lineWidth(3) + var hoverIndex = -1; + var isDragging + vis.add(pv.Dot) .data(() => points) .left(d => d.x) @@ -270,20 +273,41 @@ function createSplineEditor(context, reset=false) { .event("mousedown", pv.Behavior.drag()) .event("dragstart", function() { i = this.index; + hoverIndex = this.index; + isDragging = true; if (pv.event.button === 2 && i !== 0 && i !== points.length - 1) { points.splice(i--, 1); vis.render(); } return this; }) + .event("dragend", function() { + isDragging = false; + }) .event("drag", vis) - .anchor("top").add(pv.Label) - .font(d => Math.sqrt(d[2]) * 32 + "px sans-serif") - //.text(d => `(${Math.round(d.x)}, ${Math.round(d.y)})`) + .event("mouseover", function() { + hoverIndex = this.index; // Set the hover index to the index of the hovered dot + vis.render(); // Re-render the visualization + }) + .event("mouseout", function() { + !isDragging && (hoverIndex = -1); // Reset the hover index when the mouse leaves the dot + vis.render(); // Re-render the visualization + }) + .anchor("center") + .add(pv.Label) + .visible(function() { + return hoverIndex === this.index; // Only show the label for the hovered dot + }) + .left(d => d.x < w / 2 ? d.x + 80 : d.x - 70) // Shift label to right if on left half, otherwise shift to left + .top(d => d.y < h / 2 ? d.y + 20 : d.y - 20) // Shift label down if on top half, otherwise shift up + + .font(12 + "px Consolas") .text(d => { // Normalize y to range 0.0 to 1.0, considering the inverted y-axis var normalizedY = 1.0 - (d.y / h); - return `${normalizedY.toFixed(2)}`; + var normalizedX = (d.x / w); + var frame = Math.round((d.x / w) * points_to_sample); + return `F: ${frame}, X: ${normalizedX.toFixed(2)}, Y: ${normalizedY.toFixed(2)}`; }) .textStyle("orange") From f2dd09aa125885d5c1d5d853eb10691d62821850 Mon Sep 17 00:00:00 2001 From: kijai <40791699+kijai@users.noreply.github.com> Date: Thu, 25 Apr 2024 19:36:37 +0300 Subject: [PATCH 34/95] Update spline_editor.js --- web/js/spline_editor.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/js/spline_editor.js b/web/js/spline_editor.js index 8245798..f1638b1 100644 --- a/web/js/spline_editor.js +++ b/web/js/spline_editor.js @@ -266,7 +266,7 @@ function createSplineEditor(context, reset=false) { .data(() => points) .left(d => d.x) .top(d => d.y) - .radius(8) + .radius(10) .cursor("move") .strokeStyle(function() { return i == this.index ? "#ff7f0e" : "#1f77b4"; }) .fillStyle(function() { return "rgba(100, 100, 100, 0.2)"; }) From 501c1df1bcd05f829c26dc639cf2e0dc70da03ea Mon Sep 17 00:00:00 2001 From: kijai <40791699+kijai@users.noreply.github.com> Date: Thu, 25 Apr 2024 20:32:50 +0300 Subject: [PATCH 35/95] Update jsnodes.js --- web/js/jsnodes.js | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/web/js/jsnodes.js b/web/js/jsnodes.js index 91ab055..906f26e 100644 --- a/web/js/jsnodes.js +++ b/web/js/jsnodes.js @@ -177,14 +177,13 @@ app.registerExtension({ // to keep Set/Get node virtual connections visible when offscreen const originalComputeVisibleNodes = LGraphCanvas.prototype.computeVisibleNodes; LGraphCanvas.prototype.computeVisibleNodes = function (nodes, out) { - const visibleNodes = originalComputeVisibleNodes.apply(this, arguments); - const setAndGetNodes = this.graph._nodes.filter(node => node.type === "SetNode" || node.type === "GetNode"); - for (const node of setAndGetNodes) { - if (!visibleNodes.includes(node) && node.drawConnection) { - visibleNodes.push(node); + const visibleNodesSet = new Set(originalComputeVisibleNodes.apply(this, arguments)); + for (const node of this.graph._nodes) { + if ((node.type === "SetNode" || node.type === "GetNode") && node.drawConnection) { + visibleNodesSet.add(node); } } - return visibleNodes; + return Array.from(visibleNodesSet); }; }, }); \ No newline at end of file From 0920c127be08598f0181d2369da42a4957d8747b Mon Sep 17 00:00:00 2001 From: kijai <40791699+kijai@users.noreply.github.com> Date: Thu, 25 Apr 2024 22:48:50 +0300 Subject: [PATCH 36/95] Update jsnodes.js --- web/js/jsnodes.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/web/js/jsnodes.js b/web/js/jsnodes.js index 906f26e..02fdc43 100644 --- a/web/js/jsnodes.js +++ b/web/js/jsnodes.js @@ -173,10 +173,13 @@ app.registerExtension({ }; break; - } + } + + }, + async setup() { // to keep Set/Get node virtual connections visible when offscreen const originalComputeVisibleNodes = LGraphCanvas.prototype.computeVisibleNodes; - LGraphCanvas.prototype.computeVisibleNodes = function (nodes, out) { + LGraphCanvas.prototype.computeVisibleNodes = function () { const visibleNodesSet = new Set(originalComputeVisibleNodes.apply(this, arguments)); for (const node of this.graph._nodes) { if ((node.type === "SetNode" || node.type === "GetNode") && node.drawConnection) { @@ -185,5 +188,6 @@ app.registerExtension({ } return Array.from(visibleNodesSet); }; - }, + + } }); \ No newline at end of file From 6e0784801a933d57af44db8c2ed759263c8a2f7c Mon Sep 17 00:00:00 2001 From: kijai <40791699+kijai@users.noreply.github.com> Date: Thu, 25 Apr 2024 23:28:16 +0300 Subject: [PATCH 37/95] Spline editor cleanup --- nodes.py | 2 +- web/js/spline_editor.js | 39 +++++++++++++++------------------------ 2 files changed, 16 insertions(+), 25 deletions(-) diff --git a/nodes.py b/nodes.py index 0bdfa47..3024295 100644 --- a/nodes.py +++ b/nodes.py @@ -4755,7 +4755,7 @@ class SplineEditor: "coordinates": ("STRING", {"multiline": False}), "mask_width": ("INT", {"default": 512, "min": 8, "max": MAX_RESOLUTION, "step": 8}), "mask_height": ("INT", {"default": 512, "min": 8, "max": MAX_RESOLUTION, "step": 8}), - "points_to_sample": ("INT", {"default": 4, "min": 2, "max": 1000, "step": 1}), + "points_to_sample": ("INT", {"default": 16, "min": 2, "max": 1000, "step": 1}), "interpolation": ( [ 'cardinal', diff --git a/web/js/spline_editor.js b/web/js/spline_editor.js index f1638b1..133e469 100644 --- a/web/js/spline_editor.js +++ b/web/js/spline_editor.js @@ -150,10 +150,21 @@ app.registerExtension({ function createSplineEditor(context, reset=false) { console.log("creatingSplineEditor") + + function updatePath() { + points_to_sample = pointsWidget.value + let coords = samplePoints(pathElements[0], points_to_sample); + let coordsString = JSON.stringify(coords); + pointsStoreWidget.value = JSON.stringify(points); + if (coordWidget) { + coordWidget.value = coordsString; + } + vis.render(); + } if (reset && context.splineEditor.element) { context.splineEditor.element.innerHTML = ''; // Clear the container - } + } const coordWidget = context.widgets.find(w => w.name === "coordinates"); const interpolationWidget = context.widgets.find(w => w.name === "interpolation"); const pointsWidget = context.widgets.find(w => w.name === "points_to_sample"); @@ -166,31 +177,16 @@ function createSplineEditor(context, reset=false) { var points_to_sample = pointsWidget.value interpolationWidget.callback = () => { interpolation = interpolationWidget.value - vis.render(); } tensionWidget.callback = () => { tension = tensionWidget.value - points_to_sample = pointsWidget.value - let coords = samplePoints(pathElements[0], points_to_sample); - let coordsString = JSON.stringify(coords); - pointsStoreWidget.value = JSON.stringify(points); - if (coordWidget) { - coordWidget.value = coordsString; - vis.render(); + updatePath(); } -} pointsWidget.callback = () => { - points_to_sample = pointsWidget.value - let coords = samplePoints(pathElements[0], points_to_sample); - let coordsString = JSON.stringify(coords); - pointsStoreWidget.value = JSON.stringify(points); - if (coordWidget) { - coordWidget.value = coordsString; - vis.render(); + updatePath(); } -} // Initialize or reset points array var w = 512 @@ -234,12 +230,7 @@ function createSplineEditor(context, reset=false) { }) .event("mouseup", function() { if (this.pathElements !== null) { - let coords = samplePoints(pathElements[0], points_to_sample); - let coordsString = JSON.stringify(coords); - pointsStoreWidget.value = JSON.stringify(points); - if (coordWidget) { - coordWidget.value = coordsString; - } + updatePath(); } }); From 8c5d9ae4ad5f19725864d6de5a79c8511cfa3b3c Mon Sep 17 00:00:00 2001 From: kijai <40791699+kijai@users.noreply.github.com> Date: Fri, 26 Apr 2024 01:18:36 +0300 Subject: [PATCH 38/95] Repeat output for spline editor --- nodes.py | 23 ++++++++++------------- web/js/spline_editor.js | 2 +- 2 files changed, 11 insertions(+), 14 deletions(-) diff --git a/nodes.py b/nodes.py index 3024295..cf1d5b2 100644 --- a/nodes.py +++ b/nodes.py @@ -4771,7 +4771,7 @@ class SplineEditor: "default": 'cardinal' }), "tension": ("FLOAT", {"default": 0.5, "min": 0.0, "max": 1.0, "step": 0.01}), - "segmented": ("BOOLEAN", {"default": False}), + "repeat_output": ("INT", {"default": 1, "min": 1, "max": 4096, "step": 1}), "float_output_type": ( [ 'list', @@ -4822,37 +4822,34 @@ output types: example compatible nodes: unknown """ - def splinedata(self, mask_width, mask_height, coordinates, float_output_type, interpolation, points_to_sample, points_store, tension, segmented): + def splinedata(self, mask_width, mask_height, coordinates, float_output_type, interpolation, points_to_sample, points_store, tension, repeat_output): coordinates = json.loads(coordinates) - normalized_y_values = [ 1.0 - (point['y'] / 512) for point in coordinates ] if float_output_type == 'list': - out_floats = normalized_y_values + out_floats = normalized_y_values * repeat_output elif float_output_type == 'list of lists': - out_floats = [[value] for value in normalized_y_values], + out_floats = ([[value] for value in normalized_y_values] * repeat_output), elif float_output_type == 'pandas series': try: import pandas as pd except: raise Exception("MaskOrImageToWeight: pandas is not installed. Please install pandas to use this output_type") - out_floats = pd.Series(normalized_y_values), + out_floats = pd.Series(normalized_y_values * repeat_output), elif float_output_type == 'tensor': - out_floats = torch.tensor(normalized_y_values, dtype=torch.float32) + out_floats = torch.tensor(normalized_y_values * repeat_output, dtype=torch.float32) # Create a color map for grayscale intensities color_map = lambda y: torch.full((mask_height, mask_width, 3), y, dtype=torch.float32) # Create image tensors for each normalized y value - image_tensors = [color_map(y) for y in normalized_y_values] - - # Batch the tensors - masks_out = torch.stack(image_tensors) + mask_tensors = [color_map(y) for y in normalized_y_values] + masks_out = torch.stack(mask_tensors) + masks_out = masks_out.repeat(repeat_output, 1, 1, 1) masks_out = masks_out.mean(dim=-1) - print(masks_out.shape) - return (masks_out, coordinates, out_floats,) + return (masks_out, str(coordinates), out_floats,) class StabilityAPI_SD3: diff --git a/web/js/spline_editor.js b/web/js/spline_editor.js index 133e469..a1c966d 100644 --- a/web/js/spline_editor.js +++ b/web/js/spline_editor.js @@ -170,7 +170,7 @@ function createSplineEditor(context, reset=false) { const pointsWidget = context.widgets.find(w => w.name === "points_to_sample"); const pointsStoreWidget = context.widgets.find(w => w.name === "points_store"); const tensionWidget = context.widgets.find(w => w.name === "tension"); - const segmentedWidget = context.widgets.find(w => w.name === "segmented"); + //const segmentedWidget = context.widgets.find(w => w.name === "segmented"); var interpolation = interpolationWidget.value var tension = tensionWidget.value From 0298e965d267977993715daa226fd2591d824ed8 Mon Sep 17 00:00:00 2001 From: kijai <40791699+kijai@users.noreply.github.com> Date: Fri, 26 Apr 2024 01:38:40 +0300 Subject: [PATCH 39/95] Some js optimization --- web/js/help_popup.js | 4 +- web/js/jsnodes.js | 83 +++++++++++++++++++++-------------------- web/js/spline_editor.js | 7 ++-- 3 files changed, 48 insertions(+), 46 deletions(-) diff --git a/web/js/help_popup.js b/web/js/help_popup.js index 9567222..c4620e5 100644 --- a/web/js/help_popup.js +++ b/web/js/help_popup.js @@ -45,6 +45,7 @@ loadScript('/kjweb_async/purify.min.js').catch((e) => { console.log(e) }) +const categories = ["KJNodes", "SUPIR", "VoiceCraft", "Marigold"]; app.registerExtension({ name: "KJNodes.HelpPopup", async beforeRegisterNodeDef(nodeType, nodeData) { @@ -52,13 +53,12 @@ app.registerExtension({ if (app.ui.settings.getSettingValue("KJNodes.helpPopup") === false) { return; } - - const categories = ["KJNodes", "SUPIR", "VoiceCraft", "Marigold"]; try { categories.forEach(category => { if (nodeData?.category?.startsWith(category)) { addDocumentation(nodeData, nodeType); } + else return }); } catch (error) { console.error("Error in registering KJNodes.HelpPopup", error); diff --git a/web/js/jsnodes.js b/web/js/jsnodes.js index 02fdc43..a20a7a7 100644 --- a/web/js/jsnodes.js +++ b/web/js/jsnodes.js @@ -3,6 +3,9 @@ import { app } from "../../../scripts/app.js"; app.registerExtension({ name: "KJNodes.jsnodes", async beforeRegisterNodeDef(nodeType, nodeData, app) { + if(!nodeData?.category?.startsWith("KJNodes")) { + return; + } switch (nodeData.name) { case "ConditioningMultiCombine": nodeType.prototype.onNodeCreated = function () { @@ -23,51 +26,51 @@ app.registerExtension({ for(let i = this.inputs.length+1-this.inputs_offset; i <= target_number_of_inputs; ++i) this.addInput(`conditioning_${i}`, this.cond_type) } - }); + }); } break; - case "ImageBatchMulti": - nodeType.prototype.onNodeCreated = function () { - this._type = "IMAGE" - this.inputs_offset = nodeData.name.includes("selective")?1:0 - this.addWidget("button", "Update inputs", null, () => { - if (!this.inputs) { - this.inputs = []; - } - const target_number_of_inputs = this.widgets.find(w => w.name === "inputcount")["value"]; - if(target_number_of_inputs===this.inputs.length)return; // already set, do nothing - - if(target_number_of_inputs < this.inputs.length){ - for(let i = this.inputs.length; i>=this.inputs_offset+target_number_of_inputs; i--) - this.removeInput(i) - } - else{ - for(let i = this.inputs.length+1-this.inputs_offset; i <= target_number_of_inputs; ++i) - this.addInput(`image_${i}`, this._type) - } - }); + case "ImageBatchMulti": + nodeType.prototype.onNodeCreated = function () { + this._type = "IMAGE" + this.inputs_offset = nodeData.name.includes("selective")?1:0 + this.addWidget("button", "Update inputs", null, () => { + if (!this.inputs) { + this.inputs = []; } - break; - case "MaskBatchMulti": - nodeType.prototype.onNodeCreated = function () { - this._type = "MASK" - this.inputs_offset = nodeData.name.includes("selective")?1:0 - this.addWidget("button", "Update inputs", null, () => { - if (!this.inputs) { - this.inputs = []; + const target_number_of_inputs = this.widgets.find(w => w.name === "inputcount")["value"]; + if(target_number_of_inputs===this.inputs.length)return; // already set, do nothing + + if(target_number_of_inputs < this.inputs.length){ + for(let i = this.inputs.length; i>=this.inputs_offset+target_number_of_inputs; i--) + this.removeInput(i) + } + else{ + for(let i = this.inputs.length+1-this.inputs_offset; i <= target_number_of_inputs; ++i) + this.addInput(`image_${i}`, this._type) } - const target_number_of_inputs = this.widgets.find(w => w.name === "inputcount")["value"]; - if(target_number_of_inputs===this.inputs.length)return; // already set, do nothing - - if(target_number_of_inputs < this.inputs.length){ - for(let i = this.inputs.length; i>=this.inputs_offset+target_number_of_inputs; i--) - this.removeInput(i) - } - else{ - for(let i = this.inputs.length+1-this.inputs_offset; i <= target_number_of_inputs; ++i) - this.addInput(`mask_${i}`, this._type) - } }); + } + break; + case "MaskBatchMulti": + nodeType.prototype.onNodeCreated = function () { + this._type = "MASK" + this.inputs_offset = nodeData.name.includes("selective")?1:0 + this.addWidget("button", "Update inputs", null, () => { + if (!this.inputs) { + this.inputs = []; + } + const target_number_of_inputs = this.widgets.find(w => w.name === "inputcount")["value"]; + if(target_number_of_inputs===this.inputs.length)return; // already set, do nothing + + if(target_number_of_inputs < this.inputs.length){ + for(let i = this.inputs.length; i>=this.inputs_offset+target_number_of_inputs; i--) + this.removeInput(i) + } + else{ + for(let i = this.inputs.length+1-this.inputs_offset; i <= target_number_of_inputs; ++i) + this.addInput(`mask_${i}`, this._type) + } + }); } break; case "SoundReactive": diff --git a/web/js/spline_editor.js b/web/js/spline_editor.js index a1c966d..ad12cad 100644 --- a/web/js/spline_editor.js +++ b/web/js/spline_editor.js @@ -101,7 +101,7 @@ app.registerExtension({ name: 'KJNodes.SplineEditor', async beforeRegisterNodeDef(nodeType, nodeData) { - if (nodeData?.name == 'SplineEditor') { + if (nodeData?.name === 'SplineEditor') { chainCallback(nodeType.prototype, "onNodeCreated", function () { hideWidgetForGood(this, this.widgets.find(w => w.name === "coordinates")) @@ -123,7 +123,7 @@ app.registerExtension({ createSplineEditor(this, true) } }); - this.setSize([550, 850]) + this.setSize([550, 840]) this.splineEditor.parentEl = document.createElement("div"); this.splineEditor.parentEl.className = "spline-editor"; this.splineEditor.parentEl.id = `spline-editor-${this.uuid}` @@ -138,12 +138,11 @@ app.registerExtension({ }) chainCallback(this, "onGraphConfigured", function() { createSplineEditor(this) - this.setSize([550, 800]) + this.setSize([550, 840]) }); }); // onAfterGraphConfigured }//node created - } //before register })//register From 3275b332f3097f5d076d33deceba9629a570e4ee Mon Sep 17 00:00:00 2001 From: Kijai <40791699+kijai@users.noreply.github.com> Date: Fri, 26 Apr 2024 12:59:26 +0300 Subject: [PATCH 40/95] clamp ColorToMask --- nodes.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/nodes.py b/nodes.py index cf1d5b2..fdef63f 100644 --- a/nodes.py +++ b/nodes.py @@ -1036,6 +1036,7 @@ controls the number of images processed at once. pbar.update(batch_count) tensors_out = torch.cat(tensors_out, dim=0) + tensors_out = torch.clamp(tensors_out, min=0.0, max=1.0) return tensors_out, class ConditioningMultiCombine: @@ -4965,7 +4966,6 @@ If no image is provided, mode is set to text-to-image if model != "sd3-turbo": data["negative_prompt"] = n_prompt - headers={ "accept": "image/*" } @@ -5081,8 +5081,6 @@ class WeightScheduleConvert: "default": 'list' }), }, - - } RETURN_TYPES = ("FLOAT",) FUNCTION = "execute" From c7b3a856153e25a0fb5755e17d17300e061a0eac Mon Sep 17 00:00:00 2001 From: Kijai <40791699+kijai@users.noreply.github.com> Date: Fri, 26 Apr 2024 18:24:51 +0300 Subject: [PATCH 41/95] Spline editor updates --- nodes.py | 131 ++++++++++++++++++++++++++++++++++++++-- web/js/spline_editor.js | 124 ++++++++++++++++++++++++++++++++++--- 2 files changed, 242 insertions(+), 13 deletions(-) diff --git a/nodes.py b/nodes.py index fdef63f..759f62c 100644 --- a/nodes.py +++ b/nodes.py @@ -2851,7 +2851,7 @@ Grow value is the amount to grow the shape on each frame, creating animated mask image = pil2tensor(image) mask = image[:, :, :, 0] out.append(mask) - outstack = torch.cat(out, dim=0) + outstack = torch.cat(out, dim=0) return (outstack, 1.0 - outstack,) class CreateVoronoiMask: @@ -4826,6 +4826,9 @@ output types: def splinedata(self, mask_width, mask_height, coordinates, float_output_type, interpolation, points_to_sample, points_store, tension, repeat_output): coordinates = json.loads(coordinates) + for coord in coordinates: + coord['x'] = int(round(coord['x'])) + coord['y'] = int(round(coord['y'])) normalized_y_values = [ 1.0 - (point['y'] / 512) for point in coordinates @@ -4851,7 +4854,87 @@ output types: masks_out = masks_out.repeat(repeat_output, 1, 1, 1) masks_out = masks_out.mean(dim=-1) return (masks_out, str(coordinates), out_floats,) + +class CreateShapeMaskOnPath: + RETURN_TYPES = ("MASK", "MASK",) + RETURN_NAMES = ("mask", "mask_inverted",) + FUNCTION = "createshapemask" + CATEGORY = "KJNodes/masking/generate" + DESCRIPTION = """ +Creates a mask or batch of masks with the specified shape. +Locations are center locations. +Grow value is the amount to grow the shape on each frame, creating animated masks. +""" + + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "shape": ( + [ 'circle', + 'square', + 'triangle', + ], + { + "default": 'circle' + }), + "coordinates": ("STRING", {"forceInput": True}), + "grow": ("INT", {"default": 0, "min": -512, "max": 512, "step": 1}), + "frame_width": ("INT", {"default": 512,"min": 16, "max": 4096, "step": 1}), + "frame_height": ("INT", {"default": 512,"min": 16, "max": 4096, "step": 1}), + "shape_width": ("INT", {"default": 128,"min": 8, "max": 4096, "step": 1}), + "shape_height": ("INT", {"default": 128,"min": 8, "max": 4096, "step": 1}), + }, + } + + def createshapemask(self, coordinates, frame_width, frame_height, shape_width, shape_height, grow, shape): + # Define the number of images in the batch + coordinates = coordinates.replace("'", '"') + coordinates = json.loads(coordinates) + for coord in coordinates: + print(coord) + + batch_size = len(coordinates) + print(batch_size) + out = [] + color = "white" + + for i, coord in enumerate(coordinates): + image = Image.new("RGB", (frame_width, frame_height), "black") + draw = ImageDraw.Draw(image) + + # Calculate the size for this frame and ensure it's not less than 0 + current_width = max(0, shape_width + i*grow) + current_height = max(0, shape_height + i*grow) + + location_x = coord['x'] + location_y = coord['y'] + + if shape == 'circle' or shape == 'square': + # Define the bounding box for the shape + left_up_point = (location_x - current_width // 2, location_y - current_height // 2) + right_down_point = (location_x + current_width // 2, location_y + current_height // 2) + two_points = [left_up_point, right_down_point] + + if shape == 'circle': + draw.ellipse(two_points, fill=color) + elif shape == 'square': + draw.rectangle(two_points, fill=color) + + elif shape == 'triangle': + # Define the points for the triangle + left_up_point = (location_x - current_width // 2, location_y + current_height // 2) # bottom left + right_down_point = (location_x + current_width // 2, location_y + current_height // 2) # bottom right + top_point = (location_x, location_y - current_height // 2) # top point + draw.polygon([top_point, left_up_point, right_down_point], fill=color) + + image = pil2tensor(image) + mask = image[:, :, :, 0] + out.append(mask) + outstack = torch.cat(out, dim=0) + return (outstack, 1.0 - outstack,) + class StabilityAPI_SD3: @classmethod @@ -5072,6 +5155,7 @@ class WeightScheduleConvert: "input_values": ("FLOAT", {"default": 0.0, "forceInput": True}), "output_type": ( [ + 'match_input', 'list', 'list of lists', 'pandas series', @@ -5080,7 +5164,14 @@ class WeightScheduleConvert: { "default": 'list' }), + "invert": ("BOOLEAN", {"default": False}), + "repeat": ("INT", {"default": 1,"min": 1, "max": 255, "step": 1}), }, + "optional": { + "remap_to_frames": ("INT", {"default": 0}), + "interpolation_curve": ("FLOAT", {"forceInput": True}), + }, + } RETURN_TYPES = ("FLOAT",) FUNCTION = "execute" @@ -5102,9 +5193,8 @@ Converts different value lists/series to another type. else: raise ValueError("Unsupported input type") - def execute(self, input_values, output_type): + def execute(self, input_values, output_type, invert, repeat, remap_to_frames=0, interpolation_curve=None): import pandas as pd - # Detect the input type input_type = self.detect_input_type(input_values) # Convert input_values to a list of floats @@ -5117,6 +5207,35 @@ Converts different value lists/series to another type. else: float_values = input_values + if invert: + float_values = [1 - value for value in float_values] + + if interpolation_curve is not None: + interpolated_pattern = [] + orig_float_values = float_values + for value in interpolation_curve: + print(value) + min_val = min(orig_float_values) + max_val = max(orig_float_values) + # Normalize the values to [0, 1] + normalized_values = [(value - min_val) / (max_val - min_val) for value in orig_float_values] + # Interpolate the normalized values to the new frame count + remapped_float_values = np.interp(np.linspace(0, 1, int(remap_to_frames * value)), np.linspace(0, 1, len(normalized_values)), normalized_values).tolist() + interpolated_pattern.append(remapped_float_values) + print(interpolated_pattern) + float_values = interpolated_pattern + else: + # Remap float_values to match target_frame_amount + if remap_to_frames > 0 and remap_to_frames != len(float_values): + min_val = min(float_values) + max_val = max(float_values) + # Normalize the values to [0, 1] + normalized_values = [(value - min_val) / (max_val - min_val) for value in float_values] + # Interpolate the normalized values to the new frame count + float_values = np.interp(np.linspace(0, 1, remap_to_frames), np.linspace(0, 1, len(normalized_values)), normalized_values).tolist() + + float_values = float_values * repeat + if output_type == 'list': return float_values, elif output_type == 'list of lists': @@ -5126,6 +5245,8 @@ Converts different value lists/series to another type. elif output_type == 'tensor': if input_type == 'pandas series': return torch.tensor(input_values.values, dtype=torch.float32), + elif output_type == 'match_input': + return float_values, else: raise ValueError(f"Unsupported output_type: {output_type}") @@ -5257,7 +5378,8 @@ NODE_CLASS_MAPPINGS = { "WeightScheduleConvert": WeightScheduleConvert, "FloatToMask": FloatToMask, "CustomSigmas": CustomSigmas, - "ImagePass": ImagePass + "ImagePass": ImagePass, + "CreateShapeMaskOnPath": CreateShapeMaskOnPath } NODE_DISPLAY_NAME_MAPPINGS = { "INTConstant": "INT Constant", @@ -5347,4 +5469,5 @@ NODE_DISPLAY_NAME_MAPPINGS = { "FloatToMask": "Float To Mask", "CustomSigmas": "Custom Sigmas", "ImagePass": "ImagePass", + "CreateShapeMaskOnPath": "CreateShapeMaskOnPath", } \ No newline at end of file diff --git a/web/js/spline_editor.js b/web/js/spline_editor.js index ad12cad..78f42d2 100644 --- a/web/js/spline_editor.js +++ b/web/js/spline_editor.js @@ -113,6 +113,58 @@ app.registerExtension({ serialize: false, hideOnZoom: false, }); + + // context menu + + this.contextMenu = document.createElement("div"); + this.contextMenu.id = "context-menu"; + this.contextMenu.style.display = "none"; + this.contextMenu.style.position = "absolute"; + this.contextMenu.style.backgroundColor = "#202020"; + this.contextMenu.style.minWidth = "100px"; + this.contextMenu.style.boxShadow = "0px 8px 16px 0px rgba(0,0,0,0.2)"; + this.contextMenu.style.zIndex = "100"; + this.contextMenu.style.padding = "5px"; + + function styleMenuItem(menuItem) { + menuItem.style.display = "block"; + menuItem.style.padding = "5px"; + menuItem.style.color = "#FFF"; + menuItem.style.fontFamily = "Arial, sans-serif"; + menuItem.style.fontSize = "16px"; + menuItem.style.textDecoration = "none"; + menuItem.style.marginBottom = "5px"; + } + this.menuItem1 = document.createElement("a"); + this.menuItem1.href = "#"; + this.menuItem1.id = "menu-item-1"; + this.menuItem1.textContent = "Toggle handles"; + styleMenuItem(this.menuItem1); + + this.menuItem2 = document.createElement("a"); + this.menuItem2.href = "#"; + this.menuItem2.id = "menu-item-2"; + this.menuItem2.textContent = "Placeholder"; + styleMenuItem(this.menuItem2); + // Add hover effect to menu items + this.menuItem1.addEventListener('mouseover', function() { + this.style.backgroundColor = "gray"; + }); + this.menuItem1.addEventListener('mouseout', function() { + this.style.backgroundColor = "#202020"; + }); + + this.menuItem2.addEventListener('mouseover', function() { + this.style.backgroundColor = "gray"; + }); + this.menuItem2.addEventListener('mouseout', function() { + this.style.backgroundColor = "#202020"; + }); + + this.contextMenu.appendChild(this.menuItem1); + this.contextMenu.appendChild(this.menuItem2); + + document.body.appendChild( this.contextMenu); this.addWidget("button", "New spline", null, () => { if (!this.properties || !("points" in this.properties)) { @@ -129,18 +181,20 @@ app.registerExtension({ this.splineEditor.parentEl.id = `spline-editor-${this.uuid}` element.appendChild(this.splineEditor.parentEl); - //disable context menu on right click - document.addEventListener('contextmenu', function(e) { - if (e.button === 2) { // Right mouse button - e.preventDefault(); - e.stopPropagation(); - } - }) chainCallback(this, "onGraphConfigured", function() { + console.log('onGraphConfigured'); createSplineEditor(this) this.setSize([550, 840]) }); + //disable context menu on right click + // document.addEventListener('contextmenu', function(e) { + // if (e.button === 2) { // Right mouse button + // e.preventDefault(); + // e.stopPropagation(); + // } + // }) + }); // onAfterGraphConfigured }//node created } //before register @@ -150,6 +204,51 @@ app.registerExtension({ function createSplineEditor(context, reset=false) { console.log("creatingSplineEditor") + document.addEventListener('contextmenu', function(e) { + e.preventDefault(); + }); + + document.addEventListener('click', function(e) { + if (!context.contextMenu.contains(e.target)) { + context.contextMenu.style.display = 'none'; + } + }); + + context.menuItem1.addEventListener('click', function(e) { + e.preventDefault(); + if (!drawHandles) { + drawHandles = true + vis.add(pv.Line) + .data(() => points.map((point, index) => ({ + start: point, + end: [index] + }))) + .left(d => d.start.x) + .top(d => d.start.y) + .interpolate("linear") + .tension(0) // Straight lines + .strokeStyle("#ff7f0e") // Same color as control points + .lineWidth(1) + .visible(() => drawHandles); + vis.render(); + + + } else { + drawHandles = false + vis.render(); + } + context.contextMenu.style.display = 'none'; + + }); + + context.menuItem2.addEventListener('click', function(e) { + e.preventDefault(); + // Add functionality for menu item 2 + console.log('Option 2 clicked'); + }); + + + function updatePath() { points_to_sample = pointsWidget.value let coords = samplePoints(pathElements[0], points_to_sample); @@ -188,6 +287,7 @@ function createSplineEditor(context, reset=false) { } // Initialize or reset points array + var drawHandles = false var w = 512 var h = 512 var i = 3 @@ -226,6 +326,11 @@ function createSplineEditor(context, reset=false) { i = points.push(this.mouse()) - 1; return this; } + else if (pv.event.button === 2) { + context.contextMenu.style.display = 'block'; + context.contextMenu.style.left = `${pv.event.clientX}px`; + context.contextMenu.style.top = `${pv.event.clientY}px`; + } }) .event("mouseup", function() { if (this.pathElements !== null) { @@ -237,7 +342,7 @@ function createSplineEditor(context, reset=false) { .data(pv.range(0, 8, .5)) .bottom(d => d * 64 + 0) .strokeStyle("gray") - .lineWidth(1) + .lineWidth(2) vis.add(pv.Line) .data(() => points) @@ -291,7 +396,7 @@ function createSplineEditor(context, reset=false) { .left(d => d.x < w / 2 ? d.x + 80 : d.x - 70) // Shift label to right if on left half, otherwise shift to left .top(d => d.y < h / 2 ? d.y + 20 : d.y - 20) // Shift label down if on top half, otherwise shift up - .font(12 + "px Consolas") + .font(12 + "px sans-serif") .text(d => { // Normalize y to range 0.0 to 1.0, considering the inverted y-axis var normalizedY = 1.0 - (d.y / h); @@ -301,6 +406,7 @@ function createSplineEditor(context, reset=false) { }) .textStyle("orange") + vis.render(); var svgElement = vis.canvas(); svgElement.style['zIndex'] = "2" From c1f2fca3342c5c0d6f14c8a245adb72ba7d12f28 Mon Sep 17 00:00:00 2001 From: Kijai <40791699+kijai@users.noreply.github.com> Date: Fri, 26 Apr 2024 18:26:07 +0300 Subject: [PATCH 42/95] Update nodes.py --- nodes.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/nodes.py b/nodes.py index 759f62c..ecaf203 100644 --- a/nodes.py +++ b/nodes.py @@ -5214,7 +5214,6 @@ Converts different value lists/series to another type. interpolated_pattern = [] orig_float_values = float_values for value in interpolation_curve: - print(value) min_val = min(orig_float_values) max_val = max(orig_float_values) # Normalize the values to [0, 1] @@ -5222,7 +5221,6 @@ Converts different value lists/series to another type. # Interpolate the normalized values to the new frame count remapped_float_values = np.interp(np.linspace(0, 1, int(remap_to_frames * value)), np.linspace(0, 1, len(normalized_values)), normalized_values).tolist() interpolated_pattern.append(remapped_float_values) - print(interpolated_pattern) float_values = interpolated_pattern else: # Remap float_values to match target_frame_amount From 27b3d3e3a3f01774ad0c85bd00cb90ddb0eab81a Mon Sep 17 00:00:00 2001 From: kijai <40791699+kijai@users.noreply.github.com> Date: Sat, 27 Apr 2024 01:02:19 +0300 Subject: [PATCH 43/95] Update nodes.py --- nodes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nodes.py b/nodes.py index ecaf203..bc18dae 100644 --- a/nodes.py +++ b/nodes.py @@ -5220,7 +5220,7 @@ Converts different value lists/series to another type. normalized_values = [(value - min_val) / (max_val - min_val) for value in orig_float_values] # Interpolate the normalized values to the new frame count remapped_float_values = np.interp(np.linspace(0, 1, int(remap_to_frames * value)), np.linspace(0, 1, len(normalized_values)), normalized_values).tolist() - interpolated_pattern.append(remapped_float_values) + interpolated_pattern.extend(remapped_float_values) float_values = interpolated_pattern else: # Remap float_values to match target_frame_amount From 59d11b615db1004d659e01bf78f8fbd0be41297d Mon Sep 17 00:00:00 2001 From: kijai <40791699+kijai@users.noreply.github.com> Date: Sat, 27 Apr 2024 02:08:17 +0300 Subject: [PATCH 44/95] JS fixes and features --- web/js/browserstatus.js | 20 ++++++++++++++++---- web/js/contextmenu.js | 16 ++++++++++------ web/js/help_popup.js | 22 ++++++++++++++++++---- web/js/spline_editor.js | 10 +++++----- 4 files changed, 49 insertions(+), 19 deletions(-) diff --git a/web/js/browserstatus.js b/web/js/browserstatus.js index c796b6f..fd377e7 100644 --- a/web/js/browserstatus.js +++ b/web/js/browserstatus.js @@ -4,6 +4,9 @@ import { app } from "../../../scripts/app.js"; app.registerExtension({ name: "KJNodes.browserstatus", setup() { + if (!app.ui.settings.getSettingValue("KJNodes.browserStatus")) { + return; + } api.addEventListener("status", ({ detail }) => { let title = "ComfyUI"; let favicon = "green"; @@ -11,7 +14,6 @@ app.registerExtension({ if (queueRemaining) { favicon = "red"; - title = `00% - ${queueRemaining} | ${title}`; } let link = document.querySelector("link[rel~='icon']"); @@ -22,9 +24,8 @@ app.registerExtension({ } link.href = new URL(`../${favicon}.png`, import.meta.url); document.title = title; - }); -//add progress to the title + //add progress to the title api.addEventListener("progress", ({ detail }) => { const { value, max } = detail; const progress = Math.floor((value / max) * 100); @@ -34,8 +35,19 @@ app.registerExtension({ const paddedProgress = String(progress).padStart(2, '0'); title = `${paddedProgress}% ${title.replace(/^\d+%\s/, '')}`; } - document.title = title; }); }, + init() { + if (!app.ui.settings.getSettingValue("KJNodes.browserStatus")) { + return; + } + const pythongossFeed = app.extensions.find( + (e) => e.name === 'pysssss.FaviconStatus', + ) + if (pythongossFeed) { + console.warn("KJNodes - Overriding pysssss.FaviconStatus") + app.extensions = app.extensions.filter(item => item !== pythongossFeed); + } + }, }); \ No newline at end of file diff --git a/web/js/contextmenu.js b/web/js/contextmenu.js index 0b05d7d..636358e 100644 --- a/web/js/contextmenu.js +++ b/web/js/contextmenu.js @@ -1,7 +1,5 @@ import { app } from "../../../scripts/app.js"; - -var nodeAutoColor = true // Adds context menu entries, code partly from pyssssscustom-scripts function addMenuHandler(nodeType, cb) { @@ -45,13 +43,9 @@ app.registerExtension({ content: "Add SetNode", callback: () => {addNode("SetNode", this, { side:"right", offset: 30 }); }, - }); }); - } - - }, async setup(app) { const onChange = (value) => { @@ -144,5 +138,15 @@ app.registerExtension({ { value: false, text: "Off", selected: value === false }, ], }); + app.ui.settings.addSetting({ + id: "KJNodes.browserStatus", + name: "🦛 KJNodes: 🟢 Stoplight browser status icon 🔴", + defaultValue: false, + type: "boolean", + options: (value) => [ + { value: true, text: "On", selected: value === true }, + { value: false, text: "Off", selected: value === false }, + ], + }); } }); diff --git a/web/js/help_popup.js b/web/js/help_popup.js index c4620e5..00f21bf 100644 --- a/web/js/help_popup.js +++ b/web/js/help_popup.js @@ -182,13 +182,16 @@ const create_documentation_stylesheet = () => { let startX, startY, startWidth, startHeight resizeHandle.addEventListener('mousedown', function (e) { + e.preventDefault(); e.stopPropagation(); isResizing = true; startX = e.clientX; startY = e.clientY; startWidth = parseInt(document.defaultView.getComputedStyle(docElement).width, 10); startHeight = parseInt(document.defaultView.getComputedStyle(docElement).height, 10); - }); + }, + { signal: this.docCtrl.signal }, + ); // close button const closeButton = document.createElement('div'); @@ -208,7 +211,9 @@ const create_documentation_stylesheet = () => { this.show_doc = !this.show_doc docElement.parentNode.removeChild(docElement) docElement = null - }); + }, + { signal: this.docCtrl.signal }, + ); document.addEventListener('mousemove', function (e) { if (!isResizing) return; @@ -216,11 +221,15 @@ const create_documentation_stylesheet = () => { const newHeight = startHeight + e.clientY - startY; docElement.style.width = `${newWidth}px`; docElement.style.height = `${newHeight}px`; - }); + }, + { signal: this.docCtrl.signal }, + ); document.addEventListener('mouseup', function () { isResizing = false - }) + }, + { signal: this.docCtrl.signal }, + ) document.body.appendChild(docElement) } @@ -283,6 +292,11 @@ const create_documentation_stylesheet = () => { } else { this.show_doc = !this.show_doc } + if (this.show_doc) { + this.docCtrl = new AbortController() + } else { + this.docCtrl.abort() + } return true; } return r; diff --git a/web/js/spline_editor.js b/web/js/spline_editor.js index 78f42d2..420eeeb 100644 --- a/web/js/spline_editor.js +++ b/web/js/spline_editor.js @@ -144,7 +144,7 @@ app.registerExtension({ this.menuItem2 = document.createElement("a"); this.menuItem2.href = "#"; this.menuItem2.id = "menu-item-2"; - this.menuItem2.textContent = "Placeholder"; + this.menuItem2.textContent = "Copy coordinates to clipboard"; styleMenuItem(this.menuItem2); // Add hover effect to menu items this.menuItem1.addEventListener('mouseover', function() { @@ -243,8 +243,8 @@ function createSplineEditor(context, reset=false) { context.menuItem2.addEventListener('click', function(e) { e.preventDefault(); - // Add functionality for menu item 2 - console.log('Option 2 clicked'); + navigator.clipboard.writeText(JSON.stringify(points)); + console.log('Copied coordinates to clipboard'); }); @@ -275,6 +275,7 @@ function createSplineEditor(context, reset=false) { var points_to_sample = pointsWidget.value interpolationWidget.callback = () => { interpolation = interpolationWidget.value + updatePath(); } tensionWidget.callback = () => { @@ -405,8 +406,7 @@ function createSplineEditor(context, reset=false) { return `F: ${frame}, X: ${normalizedX.toFixed(2)}, Y: ${normalizedY.toFixed(2)}`; }) .textStyle("orange") - - + vis.render(); var svgElement = vis.canvas(); svgElement.style['zIndex'] = "2" From c9813da89292750c44a8bc4e158857d5db6caade Mon Sep 17 00:00:00 2001 From: kijai <40791699+kijai@users.noreply.github.com> Date: Sat, 27 Apr 2024 02:46:04 +0300 Subject: [PATCH 45/95] Update spline_editor.js --- web/js/spline_editor.js | 37 ++++++++++++++++++++++++++++++++----- 1 file changed, 32 insertions(+), 5 deletions(-) diff --git a/web/js/spline_editor.js b/web/js/spline_editor.js index 420eeeb..537a53c 100644 --- a/web/js/spline_editor.js +++ b/web/js/spline_editor.js @@ -144,7 +144,7 @@ app.registerExtension({ this.menuItem2 = document.createElement("a"); this.menuItem2.href = "#"; this.menuItem2.id = "menu-item-2"; - this.menuItem2.textContent = "Copy coordinates to clipboard"; + this.menuItem2.textContent = "Display sample points"; styleMenuItem(this.menuItem2); // Add hover effect to menu items this.menuItem1.addEventListener('mouseover', function() { @@ -243,15 +243,39 @@ function createSplineEditor(context, reset=false) { context.menuItem2.addEventListener('click', function(e) { e.preventDefault(); - navigator.clipboard.writeText(JSON.stringify(points)); - console.log('Copied coordinates to clipboard'); + drawSamplePoints = !drawSamplePoints; + + updatePath(); }); - + var drawSamplePoints = false; function updatePath() { points_to_sample = pointsWidget.value let coords = samplePoints(pathElements[0], points_to_sample); + if (drawSamplePoints) { + if (pointsLayer) { + // Update the data of the existing points layer + pointsLayer.data(coords); + } else { + // Create the points layer if it doesn't exist + pointsLayer = vis.add(pv.Dot) + .data(coords) + .left(function(d) { return d.x; }) + .top(function(d) { return d.y; }) + .radius(5) // Adjust the radius as needed + .fillStyle("red") // Change the color as needed + .strokeStyle("black") // Change the stroke color as needed + .lineWidth(1); // Adjust the line width as needed + } + } + else { + if (pointsLayer) { + // Remove the points layer + pointsLayer.data([]); + vis.render(); + } + } let coordsString = JSON.stringify(coords); pointsStoreWidget.value = JSON.stringify(points); if (coordWidget) { @@ -273,6 +297,8 @@ function createSplineEditor(context, reset=false) { var interpolation = interpolationWidget.value var tension = tensionWidget.value var points_to_sample = pointsWidget.value + var pointsLayer = null; + interpolationWidget.callback = () => { interpolation = interpolationWidget.value updatePath(); @@ -357,7 +383,7 @@ function createSplineEditor(context, reset=false) { var hoverIndex = -1; var isDragging - + vis.add(pv.Dot) .data(() => points) .left(d => d.x) @@ -406,6 +432,7 @@ function createSplineEditor(context, reset=false) { return `F: ${frame}, X: ${normalizedX.toFixed(2)}, Y: ${normalizedY.toFixed(2)}`; }) .textStyle("orange") + vis.render(); var svgElement = vis.canvas(); From ae0eb9be676258de32db910fbd6c6981144b9a0f Mon Sep 17 00:00:00 2001 From: kijai <40791699+kijai@users.noreply.github.com> Date: Sat, 27 Apr 2024 11:28:38 +0300 Subject: [PATCH 46/95] Start restructuring and add custom range to spline editor output --- __init__.py | 184 ++++++++++++- curve_nodes.py | 399 ++++++++++++++++++++++++++++ nodes.py | 572 +--------------------------------------- web/js/spline_editor.js | 20 +- 4 files changed, 597 insertions(+), 578 deletions(-) create mode 100644 curve_nodes.py diff --git a/__init__.py b/__init__.py index 138b9c7..f2a02ae 100644 --- a/__init__.py +++ b/__init__.py @@ -1,4 +1,186 @@ -from .nodes import NODE_CLASS_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS +from .nodes import * +from .curve_nodes import * +NODE_CLASS_MAPPINGS = { + "INTConstant": INTConstant, + "FloatConstant": FloatConstant, + "ImageBatchMulti": ImageBatchMulti, + "MaskBatchMulti": MaskBatchMulti, + "ConditioningMultiCombine": ConditioningMultiCombine, + "ConditioningSetMaskAndCombine": ConditioningSetMaskAndCombine, + "ConditioningSetMaskAndCombine3": ConditioningSetMaskAndCombine3, + "ConditioningSetMaskAndCombine4": ConditioningSetMaskAndCombine4, + "ConditioningSetMaskAndCombine5": ConditioningSetMaskAndCombine5, + "GrowMaskWithBlur": GrowMaskWithBlur, + "ColorToMask": ColorToMask, + "CreateGradientMask": CreateGradientMask, + "CreateTextMask": CreateTextMask, + "CreateAudioMask": CreateAudioMask, + "CreateFadeMask": CreateFadeMask, + "CreateFadeMaskAdvanced": CreateFadeMaskAdvanced, + "CreateFluidMask" :CreateFluidMask, + "VRAM_Debug" : VRAM_Debug, + "SomethingToString" : SomethingToString, + "CrossFadeImages": CrossFadeImages, + "EmptyLatentImagePresets": EmptyLatentImagePresets, + "ColorMatch": ColorMatch, + "GetImageRangeFromBatch": GetImageRangeFromBatch, + "SaveImageWithAlpha": SaveImageWithAlpha, + "ReverseImageBatch": ReverseImageBatch, + "ImageGridComposite2x2": ImageGridComposite2x2, + "ImageGridComposite3x3": ImageGridComposite3x3, + "ImageConcanate": ImageConcanate, + "ImageBatchTestPattern": ImageBatchTestPattern, + "ReplaceImagesInBatch": ReplaceImagesInBatch, + "BatchCropFromMask": BatchCropFromMask, + "BatchCropFromMaskAdvanced": BatchCropFromMaskAdvanced, + "FilterZeroMasksAndCorrespondingImages": FilterZeroMasksAndCorrespondingImages, + "InsertImageBatchByIndexes": InsertImageBatchByIndexes, + "BatchUncrop": BatchUncrop, + "BatchUncropAdvanced": BatchUncropAdvanced, + "BatchCLIPSeg": BatchCLIPSeg, + "RoundMask": RoundMask, + "ResizeMask": ResizeMask, + "OffsetMask": OffsetMask, + "WidgetToString": WidgetToString, + "CreateShapeMask": CreateShapeMask, + "CreateVoronoiMask": CreateVoronoiMask, + "CreateMagicMask": CreateMagicMask, + "BboxToInt": BboxToInt, + "SplitBboxes": SplitBboxes, + "ImageGrabPIL": ImageGrabPIL, + "DummyLatentOut": DummyLatentOut, + "FlipSigmasAdjusted": FlipSigmasAdjusted, + "InjectNoiseToLatent": InjectNoiseToLatent, + "AddLabel": AddLabel, + "SoundReactive": SoundReactive, + "GenerateNoise": GenerateNoise, + "StableZero123_BatchSchedule": StableZero123_BatchSchedule, + "SV3D_BatchSchedule": SV3D_BatchSchedule, + "GetImagesFromBatchIndexed": GetImagesFromBatchIndexed, + "InsertImagesToBatchIndexed": InsertImagesToBatchIndexed, + "ImageBatchRepeatInterleaving": ImageBatchRepeatInterleaving, + "NormalizedAmplitudeToMask": NormalizedAmplitudeToMask, + "OffsetMaskByNormalizedAmplitude": OffsetMaskByNormalizedAmplitude, + "ImageTransformByNormalizedAmplitude": ImageTransformByNormalizedAmplitude, + "GetLatentsFromBatchIndexed": GetLatentsFromBatchIndexed, + "StringConstant": StringConstant, + "GLIGENTextBoxApplyBatch": GLIGENTextBoxApplyBatch, + "CondPassThrough": CondPassThrough, + "ImageUpscaleWithModelBatched": ImageUpscaleWithModelBatched, + "ScaleBatchPromptSchedule": ScaleBatchPromptSchedule, + "ImageNormalize_Neg1_To_1": ImageNormalize_Neg1_To_1, + "Intrinsic_lora_sampling": Intrinsic_lora_sampling, + "RemapMaskRange": RemapMaskRange, + "LoadResAdapterNormalization": LoadResAdapterNormalization, + "Superprompt": Superprompt, + "RemapImageRange": RemapImageRange, + "CameraPoseVisualizer": CameraPoseVisualizer, + "BboxVisualize": BboxVisualize, + "StringConstantMultiline": StringConstantMultiline, + "JoinStrings": JoinStrings, + "Sleep": Sleep, + "ImagePadForOutpaintMasked": ImagePadForOutpaintMasked, + "ImageAndMaskPreview": ImageAndMaskPreview, + "StabilityAPI_SD3": StabilityAPI_SD3, + "MaskOrImageToWeight": MaskOrImageToWeight, + "WeightScheduleConvert": WeightScheduleConvert, + "FloatToMask": FloatToMask, + "CustomSigmas": CustomSigmas, + "ImagePass": ImagePass, + "SplineEditor": SplineEditor, + "CreateShapeMaskOnPath": CreateShapeMaskOnPath + +} +NODE_DISPLAY_NAME_MAPPINGS = { + "INTConstant": "INT Constant", + "FloatConstant": "Float Constant", + "ImageBatchMulti": "Image Batch Multi", + "MaskBatchMulti": "Mask Batch Multi", + "ConditioningMultiCombine": "Conditioning Multi Combine", + "ConditioningSetMaskAndCombine": "ConditioningSetMaskAndCombine", + "ConditioningSetMaskAndCombine3": "ConditioningSetMaskAndCombine3", + "ConditioningSetMaskAndCombine4": "ConditioningSetMaskAndCombine4", + "ConditioningSetMaskAndCombine5": "ConditioningSetMaskAndCombine5", + "GrowMaskWithBlur": "GrowMaskWithBlur", + "ColorToMask": "ColorToMask", + "CreateGradientMask": "CreateGradientMask", + "CreateTextMask" : "CreateTextMask", + "CreateFadeMask" : "CreateFadeMask (Deprecated)", + "CreateFadeMaskAdvanced" : "CreateFadeMaskAdvanced", + "CreateFluidMask" : "CreateFluidMask", + "CreateAudioMask" : "CreateAudioMask (Deprecated)", + "VRAM_Debug" : "VRAM Debug", + "CrossFadeImages": "CrossFadeImages", + "SomethingToString": "SomethingToString", + "EmptyLatentImagePresets": "EmptyLatentImagePresets", + "ColorMatch": "ColorMatch", + "GetImageRangeFromBatch": "GetImageRangeFromBatch", + "InsertImagesToBatchIndexed": "InsertImagesToBatchIndexed", + "SaveImageWithAlpha": "SaveImageWithAlpha", + "ReverseImageBatch": "ReverseImageBatch", + "ImageGridComposite2x2": "ImageGridComposite2x2", + "ImageGridComposite3x3": "ImageGridComposite3x3", + "ImageConcanate": "ImageConcatenate", + "ImageBatchTestPattern": "ImageBatchTestPattern", + "ReplaceImagesInBatch": "ReplaceImagesInBatch", + "BatchCropFromMask": "BatchCropFromMask", + "BatchCropFromMaskAdvanced": "BatchCropFromMaskAdvanced", + "FilterZeroMasksAndCorrespondingImages": "FilterZeroMasksAndCorrespondingImages", + "InsertImageBatchByIndexes": "InsertImageBatchByIndexes", + "BatchUncrop": "BatchUncrop", + "BatchUncropAdvanced": "BatchUncropAdvanced", + "BatchCLIPSeg": "BatchCLIPSeg", + "RoundMask": "RoundMask", + "ResizeMask": "ResizeMask", + "OffsetMask": "OffsetMask", + "WidgetToString": "WidgetToString", + "CreateShapeMask": "CreateShapeMask", + "CreateVoronoiMask": "CreateVoronoiMask", + "CreateMagicMask": "CreateMagicMask", + "BboxToInt": "BboxToInt", + "SplitBboxes": "SplitBboxes", + "ImageGrabPIL": "ImageGrabPIL", + "DummyLatentOut": "DummyLatentOut", + "FlipSigmasAdjusted": "FlipSigmasAdjusted", + "InjectNoiseToLatent": "InjectNoiseToLatent", + "AddLabel": "AddLabel", + "SoundReactive": "SoundReactive", + "GenerateNoise": "GenerateNoise", + "StableZero123_BatchSchedule": "StableZero123_BatchSchedule", + "SV3D_BatchSchedule": "SV3D_BatchSchedule", + "GetImagesFromBatchIndexed": "GetImagesFromBatchIndexed", + "ImageBatchRepeatInterleaving": "ImageBatchRepeatInterleaving", + "NormalizedAmplitudeToMask": "NormalizedAmplitudeToMask", + "OffsetMaskByNormalizedAmplitude": "OffsetMaskByNormalizedAmplitude", + "ImageTransformByNormalizedAmplitude": "ImageTransformByNormalizedAmplitude", + "GetLatentsFromBatchIndexed": "GetLatentsFromBatchIndexed", + "StringConstant": "StringConstant", + "GLIGENTextBoxApplyBatch": "GLIGENTextBoxApplyBatch", + "CondPassThrough": "CondPassThrough", + "ImageUpscaleWithModelBatched": "ImageUpscaleWithModelBatched", + "ScaleBatchPromptSchedule": "ScaleBatchPromptSchedule", + "ImageNormalize_Neg1_To_1": "ImageNormalize_Neg1_To_1", + "Intrinsic_lora_sampling": "Intrinsic_lora_sampling", + "RemapMaskRange": "RemapMaskRange", + "LoadResAdapterNormalization": "LoadResAdapterNormalization", + "Superprompt": "Superprompt", + "RemapImageRange": "RemapImageRange", + "CameraPoseVisualizer": "CameraPoseVisualizer", + "BboxVisualize": "BboxVisualize", + "StringConstantMultiline": "StringConstantMultiline", + "JoinStrings": "JoinStrings", + "Sleep": "🛌 Sleep 🛌", + "ImagePadForOutpaintMasked": "Pad Image For Outpaint Masked", + "ImageAndMaskPreview": "Image & Mask Preview", + "StabilityAPI_SD3": "Stability API SD3", + "MaskOrImageToWeight": "Mask Or Image To Weight", + "WeightScheduleConvert": "Weight Schedule Convert", + "FloatToMask": "Float To Mask", + "CustomSigmas": "Custom Sigmas", + "ImagePass": "ImagePass", + "SplineEditor": "Spline Editor", + "CreateShapeMaskOnPath": "Create Shape Mask On Path" +} __all__ = ["NODE_CLASS_MAPPINGS", "NODE_DISPLAY_NAME_MAPPINGS", "WEB_DIRECTORY"] WEB_DIRECTORY = "./web" diff --git a/curve_nodes.py b/curve_nodes.py new file mode 100644 index 0000000..0c45454 --- /dev/null +++ b/curve_nodes.py @@ -0,0 +1,399 @@ +import torch +import json +from PIL import Image, ImageDraw +import numpy as np +from .utility import pil2tensor + +class SplineEditor: + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "points_store": ("STRING", {"multiline": False}), + "coordinates": ("STRING", {"multiline": False}), + "mask_width": ("INT", {"default": 512, "min": 8, "max": 4096, "step": 8}), + "mask_height": ("INT", {"default": 512, "min": 8, "max": 4096, "step": 8}), + "points_to_sample": ("INT", {"default": 16, "min": 2, "max": 1000, "step": 1}), + "interpolation": ( + [ + 'cardinal', + 'monotone', + 'basis', + 'linear', + 'step-before', + 'step-after', + 'polar', + 'polar-reverse', + ], + { + "default": 'cardinal' + }), + "tension": ("FLOAT", {"default": 0.5, "min": 0.0, "max": 1.0, "step": 0.01}), + "repeat_output": ("INT", {"default": 1, "min": 1, "max": 4096, "step": 1}), + "float_output_type": ( + [ + 'list', + 'list of lists', + 'pandas series', + 'tensor', + ], + { + "default": 'list' + }), + }, + "optional": { + "min_value": ("FLOAT", {"default": 0.0, "min": -10000.0, "max": 10000.0, "step": 0.01}), + "max_value": ("FLOAT", {"default": 1.0, "min": -10000.0, "max": 10000.0, "step": 0.01}), + } + } + + RETURN_TYPES = ("MASK", "STRING", "FLOAT") + FUNCTION = "splinedata" + CATEGORY = "KJNodes/experimental" + DESCRIPTION = """ +# WORK IN PROGRESS +Do not count on this as part of your workflow yet, +probably contains lots of bugs and stability is not +guaranteed!! + +## Graphical editor to create values for various +## schedules and/or mask batches. + +Shift + click to add control points. +Right click to delete control points. +Note that you can't delete from start/end. + +**points_to_sample** value sets the number of samples +returned from the **drawn spline itself**, this is independent from the +actual control points, so the interpolation type matters. + +Changing interpolation type and tension value takes effect on +interaction with the graph. + +output types: + - mask batch + example compatible nodes: anything that takes masks + - list of floats + example compatible nodes: IPAdapter weights + - list of lists + example compatible nodes: unknown + - pandas series + example compatible nodes: anything that takes Fizz' + nodes Batch Value Schedule + - torch tensor + example compatible nodes: unknown +""" + + def splinedata(self, mask_width, mask_height, coordinates, float_output_type, interpolation, + points_to_sample, points_store, tension, repeat_output, min_value=0.0, max_value=1.0): + + coordinates = json.loads(coordinates) + for coord in coordinates: + coord['x'] = int(round(coord['x'])) + coord['y'] = int(round(coord['y'])) + normalized_y_values = [ + (1.0 - (point['y'] / 512) - 0.0) * (max_value - min_value) + min_value + for point in coordinates + ] + if float_output_type == 'list': + out_floats = normalized_y_values * repeat_output + elif float_output_type == 'list of lists': + out_floats = ([[value] for value in normalized_y_values] * repeat_output), + elif float_output_type == 'pandas series': + try: + import pandas as pd + except: + raise Exception("MaskOrImageToWeight: pandas is not installed. Please install pandas to use this output_type") + out_floats = pd.Series(normalized_y_values * repeat_output), + elif float_output_type == 'tensor': + out_floats = torch.tensor(normalized_y_values * repeat_output, dtype=torch.float32) + # Create a color map for grayscale intensities + color_map = lambda y: torch.full((mask_height, mask_width, 3), y, dtype=torch.float32) + + # Create image tensors for each normalized y value + mask_tensors = [color_map(y) for y in normalized_y_values] + masks_out = torch.stack(mask_tensors) + masks_out = masks_out.repeat(repeat_output, 1, 1, 1) + masks_out = masks_out.mean(dim=-1) + return (masks_out, str(coordinates), out_floats,) + +class CreateShapeMaskOnPath: + + RETURN_TYPES = ("MASK", "MASK",) + RETURN_NAMES = ("mask", "mask_inverted",) + FUNCTION = "createshapemask" + CATEGORY = "KJNodes/masking/generate" + DESCRIPTION = """ +Creates a mask or batch of masks with the specified shape. +Locations are center locations. +Grow value is the amount to grow the shape on each frame, creating animated masks. +""" + + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "shape": ( + [ 'circle', + 'square', + 'triangle', + ], + { + "default": 'circle' + }), + "coordinates": ("STRING", {"forceInput": True}), + "grow": ("INT", {"default": 0, "min": -512, "max": 512, "step": 1}), + "frame_width": ("INT", {"default": 512,"min": 16, "max": 4096, "step": 1}), + "frame_height": ("INT", {"default": 512,"min": 16, "max": 4096, "step": 1}), + "shape_width": ("INT", {"default": 128,"min": 8, "max": 4096, "step": 1}), + "shape_height": ("INT", {"default": 128,"min": 8, "max": 4096, "step": 1}), + }, + } + + def createshapemask(self, coordinates, frame_width, frame_height, shape_width, shape_height, grow, shape): + # Define the number of images in the batch + coordinates = coordinates.replace("'", '"') + coordinates = json.loads(coordinates) + for coord in coordinates: + print(coord) + + batch_size = len(coordinates) + print(batch_size) + out = [] + color = "white" + + for i, coord in enumerate(coordinates): + image = Image.new("RGB", (frame_width, frame_height), "black") + draw = ImageDraw.Draw(image) + + # Calculate the size for this frame and ensure it's not less than 0 + current_width = max(0, shape_width + i*grow) + current_height = max(0, shape_height + i*grow) + + location_x = coord['x'] + location_y = coord['y'] + + if shape == 'circle' or shape == 'square': + # Define the bounding box for the shape + left_up_point = (location_x - current_width // 2, location_y - current_height // 2) + right_down_point = (location_x + current_width // 2, location_y + current_height // 2) + two_points = [left_up_point, right_down_point] + + if shape == 'circle': + draw.ellipse(two_points, fill=color) + elif shape == 'square': + draw.rectangle(two_points, fill=color) + + elif shape == 'triangle': + # Define the points for the triangle + left_up_point = (location_x - current_width // 2, location_y + current_height // 2) # bottom left + right_down_point = (location_x + current_width // 2, location_y + current_height // 2) # bottom right + top_point = (location_x, location_y - current_height // 2) # top point + draw.polygon([top_point, left_up_point, right_down_point], fill=color) + + image = pil2tensor(image) + mask = image[:, :, :, 0] + out.append(mask) + outstack = torch.cat(out, dim=0) + return (outstack, 1.0 - outstack,) + +class MaskOrImageToWeight: + + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "output_type": ( + [ + 'list', + 'list of lists', + 'pandas series', + 'tensor', + ], + { + "default": 'list' + }), + }, + "optional": { + "images": ("IMAGE",), + "masks": ("MASK",), + }, + + } + RETURN_TYPES = ("FLOAT",) + FUNCTION = "execute" + CATEGORY = "KJNodes" + DESCRIPTION = """ +Gets the mean values from mask or image batch +and returns that as the selected output type. +""" + + def execute(self, output_type, images=None, masks=None): + mean_values = [] + if masks is not None and images is None: + for mask in masks: + mean_values.append(mask.mean().item()) + elif masks is None and images is not None: + for image in images: + mean_values.append(image.mean().item()) + elif masks is not None and images is not None: + raise Exception("MaskOrImageToWeight: Use either mask or image input only.") + + # Convert mean_values to the specified output_type + if output_type == 'list': + return mean_values, + elif output_type == 'list of lists': + return [[value] for value in mean_values], + elif output_type == 'pandas series': + try: + import pandas as pd + except: + raise Exception("MaskOrImageToWeight: pandas is not installed. Please install pandas to use this output_type") + return pd.Series(mean_values), + elif output_type == 'tensor': + return torch.tensor(mean_values, dtype=torch.float32), + else: + raise ValueError(f"Unsupported output_type: {output_type}") + +class WeightScheduleConvert: + + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "input_values": ("FLOAT", {"default": 0.0, "forceInput": True}), + "output_type": ( + [ + 'match_input', + 'list', + 'list of lists', + 'pandas series', + 'tensor', + ], + { + "default": 'list' + }), + "invert": ("BOOLEAN", {"default": False}), + "repeat": ("INT", {"default": 1,"min": 1, "max": 255, "step": 1}), + }, + "optional": { + "remap_to_frames": ("INT", {"default": 0}), + "interpolation_curve": ("FLOAT", {"forceInput": True}), + }, + + } + RETURN_TYPES = ("FLOAT",) + FUNCTION = "execute" + CATEGORY = "KJNodes" + DESCRIPTION = """ +Converts different value lists/series to another type. +""" + + def detect_input_type(self, input_values): + import pandas as pd + if isinstance(input_values, list): + return 'list' + elif isinstance(input_values, pd.Series): + return 'pandas series' + elif isinstance(input_values, torch.Tensor): + return 'tensor' + elif isinstance(input_values, list) and all(isinstance(sub, list) for sub in input_values): + return 'list of lists' + else: + raise ValueError("Unsupported input type") + + def execute(self, input_values, output_type, invert, repeat, remap_to_frames=0, interpolation_curve=None): + import pandas as pd + input_type = self.detect_input_type(input_values) + + # Convert input_values to a list of floats + if input_type == 'list of lists': + float_values = [item for sublist in input_values for item in sublist] + elif input_type == 'pandas series': + float_values = input_values.tolist() + elif input_type == 'tensor': + float_values = input_values + else: + float_values = input_values + + if invert: + float_values = [1 - value for value in float_values] + + if interpolation_curve is not None: + interpolated_pattern = [] + orig_float_values = float_values + for value in interpolation_curve: + min_val = min(orig_float_values) + max_val = max(orig_float_values) + # Normalize the values to [0, 1] + normalized_values = [(value - min_val) / (max_val - min_val) for value in orig_float_values] + # Interpolate the normalized values to the new frame count + remapped_float_values = np.interp(np.linspace(0, 1, int(remap_to_frames * value)), np.linspace(0, 1, len(normalized_values)), normalized_values).tolist() + interpolated_pattern.extend(remapped_float_values) + float_values = interpolated_pattern + else: + # Remap float_values to match target_frame_amount + if remap_to_frames > 0 and remap_to_frames != len(float_values): + min_val = min(float_values) + max_val = max(float_values) + # Normalize the values to [0, 1] + normalized_values = [(value - min_val) / (max_val - min_val) for value in float_values] + # Interpolate the normalized values to the new frame count + float_values = np.interp(np.linspace(0, 1, remap_to_frames), np.linspace(0, 1, len(normalized_values)), normalized_values).tolist() + + float_values = float_values * repeat + + if output_type == 'list': + return float_values, + elif output_type == 'list of lists': + return [[value] for value in float_values], + elif output_type == 'pandas series': + return pd.Series(float_values), + elif output_type == 'tensor': + if input_type == 'pandas series': + return torch.tensor(input_values.values, dtype=torch.float32), + elif output_type == 'match_input': + return float_values, + else: + raise ValueError(f"Unsupported output_type: {output_type}") + +class FloatToMask: + + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "input_values": ("FLOAT", {"forceInput": True, "default": 0}), + "width": ("INT", {"default": 100, "min": 1}), + "height": ("INT", {"default": 100, "min": 1}), + }, + } + RETURN_TYPES = ("MASK",) + FUNCTION = "execute" + CATEGORY = "KJNodes" + DESCRIPTION = """ +Generates a batch of masks based on the input float values. +The batch size is determined by the length of the input float values. +Each mask is generated with the specified width and height. +""" + + def execute(self, input_values, width, height): + import pandas as pd + # Ensure input_values is a list + if isinstance(input_values, (float, int)): + input_values = [input_values] + elif isinstance(input_values, pd.Series): + input_values = input_values.tolist() + elif isinstance(input_values, list) and all(isinstance(item, list) for item in input_values): + input_values = [item for sublist in input_values for item in sublist] + + # Generate a batch of masks based on the input_values + masks = [] + for value in input_values: + # Assuming value is a float between 0 and 1 representing the mask's intensity + mask = torch.ones((height, width), dtype=torch.float32) * value + masks.append(mask) + masks_out = torch.stack(masks, dim=0) + + return(masks_out,) \ No newline at end of file diff --git a/nodes.py b/nodes.py index bc18dae..39c687b 100644 --- a/nodes.py +++ b/nodes.py @@ -4746,194 +4746,7 @@ nodes for example. return(self.save_images(preview, filename_prefix, prompt, extra_pnginfo)) -class SplineEditor: - @classmethod - def INPUT_TYPES(cls): - return { - "required": { - "points_store": ("STRING", {"multiline": False}), - "coordinates": ("STRING", {"multiline": False}), - "mask_width": ("INT", {"default": 512, "min": 8, "max": MAX_RESOLUTION, "step": 8}), - "mask_height": ("INT", {"default": 512, "min": 8, "max": MAX_RESOLUTION, "step": 8}), - "points_to_sample": ("INT", {"default": 16, "min": 2, "max": 1000, "step": 1}), - "interpolation": ( - [ - 'cardinal', - 'monotone', - 'basis', - 'linear', - 'step-before', - 'step-after', - 'polar', - 'polar-reverse', - ], - { - "default": 'cardinal' - }), - "tension": ("FLOAT", {"default": 0.5, "min": 0.0, "max": 1.0, "step": 0.01}), - "repeat_output": ("INT", {"default": 1, "min": 1, "max": 4096, "step": 1}), - "float_output_type": ( - [ - 'list', - 'list of lists', - 'pandas series', - 'tensor', - ], - { - "default": 'list' - }), - }, - } - - RETURN_TYPES = ("MASK", "STRING", "FLOAT") - FUNCTION = "splinedata" - CATEGORY = "KJNodes/experimental" - DESCRIPTION = """ -# WORK IN PROGRESS -Do not count on this as part of your workflow yet, -probably contains lots of bugs and stability is not -guaranteed!! - -## Graphical editor to create values for various -## schedules and/or mask batches. - -Shift + click to add control points. -Right click to delete control points. -Note that you can't delete from start/end. - -**points_to_sample** value sets the number of samples -returned from the **drawn spline itself**, this is independent from the -actual control points, so the interpolation type matters. - -Changing interpolation type and tension value takes effect on -interaction with the graph. - -output types: - - mask batch - example compatible nodes: anything that takes masks - - list of floats - example compatible nodes: IPAdapter weights - - list of lists - example compatible nodes: unknown - - pandas series - example compatible nodes: anything that takes Fizz' - nodes Batch Value Schedule - - torch tensor - example compatible nodes: unknown -""" - - def splinedata(self, mask_width, mask_height, coordinates, float_output_type, interpolation, points_to_sample, points_store, tension, repeat_output): - - coordinates = json.loads(coordinates) - for coord in coordinates: - coord['x'] = int(round(coord['x'])) - coord['y'] = int(round(coord['y'])) - normalized_y_values = [ - 1.0 - (point['y'] / 512) - for point in coordinates - ] - if float_output_type == 'list': - out_floats = normalized_y_values * repeat_output - elif float_output_type == 'list of lists': - out_floats = ([[value] for value in normalized_y_values] * repeat_output), - elif float_output_type == 'pandas series': - try: - import pandas as pd - except: - raise Exception("MaskOrImageToWeight: pandas is not installed. Please install pandas to use this output_type") - out_floats = pd.Series(normalized_y_values * repeat_output), - elif float_output_type == 'tensor': - out_floats = torch.tensor(normalized_y_values * repeat_output, dtype=torch.float32) - # Create a color map for grayscale intensities - color_map = lambda y: torch.full((mask_height, mask_width, 3), y, dtype=torch.float32) - - # Create image tensors for each normalized y value - mask_tensors = [color_map(y) for y in normalized_y_values] - masks_out = torch.stack(mask_tensors) - masks_out = masks_out.repeat(repeat_output, 1, 1, 1) - masks_out = masks_out.mean(dim=-1) - return (masks_out, str(coordinates), out_floats,) - -class CreateShapeMaskOnPath: - - RETURN_TYPES = ("MASK", "MASK",) - RETURN_NAMES = ("mask", "mask_inverted",) - FUNCTION = "createshapemask" - CATEGORY = "KJNodes/masking/generate" - DESCRIPTION = """ -Creates a mask or batch of masks with the specified shape. -Locations are center locations. -Grow value is the amount to grow the shape on each frame, creating animated masks. -""" - - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "shape": ( - [ 'circle', - 'square', - 'triangle', - ], - { - "default": 'circle' - }), - "coordinates": ("STRING", {"forceInput": True}), - "grow": ("INT", {"default": 0, "min": -512, "max": 512, "step": 1}), - "frame_width": ("INT", {"default": 512,"min": 16, "max": 4096, "step": 1}), - "frame_height": ("INT", {"default": 512,"min": 16, "max": 4096, "step": 1}), - "shape_width": ("INT", {"default": 128,"min": 8, "max": 4096, "step": 1}), - "shape_height": ("INT", {"default": 128,"min": 8, "max": 4096, "step": 1}), - }, - } - - def createshapemask(self, coordinates, frame_width, frame_height, shape_width, shape_height, grow, shape): - # Define the number of images in the batch - coordinates = coordinates.replace("'", '"') - coordinates = json.loads(coordinates) - for coord in coordinates: - print(coord) - - batch_size = len(coordinates) - print(batch_size) - out = [] - color = "white" - - for i, coord in enumerate(coordinates): - image = Image.new("RGB", (frame_width, frame_height), "black") - draw = ImageDraw.Draw(image) - - # Calculate the size for this frame and ensure it's not less than 0 - current_width = max(0, shape_width + i*grow) - current_height = max(0, shape_height + i*grow) - - location_x = coord['x'] - location_y = coord['y'] - - if shape == 'circle' or shape == 'square': - # Define the bounding box for the shape - left_up_point = (location_x - current_width // 2, location_y - current_height // 2) - right_down_point = (location_x + current_width // 2, location_y + current_height // 2) - two_points = [left_up_point, right_down_point] - - if shape == 'circle': - draw.ellipse(two_points, fill=color) - elif shape == 'square': - draw.rectangle(two_points, fill=color) - - elif shape == 'triangle': - # Define the points for the triangle - left_up_point = (location_x - current_width // 2, location_y + current_height // 2) # bottom left - right_down_point = (location_x + current_width // 2, location_y + current_height // 2) # bottom right - top_point = (location_x, location_y - current_height // 2) # top point - draw.polygon([top_point, left_up_point, right_down_point], fill=color) - - image = pil2tensor(image) - mask = image[:, :, :, 0] - out.append(mask) - outstack = torch.cat(out, dim=0) - return (outstack, 1.0 - outstack,) class StabilityAPI_SD3: @@ -5085,387 +4898,4 @@ If no image is provided, mode is set to text-to-image raise Exception(f"Server error: {error_data}") except json.JSONDecodeError: # If the response is not valid JSON, raise a different exception - raise Exception(f"Server error: {response.text}") - - -class MaskOrImageToWeight: - - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "output_type": ( - [ - 'list', - 'list of lists', - 'pandas series', - 'tensor', - ], - { - "default": 'list' - }), - }, - "optional": { - "images": ("IMAGE",), - "masks": ("MASK",), - }, - - } - RETURN_TYPES = ("FLOAT",) - FUNCTION = "execute" - CATEGORY = "KJNodes" - DESCRIPTION = """ -Gets the mean values from mask or image batch -and returns that as the selected output type. -""" - - def execute(self, output_type, images=None, masks=None): - mean_values = [] - if masks is not None and images is None: - for mask in masks: - mean_values.append(mask.mean().item()) - elif masks is None and images is not None: - for image in images: - mean_values.append(image.mean().item()) - elif masks is not None and images is not None: - raise Exception("MaskOrImageToWeight: Use either mask or image input only.") - - # Convert mean_values to the specified output_type - if output_type == 'list': - return mean_values, - elif output_type == 'list of lists': - return [[value] for value in mean_values], - elif output_type == 'pandas series': - try: - import pandas as pd - except: - raise Exception("MaskOrImageToWeight: pandas is not installed. Please install pandas to use this output_type") - return pd.Series(mean_values), - elif output_type == 'tensor': - return torch.tensor(mean_values, dtype=torch.float32), - else: - raise ValueError(f"Unsupported output_type: {output_type}") - -class WeightScheduleConvert: - - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "input_values": ("FLOAT", {"default": 0.0, "forceInput": True}), - "output_type": ( - [ - 'match_input', - 'list', - 'list of lists', - 'pandas series', - 'tensor', - ], - { - "default": 'list' - }), - "invert": ("BOOLEAN", {"default": False}), - "repeat": ("INT", {"default": 1,"min": 1, "max": 255, "step": 1}), - }, - "optional": { - "remap_to_frames": ("INT", {"default": 0}), - "interpolation_curve": ("FLOAT", {"forceInput": True}), - }, - - } - RETURN_TYPES = ("FLOAT",) - FUNCTION = "execute" - CATEGORY = "KJNodes" - DESCRIPTION = """ -Converts different value lists/series to another type. -""" - - def detect_input_type(self, input_values): - import pandas as pd - if isinstance(input_values, list): - return 'list' - elif isinstance(input_values, pd.Series): - return 'pandas series' - elif isinstance(input_values, torch.Tensor): - return 'tensor' - elif isinstance(input_values, list) and all(isinstance(sub, list) for sub in input_values): - return 'list of lists' - else: - raise ValueError("Unsupported input type") - - def execute(self, input_values, output_type, invert, repeat, remap_to_frames=0, interpolation_curve=None): - import pandas as pd - input_type = self.detect_input_type(input_values) - - # Convert input_values to a list of floats - if input_type == 'list of lists': - float_values = [item for sublist in input_values for item in sublist] - elif input_type == 'pandas series': - float_values = input_values.tolist() - elif input_type == 'tensor': - float_values = input_values - else: - float_values = input_values - - if invert: - float_values = [1 - value for value in float_values] - - if interpolation_curve is not None: - interpolated_pattern = [] - orig_float_values = float_values - for value in interpolation_curve: - min_val = min(orig_float_values) - max_val = max(orig_float_values) - # Normalize the values to [0, 1] - normalized_values = [(value - min_val) / (max_val - min_val) for value in orig_float_values] - # Interpolate the normalized values to the new frame count - remapped_float_values = np.interp(np.linspace(0, 1, int(remap_to_frames * value)), np.linspace(0, 1, len(normalized_values)), normalized_values).tolist() - interpolated_pattern.extend(remapped_float_values) - float_values = interpolated_pattern - else: - # Remap float_values to match target_frame_amount - if remap_to_frames > 0 and remap_to_frames != len(float_values): - min_val = min(float_values) - max_val = max(float_values) - # Normalize the values to [0, 1] - normalized_values = [(value - min_val) / (max_val - min_val) for value in float_values] - # Interpolate the normalized values to the new frame count - float_values = np.interp(np.linspace(0, 1, remap_to_frames), np.linspace(0, 1, len(normalized_values)), normalized_values).tolist() - - float_values = float_values * repeat - - if output_type == 'list': - return float_values, - elif output_type == 'list of lists': - return [[value] for value in float_values], - elif output_type == 'pandas series': - return pd.Series(float_values), - elif output_type == 'tensor': - if input_type == 'pandas series': - return torch.tensor(input_values.values, dtype=torch.float32), - elif output_type == 'match_input': - return float_values, - else: - raise ValueError(f"Unsupported output_type: {output_type}") - -class FloatToMask: - - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "input_values": ("FLOAT", {"forceInput": True, "default": 0}), - "width": ("INT", {"default": 100, "min": 1}), - "height": ("INT", {"default": 100, "min": 1}), - }, - } - RETURN_TYPES = ("MASK",) - FUNCTION = "execute" - CATEGORY = "KJNodes" - DESCRIPTION = """ -Generates a batch of masks based on the input float values. -The batch size is determined by the length of the input float values. -Each mask is generated with the specified width and height. -""" - - def execute(self, input_values, width, height): - import pandas as pd - # Ensure input_values is a list - if isinstance(input_values, (float, int)): - input_values = [input_values] - elif isinstance(input_values, pd.Series): - input_values = input_values.tolist() - elif isinstance(input_values, list) and all(isinstance(item, list) for item in input_values): - input_values = [item for sublist in input_values for item in sublist] - - # Generate a batch of masks based on the input_values - masks = [] - for value in input_values: - # Assuming value is a float between 0 and 1 representing the mask's intensity - mask = torch.ones((height, width), dtype=torch.float32) * value - masks.append(mask) - masks_out = torch.stack(masks, dim=0) - - return(masks_out,) - - -NODE_CLASS_MAPPINGS = { - "INTConstant": INTConstant, - "FloatConstant": FloatConstant, - "ImageBatchMulti": ImageBatchMulti, - "MaskBatchMulti": MaskBatchMulti, - "ConditioningMultiCombine": ConditioningMultiCombine, - "ConditioningSetMaskAndCombine": ConditioningSetMaskAndCombine, - "ConditioningSetMaskAndCombine3": ConditioningSetMaskAndCombine3, - "ConditioningSetMaskAndCombine4": ConditioningSetMaskAndCombine4, - "ConditioningSetMaskAndCombine5": ConditioningSetMaskAndCombine5, - "GrowMaskWithBlur": GrowMaskWithBlur, - "ColorToMask": ColorToMask, - "CreateGradientMask": CreateGradientMask, - "CreateTextMask": CreateTextMask, - "CreateAudioMask": CreateAudioMask, - "CreateFadeMask": CreateFadeMask, - "CreateFadeMaskAdvanced": CreateFadeMaskAdvanced, - "CreateFluidMask" :CreateFluidMask, - "VRAM_Debug" : VRAM_Debug, - "SomethingToString" : SomethingToString, - "CrossFadeImages": CrossFadeImages, - "EmptyLatentImagePresets": EmptyLatentImagePresets, - "ColorMatch": ColorMatch, - "GetImageRangeFromBatch": GetImageRangeFromBatch, - "SaveImageWithAlpha": SaveImageWithAlpha, - "ReverseImageBatch": ReverseImageBatch, - "ImageGridComposite2x2": ImageGridComposite2x2, - "ImageGridComposite3x3": ImageGridComposite3x3, - "ImageConcanate": ImageConcanate, - "ImageBatchTestPattern": ImageBatchTestPattern, - "ReplaceImagesInBatch": ReplaceImagesInBatch, - "BatchCropFromMask": BatchCropFromMask, - "BatchCropFromMaskAdvanced": BatchCropFromMaskAdvanced, - "FilterZeroMasksAndCorrespondingImages": FilterZeroMasksAndCorrespondingImages, - "InsertImageBatchByIndexes": InsertImageBatchByIndexes, - "BatchUncrop": BatchUncrop, - "BatchUncropAdvanced": BatchUncropAdvanced, - "BatchCLIPSeg": BatchCLIPSeg, - "RoundMask": RoundMask, - "ResizeMask": ResizeMask, - "OffsetMask": OffsetMask, - "WidgetToString": WidgetToString, - "CreateShapeMask": CreateShapeMask, - "CreateVoronoiMask": CreateVoronoiMask, - "CreateMagicMask": CreateMagicMask, - "BboxToInt": BboxToInt, - "SplitBboxes": SplitBboxes, - "ImageGrabPIL": ImageGrabPIL, - "DummyLatentOut": DummyLatentOut, - "FlipSigmasAdjusted": FlipSigmasAdjusted, - "InjectNoiseToLatent": InjectNoiseToLatent, - "AddLabel": AddLabel, - "SoundReactive": SoundReactive, - "GenerateNoise": GenerateNoise, - "StableZero123_BatchSchedule": StableZero123_BatchSchedule, - "SV3D_BatchSchedule": SV3D_BatchSchedule, - "GetImagesFromBatchIndexed": GetImagesFromBatchIndexed, - "InsertImagesToBatchIndexed": InsertImagesToBatchIndexed, - "ImageBatchRepeatInterleaving": ImageBatchRepeatInterleaving, - "NormalizedAmplitudeToMask": NormalizedAmplitudeToMask, - "OffsetMaskByNormalizedAmplitude": OffsetMaskByNormalizedAmplitude, - "ImageTransformByNormalizedAmplitude": ImageTransformByNormalizedAmplitude, - "GetLatentsFromBatchIndexed": GetLatentsFromBatchIndexed, - "StringConstant": StringConstant, - "GLIGENTextBoxApplyBatch": GLIGENTextBoxApplyBatch, - "CondPassThrough": CondPassThrough, - "ImageUpscaleWithModelBatched": ImageUpscaleWithModelBatched, - "ScaleBatchPromptSchedule": ScaleBatchPromptSchedule, - "ImageNormalize_Neg1_To_1": ImageNormalize_Neg1_To_1, - "Intrinsic_lora_sampling": Intrinsic_lora_sampling, - "RemapMaskRange": RemapMaskRange, - "LoadResAdapterNormalization": LoadResAdapterNormalization, - "Superprompt": Superprompt, - "RemapImageRange": RemapImageRange, - "CameraPoseVisualizer": CameraPoseVisualizer, - "BboxVisualize": BboxVisualize, - "StringConstantMultiline": StringConstantMultiline, - "JoinStrings": JoinStrings, - "Sleep": Sleep, - "ImagePadForOutpaintMasked": ImagePadForOutpaintMasked, - "SplineEditor": SplineEditor, - "ImageAndMaskPreview": ImageAndMaskPreview, - "StabilityAPI_SD3": StabilityAPI_SD3, - "MaskOrImageToWeight": MaskOrImageToWeight, - "WeightScheduleConvert": WeightScheduleConvert, - "FloatToMask": FloatToMask, - "CustomSigmas": CustomSigmas, - "ImagePass": ImagePass, - "CreateShapeMaskOnPath": CreateShapeMaskOnPath -} -NODE_DISPLAY_NAME_MAPPINGS = { - "INTConstant": "INT Constant", - "FloatConstant": "Float Constant", - "ImageBatchMulti": "Image Batch Multi", - "MaskBatchMulti": "Mask Batch Multi", - "ConditioningMultiCombine": "Conditioning Multi Combine", - "ConditioningSetMaskAndCombine": "ConditioningSetMaskAndCombine", - "ConditioningSetMaskAndCombine3": "ConditioningSetMaskAndCombine3", - "ConditioningSetMaskAndCombine4": "ConditioningSetMaskAndCombine4", - "ConditioningSetMaskAndCombine5": "ConditioningSetMaskAndCombine5", - "GrowMaskWithBlur": "GrowMaskWithBlur", - "ColorToMask": "ColorToMask", - "CreateGradientMask": "CreateGradientMask", - "CreateTextMask" : "CreateTextMask", - "CreateFadeMask" : "CreateFadeMask (Deprecated)", - "CreateFadeMaskAdvanced" : "CreateFadeMaskAdvanced", - "CreateFluidMask" : "CreateFluidMask", - "CreateAudioMask" : "CreateAudioMask (Deprecated)", - "VRAM_Debug" : "VRAM Debug", - "CrossFadeImages": "CrossFadeImages", - "SomethingToString": "SomethingToString", - "EmptyLatentImagePresets": "EmptyLatentImagePresets", - "ColorMatch": "ColorMatch", - "GetImageRangeFromBatch": "GetImageRangeFromBatch", - "InsertImagesToBatchIndexed": "InsertImagesToBatchIndexed", - "SaveImageWithAlpha": "SaveImageWithAlpha", - "ReverseImageBatch": "ReverseImageBatch", - "ImageGridComposite2x2": "ImageGridComposite2x2", - "ImageGridComposite3x3": "ImageGridComposite3x3", - "ImageConcanate": "ImageConcatenate", - "ImageBatchTestPattern": "ImageBatchTestPattern", - "ReplaceImagesInBatch": "ReplaceImagesInBatch", - "BatchCropFromMask": "BatchCropFromMask", - "BatchCropFromMaskAdvanced": "BatchCropFromMaskAdvanced", - "FilterZeroMasksAndCorrespondingImages": "FilterZeroMasksAndCorrespondingImages", - "InsertImageBatchByIndexes": "InsertImageBatchByIndexes", - "BatchUncrop": "BatchUncrop", - "BatchUncropAdvanced": "BatchUncropAdvanced", - "BatchCLIPSeg": "BatchCLIPSeg", - "RoundMask": "RoundMask", - "ResizeMask": "ResizeMask", - "OffsetMask": "OffsetMask", - "WidgetToString": "WidgetToString", - "CreateShapeMask": "CreateShapeMask", - "CreateVoronoiMask": "CreateVoronoiMask", - "CreateMagicMask": "CreateMagicMask", - "BboxToInt": "BboxToInt", - "SplitBboxes": "SplitBboxes", - "ImageGrabPIL": "ImageGrabPIL", - "DummyLatentOut": "DummyLatentOut", - "FlipSigmasAdjusted": "FlipSigmasAdjusted", - "InjectNoiseToLatent": "InjectNoiseToLatent", - "AddLabel": "AddLabel", - "SoundReactive": "SoundReactive", - "GenerateNoise": "GenerateNoise", - "StableZero123_BatchSchedule": "StableZero123_BatchSchedule", - "SV3D_BatchSchedule": "SV3D_BatchSchedule", - "GetImagesFromBatchIndexed": "GetImagesFromBatchIndexed", - "ImageBatchRepeatInterleaving": "ImageBatchRepeatInterleaving", - "NormalizedAmplitudeToMask": "NormalizedAmplitudeToMask", - "OffsetMaskByNormalizedAmplitude": "OffsetMaskByNormalizedAmplitude", - "ImageTransformByNormalizedAmplitude": "ImageTransformByNormalizedAmplitude", - "GetLatentsFromBatchIndexed": "GetLatentsFromBatchIndexed", - "StringConstant": "StringConstant", - "GLIGENTextBoxApplyBatch": "GLIGENTextBoxApplyBatch", - "CondPassThrough": "CondPassThrough", - "ImageUpscaleWithModelBatched": "ImageUpscaleWithModelBatched", - "ScaleBatchPromptSchedule": "ScaleBatchPromptSchedule", - "ImageNormalize_Neg1_To_1": "ImageNormalize_Neg1_To_1", - "Intrinsic_lora_sampling": "Intrinsic_lora_sampling", - "RemapMaskRange": "RemapMaskRange", - "LoadResAdapterNormalization": "LoadResAdapterNormalization", - "Superprompt": "Superprompt", - "RemapImageRange": "RemapImageRange", - "CameraPoseVisualizer": "CameraPoseVisualizer", - "BboxVisualize": "BboxVisualize", - "StringConstantMultiline": "StringConstantMultiline", - "JoinStrings": "JoinStrings", - "Sleep": "🛌 Sleep 🛌", - "ImagePadForOutpaintMasked": "Pad Image For Outpaint Masked", - "SplineEditor": "Spline Editor", - "ImageAndMaskPreview": "Image & Mask Preview", - "StabilityAPI_SD3": "Stability API SD3", - "MaskOrImageToWeight": "Mask Or Image To Weight", - "WeightScheduleConvert": "Weight Schedule Convert", - "FloatToMask": "Float To Mask", - "CustomSigmas": "Custom Sigmas", - "ImagePass": "ImagePass", - "CreateShapeMaskOnPath": "CreateShapeMaskOnPath", -} \ No newline at end of file + raise Exception(f"Server error: {response.text}") \ No newline at end of file diff --git a/web/js/spline_editor.js b/web/js/spline_editor.js index 537a53c..5f85c8b 100644 --- a/web/js/spline_editor.js +++ b/web/js/spline_editor.js @@ -175,7 +175,7 @@ app.registerExtension({ createSplineEditor(this, true) } }); - this.setSize([550, 840]) + this.setSize([550, 870]) this.splineEditor.parentEl = document.createElement("div"); this.splineEditor.parentEl.className = "spline-editor"; this.splineEditor.parentEl.id = `spline-editor-${this.uuid}` @@ -184,7 +184,7 @@ app.registerExtension({ chainCallback(this, "onGraphConfigured", function() { console.log('onGraphConfigured'); createSplineEditor(this) - this.setSize([550, 840]) + this.setSize([550, 870]) }); //disable context menu on right click @@ -292,11 +292,15 @@ function createSplineEditor(context, reset=false) { const pointsWidget = context.widgets.find(w => w.name === "points_to_sample"); const pointsStoreWidget = context.widgets.find(w => w.name === "points_store"); const tensionWidget = context.widgets.find(w => w.name === "tension"); + const minValueWidget = context.widgets.find(w => w.name === "min_value"); + const maxValueWidget = context.widgets.find(w => w.name === "max_value"); //const segmentedWidget = context.widgets.find(w => w.name === "segmented"); var interpolation = interpolationWidget.value var tension = tensionWidget.value var points_to_sample = pointsWidget.value + var rangeMin = minValueWidget.value + var rangeMax = maxValueWidget.value var pointsLayer = null; interpolationWidget.callback = () => { @@ -309,7 +313,10 @@ function createSplineEditor(context, reset=false) { updatePath(); } - pointsWidget.callback = () => { + minValueWidget.callback = () => { + updatePath(); + } + maxValueWidget.callback = () => { updatePath(); } @@ -319,6 +326,7 @@ function createSplineEditor(context, reset=false) { var h = 512 var i = 3 let points = []; + if (!reset && pointsStoreWidget.value != "") { points = JSON.parse(pointsStoreWidget.value); } else { @@ -426,9 +434,9 @@ function createSplineEditor(context, reset=false) { .font(12 + "px sans-serif") .text(d => { // Normalize y to range 0.0 to 1.0, considering the inverted y-axis - var normalizedY = 1.0 - (d.y / h); - var normalizedX = (d.x / w); - var frame = Math.round((d.x / w) * points_to_sample); + let normalizedY = (1.0 - (d.y / h) - 0.0) * (rangeMax - rangeMin) + rangeMin; + let normalizedX = (d.x / w); + let frame = Math.round((d.x / w) * points_to_sample); return `F: ${frame}, X: ${normalizedX.toFixed(2)}, Y: ${normalizedY.toFixed(2)}`; }) .textStyle("orange") From b90c8d830f34d3017373275017167eef7db3ac5f Mon Sep 17 00:00:00 2001 From: kijai <40791699+kijai@users.noreply.github.com> Date: Sat, 27 Apr 2024 11:50:24 +0300 Subject: [PATCH 47/95] Add WeightScheduleExtend -node --- __init__.py | 6 ++-- curve_nodes.py | 80 +++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 83 insertions(+), 3 deletions(-) diff --git a/__init__.py b/__init__.py index f2a02ae..586f2c5 100644 --- a/__init__.py +++ b/__init__.py @@ -88,7 +88,8 @@ NODE_CLASS_MAPPINGS = { "CustomSigmas": CustomSigmas, "ImagePass": ImagePass, "SplineEditor": SplineEditor, - "CreateShapeMaskOnPath": CreateShapeMaskOnPath + "CreateShapeMaskOnPath": CreateShapeMaskOnPath, + "WeightScheduleExtend": WeightScheduleExtend } NODE_DISPLAY_NAME_MAPPINGS = { @@ -179,7 +180,8 @@ NODE_DISPLAY_NAME_MAPPINGS = { "CustomSigmas": "Custom Sigmas", "ImagePass": "ImagePass", "SplineEditor": "Spline Editor", - "CreateShapeMaskOnPath": "Create Shape Mask On Path" + "CreateShapeMaskOnPath": "Create Shape Mask On Path", + "WeightScheduleExtend": "Weight Schedule Extend" } __all__ = ["NODE_CLASS_MAPPINGS", "NODE_DISPLAY_NAME_MAPPINGS", "WEB_DIRECTORY"] diff --git a/curve_nodes.py b/curve_nodes.py index 0c45454..0484a70 100644 --- a/curve_nodes.py +++ b/curve_nodes.py @@ -396,4 +396,82 @@ Each mask is generated with the specified width and height. masks.append(mask) masks_out = torch.stack(masks, dim=0) - return(masks_out,) \ No newline at end of file + return(masks_out,) +class WeightScheduleExtend: + + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "input_values_1": ("FLOAT", {"default": 0.0, "forceInput": True}), + "input_values_2": ("FLOAT", {"default": 0.0, "forceInput": True}), + "output_type": ( + [ + 'match_input', + 'list', + 'list of lists', + 'pandas series', + 'tensor', + ], + { + "default": 'match_input' + }), + }, + + } + RETURN_TYPES = ("FLOAT",) + FUNCTION = "execute" + CATEGORY = "KJNodes" + DESCRIPTION = """ +Converts different value lists/series to another type. +""" + + def detect_input_type(self, input_values): + import pandas as pd + if isinstance(input_values, list): + return 'list' + elif isinstance(input_values, pd.Series): + return 'pandas series' + elif isinstance(input_values, torch.Tensor): + return 'tensor' + elif isinstance(input_values, list) and all(isinstance(sub, list) for sub in input_values): + return 'list of lists' + else: + raise ValueError("Unsupported input type") + + def execute(self, input_values_1, input_values_2, output_type): + import pandas as pd + input_type_1 = self.detect_input_type(input_values_1) + input_type_2 = self.detect_input_type(input_values_2) + # Convert input_values_2 to the same format as input_values_1 if they do not match + if not input_type_1 == input_type_2: + print("Converting input_values_2 to the same format as input_values_1") + if input_type_1 == 'list of lists': + # Assuming input_values_2 is a flat list, convert it to a list of lists + float_values_2 = [[item] for item in input_values_2] + elif input_type_1 == 'pandas series': + # Convert input_values_2 to a pandas Series + float_values_2 = pd.Series(input_values_2) + elif input_type_1 == 'tensor': + # Convert input_values_2 to a tensor + float_values_2 = torch.tensor(input_values_2, dtype=torch.float32) + else: + print("Input types match, no conversion needed") + # If the types match, no conversion is needed + float_values_2 = input_values_2 + + float_values = input_values_1 + float_values_2 + + if output_type == 'list': + return float_values, + elif output_type == 'list of lists': + return [[value] for value in float_values], + elif output_type == 'pandas series': + return pd.Series(float_values), + elif output_type == 'tensor': + if input_type_1 == 'pandas series': + return torch.tensor(input_values_1.values, dtype=torch.float32), + elif output_type == 'match_input': + return float_values, + else: + raise ValueError(f"Unsupported output_type: {output_type}") \ No newline at end of file From f3fcc2d37ddcd324f07daecc6579306b333215a7 Mon Sep 17 00:00:00 2001 From: kijai <40791699+kijai@users.noreply.github.com> Date: Sat, 27 Apr 2024 11:50:56 +0300 Subject: [PATCH 48/95] Update curve_nodes.py --- curve_nodes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/curve_nodes.py b/curve_nodes.py index 0484a70..94dcb52 100644 --- a/curve_nodes.py +++ b/curve_nodes.py @@ -423,7 +423,7 @@ class WeightScheduleExtend: FUNCTION = "execute" CATEGORY = "KJNodes" DESCRIPTION = """ -Converts different value lists/series to another type. +Extends, and converts if needed, different value lists/series """ def detect_input_type(self, input_values): From f891eb9e0fe8ff3b9fb1274b5f59daaf8ead5285 Mon Sep 17 00:00:00 2001 From: kijai <40791699+kijai@users.noreply.github.com> Date: Sat, 27 Apr 2024 12:34:27 +0300 Subject: [PATCH 49/95] Update spline_editor.js --- web/js/spline_editor.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/web/js/spline_editor.js b/web/js/spline_editor.js index 5f85c8b..0fc7939 100644 --- a/web/js/spline_editor.js +++ b/web/js/spline_editor.js @@ -447,7 +447,8 @@ function createSplineEditor(context, reset=false) { svgElement.style['zIndex'] = "2" svgElement.style['position'] = "relative" context.splineEditor.element.appendChild(svgElement); - var pathElements = svgElement.getElementsByTagName('path'); // Get all path elements + var pathElements = svgElement.getElementsByTagName('path'); // Get all path elements + updatePath(); } function samplePoints(svgPathElement, numSamples) { From a265879af0d971c7a332d0fd3c7719edcd654278 Mon Sep 17 00:00:00 2001 From: kijai <40791699+kijai@users.noreply.github.com> Date: Sat, 27 Apr 2024 20:42:03 +0300 Subject: [PATCH 50/95] Account for DPI --- web/js/help_popup.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/js/help_popup.js b/web/js/help_popup.js index 00f21bf..4982fd1 100644 --- a/web/js/help_popup.js +++ b/web/js/help_popup.js @@ -247,7 +247,7 @@ const create_documentation_stylesheet = () => { const transform = new DOMMatrix() .scaleSelf(scaleX, scaleY) .multiplySelf(ctx.getTransform()) - .translateSelf(this.size[0] * scaleX, 0) + .translateSelf(this.size[0] * scaleX * window.devicePixelRatio, 0) .translateSelf(10, -32) const scale = new DOMMatrix() From 6510f7eda96c46cf8e12c09b1b0a46667ceaeda0 Mon Sep 17 00:00:00 2001 From: kijai <40791699+kijai@users.noreply.github.com> Date: Sun, 28 Apr 2024 14:10:31 +0300 Subject: [PATCH 51/95] Update spline_editor.js --- web/js/spline_editor.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/web/js/spline_editor.js b/web/js/spline_editor.js index 0fc7939..d320e85 100644 --- a/web/js/spline_editor.js +++ b/web/js/spline_editor.js @@ -312,6 +312,10 @@ function createSplineEditor(context, reset=false) { tension = tensionWidget.value updatePath(); } + pointsWidget.callback = () => { + points_to_sample = pointsWidget.value + updatePath(); + } minValueWidget.callback = () => { updatePath(); From 98add2716bf9e1d8263890fb0786601031d36a71 Mon Sep 17 00:00:00 2001 From: kijai <40791699+kijai@users.noreply.github.com> Date: Sun, 28 Apr 2024 16:51:41 +0300 Subject: [PATCH 52/95] Continue restructuring --- README.md | 11 +- __init__.py | 117 ++- audio.wav | Bin 654486 -> 0 bytes favicon-active.ico | Bin 1025 -> 0 bytes favicon.ico | Bin 1006 -> 0 bytes .../intrinsic_loras.txt | 0 kjweb_async/d3.v6.min.js | 2 - nodes/audioscheduler_nodes.py | 230 +++++ nodes/batchcrop_nodes.py | 677 +++++++++++++ curve_nodes.py => nodes/curve_nodes.py | 3 +- nodes.py => nodes/nodes.py | 908 +----------------- fluid.py => utility/fluid.py | 0 magictex.py => utility/magictex.py | 0 numerical.py => utility/numerical.py | 0 utility.py => utility/utility.py | 0 web/js/help_popup.js | 22 +- 16 files changed, 1001 insertions(+), 969 deletions(-) delete mode 100644 audio.wav delete mode 100644 favicon-active.ico delete mode 100644 favicon.ico rename intrinsic_loras.txt => intristic_loras/intrinsic_loras.txt (100%) delete mode 100644 kjweb_async/d3.v6.min.js create mode 100644 nodes/audioscheduler_nodes.py create mode 100644 nodes/batchcrop_nodes.py rename curve_nodes.py => nodes/curve_nodes.py (99%) rename nodes.py => nodes/nodes.py (80%) rename fluid.py => utility/fluid.py (100%) rename magictex.py => utility/magictex.py (100%) rename numerical.py => utility/numerical.py (100%) rename utility.py => utility/utility.py (100%) diff --git a/README.md b/README.md index a1589bf..4902861 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,8 @@ This is still work in progress, like everything else. ## Javascript ### browserstatus.js -Sets the favicon to green circle when not processing anything, sets it to red when processing and shows progress percentage and the lenghth of your queue. Might clash with other scripts that affect the page title, delete this file to disable (until I figure out how to add options). +Sets the favicon to green circle when not processing anything, sets it to red when processing and shows progress percentage and the lenghth of your queue. +Default off, needs to be enabled from options, overrides Custom-Scripts favicon when enabled. ## Nodes: @@ -47,14 +48,6 @@ Mask and combine two sets of conditions, saves space. Grows or shrinks (with negative values) mask, option to invert input, returns mask and inverted mask. Additionally Blurs the mask, this is a slow operation especially with big batches. -### CreateFadeMask - -This node creates batch of single color images by interpolating between white/black levels. Useful to control mask strengths or QR code controlnet input weight when combined with MaskComposite node. - -### CreateAudioMask - -Work in progress, currently creates a sphere that's size is synced with audio input. - ### RoundMask ![image](https://github.com/kijai/ComfyUI-KJNodes/assets/40791699/52c85202-f74e-4b96-9dac-c8bda5ddcc40) diff --git a/__init__.py b/__init__.py index 586f2c5..f8d46fe 100644 --- a/__init__.py +++ b/__init__.py @@ -1,15 +1,26 @@ -from .nodes import * -from .curve_nodes import * +from .nodes.nodes import * +from .nodes.curve_nodes import * +from .nodes.batchcrop_nodes import * +from .nodes.audioscheduler_nodes import * NODE_CLASS_MAPPINGS = { + #constants "INTConstant": INTConstant, "FloatConstant": FloatConstant, - "ImageBatchMulti": ImageBatchMulti, - "MaskBatchMulti": MaskBatchMulti, + "StringConstant": StringConstant, + "StringConstantMultiline": StringConstantMultiline, + #conditioning "ConditioningMultiCombine": ConditioningMultiCombine, "ConditioningSetMaskAndCombine": ConditioningSetMaskAndCombine, "ConditioningSetMaskAndCombine3": ConditioningSetMaskAndCombine3, "ConditioningSetMaskAndCombine4": ConditioningSetMaskAndCombine4, "ConditioningSetMaskAndCombine5": ConditioningSetMaskAndCombine5, + "CondPassThrough": CondPassThrough, + #masking + "BatchCLIPSeg": BatchCLIPSeg, + "RoundMask": RoundMask, + "ResizeMask": ResizeMask, + "OffsetMask": OffsetMask, + "MaskBatchMulti": MaskBatchMulti, "GrowMaskWithBlur": GrowMaskWithBlur, "ColorToMask": ColorToMask, "CreateGradientMask": CreateGradientMask, @@ -18,11 +29,14 @@ NODE_CLASS_MAPPINGS = { "CreateFadeMask": CreateFadeMask, "CreateFadeMaskAdvanced": CreateFadeMaskAdvanced, "CreateFluidMask" :CreateFluidMask, - "VRAM_Debug" : VRAM_Debug, - "SomethingToString" : SomethingToString, - "CrossFadeImages": CrossFadeImages, - "EmptyLatentImagePresets": EmptyLatentImagePresets, + "CreateShapeMask": CreateShapeMask, + "CreateVoronoiMask": CreateVoronoiMask, + "CreateMagicMask": CreateMagicMask, + "RemapMaskRange": RemapMaskRange, + #images + "ImageBatchMulti": ImageBatchMulti, "ColorMatch": ColorMatch, + "CrossFadeImages": CrossFadeImages, "GetImageRangeFromBatch": GetImageRangeFromBatch, "SaveImageWithAlpha": SaveImageWithAlpha, "ReverseImageBatch": ReverseImageBatch, @@ -31,65 +45,63 @@ NODE_CLASS_MAPPINGS = { "ImageConcanate": ImageConcanate, "ImageBatchTestPattern": ImageBatchTestPattern, "ReplaceImagesInBatch": ReplaceImagesInBatch, + "ImageGrabPIL": ImageGrabPIL, + "AddLabel": AddLabel, + "ImageUpscaleWithModelBatched": ImageUpscaleWithModelBatched, + "GetImagesFromBatchIndexed": GetImagesFromBatchIndexed, + "InsertImagesToBatchIndexed": InsertImagesToBatchIndexed, + "ImageBatchRepeatInterleaving": ImageBatchRepeatInterleaving, + "ImageNormalize_Neg1_To_1": ImageNormalize_Neg1_To_1, + "RemapImageRange": RemapImageRange, + "ImagePass": ImagePass, + "ImagePadForOutpaintMasked": ImagePadForOutpaintMasked, + "ImageAndMaskPreview": ImageAndMaskPreview, + #batch cropping "BatchCropFromMask": BatchCropFromMask, "BatchCropFromMaskAdvanced": BatchCropFromMaskAdvanced, "FilterZeroMasksAndCorrespondingImages": FilterZeroMasksAndCorrespondingImages, "InsertImageBatchByIndexes": InsertImageBatchByIndexes, "BatchUncrop": BatchUncrop, "BatchUncropAdvanced": BatchUncropAdvanced, - "BatchCLIPSeg": BatchCLIPSeg, - "RoundMask": RoundMask, - "ResizeMask": ResizeMask, - "OffsetMask": OffsetMask, - "WidgetToString": WidgetToString, - "CreateShapeMask": CreateShapeMask, - "CreateVoronoiMask": CreateVoronoiMask, - "CreateMagicMask": CreateMagicMask, - "BboxToInt": BboxToInt, "SplitBboxes": SplitBboxes, - "ImageGrabPIL": ImageGrabPIL, - "DummyLatentOut": DummyLatentOut, + "BboxToInt": BboxToInt, + "BboxVisualize": BboxVisualize, + #noise + "GenerateNoise": GenerateNoise, "FlipSigmasAdjusted": FlipSigmasAdjusted, "InjectNoiseToLatent": InjectNoiseToLatent, - "AddLabel": AddLabel, - "SoundReactive": SoundReactive, - "GenerateNoise": GenerateNoise, - "StableZero123_BatchSchedule": StableZero123_BatchSchedule, - "SV3D_BatchSchedule": SV3D_BatchSchedule, - "GetImagesFromBatchIndexed": GetImagesFromBatchIndexed, - "InsertImagesToBatchIndexed": InsertImagesToBatchIndexed, - "ImageBatchRepeatInterleaving": ImageBatchRepeatInterleaving, + "CustomSigmas": CustomSigmas, + #utility + "WidgetToString": WidgetToString, + "DummyLatentOut": DummyLatentOut, + "GetLatentsFromBatchIndexed": GetLatentsFromBatchIndexed, + "ScaleBatchPromptSchedule": ScaleBatchPromptSchedule, + "CameraPoseVisualizer": CameraPoseVisualizer, + "JoinStrings": JoinStrings, + "Sleep": Sleep, + "VRAM_Debug" : VRAM_Debug, + "SomethingToString" : SomethingToString, + "EmptyLatentImagePresets": EmptyLatentImagePresets, + #audioscheduler stuff "NormalizedAmplitudeToMask": NormalizedAmplitudeToMask, "OffsetMaskByNormalizedAmplitude": OffsetMaskByNormalizedAmplitude, "ImageTransformByNormalizedAmplitude": ImageTransformByNormalizedAmplitude, - "GetLatentsFromBatchIndexed": GetLatentsFromBatchIndexed, - "StringConstant": StringConstant, - "GLIGENTextBoxApplyBatch": GLIGENTextBoxApplyBatch, - "CondPassThrough": CondPassThrough, - "ImageUpscaleWithModelBatched": ImageUpscaleWithModelBatched, - "ScaleBatchPromptSchedule": ScaleBatchPromptSchedule, - "ImageNormalize_Neg1_To_1": ImageNormalize_Neg1_To_1, - "Intrinsic_lora_sampling": Intrinsic_lora_sampling, - "RemapMaskRange": RemapMaskRange, - "LoadResAdapterNormalization": LoadResAdapterNormalization, - "Superprompt": Superprompt, - "RemapImageRange": RemapImageRange, - "CameraPoseVisualizer": CameraPoseVisualizer, - "BboxVisualize": BboxVisualize, - "StringConstantMultiline": StringConstantMultiline, - "JoinStrings": JoinStrings, - "Sleep": Sleep, - "ImagePadForOutpaintMasked": ImagePadForOutpaintMasked, - "ImageAndMaskPreview": ImageAndMaskPreview, - "StabilityAPI_SD3": StabilityAPI_SD3, + #curve nodes + "SplineEditor": SplineEditor, + "CreateShapeMaskOnPath": CreateShapeMaskOnPath, + "WeightScheduleExtend": WeightScheduleExtend, "MaskOrImageToWeight": MaskOrImageToWeight, "WeightScheduleConvert": WeightScheduleConvert, "FloatToMask": FloatToMask, - "CustomSigmas": CustomSigmas, - "ImagePass": ImagePass, - "SplineEditor": SplineEditor, - "CreateShapeMaskOnPath": CreateShapeMaskOnPath, - "WeightScheduleExtend": WeightScheduleExtend + #experimental + "StabilityAPI_SD3": StabilityAPI_SD3, + "SoundReactive": SoundReactive, + "StableZero123_BatchSchedule": StableZero123_BatchSchedule, + "SV3D_BatchSchedule": SV3D_BatchSchedule, + "LoadResAdapterNormalization": LoadResAdapterNormalization, + "Superprompt": Superprompt, + "GLIGENTextBoxApplyBatch": GLIGENTextBoxApplyBatch, + "Intrinsic_lora_sampling": Intrinsic_lora_sampling, } NODE_DISPLAY_NAME_MAPPINGS = { @@ -179,6 +191,7 @@ NODE_DISPLAY_NAME_MAPPINGS = { "FloatToMask": "Float To Mask", "CustomSigmas": "Custom Sigmas", "ImagePass": "ImagePass", + #curve nodes "SplineEditor": "Spline Editor", "CreateShapeMaskOnPath": "Create Shape Mask On Path", "WeightScheduleExtend": "Weight Schedule Extend" diff --git a/audio.wav b/audio.wav deleted file mode 100644 index 78cff4706ab9c92c4b07c8bf5aab6f98575e9c2e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 654486 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
k1frbk8_ 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%0D{BU@-I>PJO6DlF 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

U{Rzu_XmsNKO548lja+uY`rg!wCAXp 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

@Yc?hU! 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

vt$i0^B%{(HnN6gM6k2aemE(>kozhyG%C!B2+xp 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 diff --git a/favicon.ico b/favicon.ico deleted file mode 100644 index 08df2481551bb6903735fa69d658b5abfb0a5ae1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1006 zcmVY8+a0h?mq>Mi zx$acL0<|S&Avt#Ttuw`&p~ZnaS~#8_2uC&2$YwUfos&5-eI;1q5h(cw#NttJ^mk6Z z(2?iZ2fQTI4DHg}Q=`^`f3hWVoQO`I;%I*WtaRMzUW%#k1qNTd0N_g@rqhEDkI&pMfMxk#8pd&uG*B0MBou41SMJP`_p9iO%1H2aK&Vj%7S4XU%8hrx1V~4RZz|W$LkD!j`*&yPx#bKzw*^j(1d5?=b^zOgpa6=~333aL z>%rxDuN?|4a}%*RHa2rDhwA->N&7=R|2MIZdmvsHG?2bu0MSiwAqKtf!7>LyT=VC> zw)=Iq)_ux04+YE$1x%;nIk(=h?>?xC;n*nNzcCb`;889qFq~6N1(3aN6NP_Q8{L7} zH$)lCWw-KRr`iNAOeKXgpaG zsh3N^sf!17Fy`A(Bibkx$+cFcLEal3z|w34YAOzx2JT!}@aepW1bYF)(ve83vxXzi z|MFM&vDp_vlU9LjuW$FMYxGRZ$4R>ZRR-E?J1|OzcvW+eXy(z=n+QckXps;sKJ3*E zAAQ0R8sa$;3TrnNnI(tPaU53)DDCFJbxrtf9hLF{GAmC&gLTmF2QY06%S5>{rhri9 z)6sO;zxK||Z7aBgZ*`%@Bs{wR5WD&RP~IzG<%fG1ITwMXL{ZtUVKC_hP~D4{Q!0eu cm;V=F0ROq3b07*qoM6N<$g8zZc;s5{u diff --git a/intrinsic_loras.txt b/intristic_loras/intrinsic_loras.txt similarity index 100% rename from intrinsic_loras.txt rename to intristic_loras/intrinsic_loras.txt diff --git a/kjweb_async/d3.v6.min.js b/kjweb_async/d3.v6.min.js deleted file mode 100644 index 05cd5ca..0000000 --- a/kjweb_async/d3.v6.min.js +++ /dev/null @@ -1,2 +0,0 @@ -// https://d3js.org v6.7.0 Copyright 2021 Mike Bostock -!function(t,n){"object"==typeof exports&&"undefined"!=typeof module?n(exports):"function"==typeof define&&define.amd?define(["exports"],n):n((t="undefined"!=typeof globalThis?globalThis:t||self).d3=t.d3||{})}(this,(function(t){"use strict";function n(t,n){return tn?1:t>=n?0:NaN}function e(t){let e=t,r=t;function i(t,n,e,i){for(null==e&&(e=0),null==i&&(i=t.length);e>>1;r(t[o],n)<0?e=o+1:i=o}return e}return 1===t.length&&(e=(n,e)=>t(n)-e,r=function(t){return(e,r)=>n(t(e),r)}(t)),{left:i,center:function(t,n,r,o){null==r&&(r=0),null==o&&(o=t.length);const a=i(t,n,r,o-1);return a>r&&e(t[a-1],n)>-e(t[a],n)?a-1:a},right:function(t,n,e,i){for(null==e&&(e=0),null==i&&(i=t.length);e>>1;r(t[o],n)>0?i=o:e=o+1}return e}}}function r(t){return null===t?NaN:+t}const i=e(n),o=i.right,a=i.left,u=e(r).center;function c(t,n){let e=0;if(void 0===n)for(let n of t)null!=n&&(n=+n)>=n&&++e;else{let r=-1;for(let i of t)null!=(i=n(i,++r,t))&&(i=+i)>=i&&++e}return e}function f(t){return 0|t.length}function s(t){return!(t>0)}function l(t){return"object"!=typeof t||"length"in t?t:Array.from(t)}function h(t,n){let e,r=0,i=0,o=0;if(void 0===n)for(let n of t)null!=n&&(n=+n)>=n&&(e=n-i,i+=e/++r,o+=e*(n-i));else{let a=-1;for(let u of t)null!=(u=n(u,++a,t))&&(u=+u)>=u&&(e=u-i,i+=e/++r,o+=e*(u-i))}if(r>1)return o/(r-1)}function d(t,n){const e=h(t,n);return e?Math.sqrt(e):e}function p(t,n){let e,r;if(void 0===n)for(const n of t)null!=n&&(void 0===e?n>=n&&(e=r=n):(e>n&&(e=n),r=o&&(e=r=o):(e>o&&(e=o),r0){for(o=t[--i];i>0&&(n=o,e=t[--i],o=n+e,r=e-(o-n),!r););i>0&&(r<0&&t[i-1]<0||r>0&&t[i-1]>0)&&(e=2*r,n=o+e,e==n-o&&(o=n))}return o}}class y extends Map{constructor(t,n=x){if(super(),Object.defineProperties(this,{_intern:{value:new Map},_key:{value:n}}),null!=t)for(const[n,e]of t)this.set(n,e)}get(t){return super.get(_(this,t))}has(t){return super.has(_(this,t))}set(t,n){return super.set(b(this,t),n)}delete(t){return super.delete(m(this,t))}}class v extends Set{constructor(t,n=x){if(super(),Object.defineProperties(this,{_intern:{value:new Map},_key:{value:n}}),null!=t)for(const n of t)this.add(n)}has(t){return super.has(_(this,t))}add(t){return super.add(b(this,t))}delete(t){return super.delete(m(this,t))}}function _({_intern:t,_key:n},e){const r=n(e);return t.has(r)?t.get(r):e}function b({_intern:t,_key:n},e){const r=n(e);return t.has(r)?t.get(r):(t.set(r,e),e)}function m({_intern:t,_key:n},e){const r=n(e);return t.has(r)&&(e=t.get(e),t.delete(r)),e}function x(t){return null!==t&&"object"==typeof t?t.valueOf():t}function w(t){return t}function M(t,...n){return S(t,w,w,n)}function A(t,n,...e){return S(t,w,n,e)}function T(t){if(1!==t.length)throw new Error("duplicate key");return t[0]}function S(t,n,e,r){return function t(i,o){if(o>=r.length)return e(i);const a=new y,u=r[o++];let c=-1;for(const t of i){const n=u(t,++c,i),e=a.get(n);e?e.push(t):a.set(n,[t])}for(const[n,e]of a)a.set(n,t(e,o));return n(a)}(t,0)}function E(t,n){return Array.from(n,(n=>t[n]))}function k(t,...e){if("function"!=typeof t[Symbol.iterator])throw new TypeError("values is not iterable");t=Array.from(t);let[r=n]=e;if(1===r.length||e.length>1){const i=Uint32Array.from(t,((t,n)=>n));return e.length>1?(e=e.map((n=>t.map(n))),i.sort(((t,r)=>{for(const i of e){const e=n(i[t],i[r]);if(e)return e}}))):(r=t.map(r),i.sort(((t,e)=>n(r[t],r[e])))),E(t,i)}return t.sort(r)}var N=Array.prototype.slice;function C(t){return function(){return t}}var P=Math.sqrt(50),z=Math.sqrt(10),D=Math.sqrt(2);function q(t,n,e){var r,i,o,a,u=-1;if(e=+e,(t=+t)===(n=+n)&&e>0)return[t];if((r=n0){let e=Math.round(t/a),r=Math.round(n/a);for(e*an&&--r,o=new Array(i=r-e+1);++un&&--r,o=new Array(i=r-e+1);++u=0?(o>=P?10:o>=z?5:o>=D?2:1)*Math.pow(10,i):-Math.pow(10,-i)/(o>=P?10:o>=z?5:o>=D?2:1)}function F(t,n,e){var r=Math.abs(n-t)/Math.max(0,e),i=Math.pow(10,Math.floor(Math.log(r)/Math.LN10)),o=r/i;return o>=P?i*=10:o>=z?i*=5:o>=D&&(i*=2),n0?(t=Math.floor(t/i)*i,n=Math.ceil(n/i)*i):i<0&&(t=Math.ceil(t*i)/i,n=Math.floor(n*i)/i),r=i}}function I(t){return Math.ceil(Math.log(c(t))/Math.LN2)+1}function U(){var t=w,n=p,e=I;function r(r){Array.isArray(r)||(r=Array.from(r));var i,a,u=r.length,c=new Array(u);for(i=0;i=l)if(t>=l&&n===p){const t=R(s,l,e);isFinite(t)&&(t>0?l=(Math.floor(l/t)+1)*t:t<0&&(l=(Math.ceil(l*-t)+1)/-t))}else h.pop()}for(var d=h.length;h[0]<=s;)h.shift(),--d;for(;h[d-1]>l;)h.pop(),--d;var g,y=new Array(d+1);for(i=0;i<=d;++i)(g=y[i]=[]).x0=i>0?h[i-1]:s,g.x1=i=n)&&(e=n);else{let r=-1;for(let i of t)null!=(i=n(i,++r,t))&&(e=i)&&(e=i)}return e}function Y(t,n){let e;if(void 0===n)for(const n of t)null!=n&&(e>n||void 0===e&&n>=n)&&(e=n);else{let r=-1;for(let i of t)null!=(i=n(i,++r,t))&&(e>i||void 0===e&&i>=i)&&(e=i)}return e}function L(t,e,r=0,i=t.length-1,o=n){for(;i>r;){if(i-r>600){const n=i-r+1,a=e-r+1,u=Math.log(n),c=.5*Math.exp(2*u/3),f=.5*Math.sqrt(u*c*(n-c)/n)*(a-n/2<0?-1:1);L(t,e,Math.max(r,Math.floor(e-a*c/n+f)),Math.min(i,Math.floor(e+(n-a)*c/n+f)),o)}const n=t[e];let a=r,u=i;for(j(t,r,e),o(t[i],n)>0&&j(t,r,i);a0;)--u}0===o(t[r],n)?j(t,r,u):(++u,j(t,u,i)),u<=e&&(r=u+1),e<=u&&(i=u-1)}return t}function j(t,n,e){const r=t[n];t[n]=t[e],t[e]=r}function H(t,n,e){if(r=(t=Float64Array.from(function*(t,n){if(void 0===n)for(let n of t)null!=n&&(n=+n)>=n&&(yield n);else{let e=-1;for(let r of t)null!=(r=n(r,++e,t))&&(r=+r)>=r&&(yield r)}}(t,e))).length){if((n=+n)<=0||r<2)return Y(t);if(n>=1)return B(t);var r,i=(r-1)*n,o=Math.floor(i),a=B(L(t,o).subarray(0,o+1));return a+(Y(t.subarray(o+1))-a)*(i-o)}}function X(t,n,e=r){if(i=t.length){if((n=+n)<=0||i<2)return+e(t[0],0,t);if(n>=1)return+e(t[i-1],i-1,t);var i,o=(i-1)*n,a=Math.floor(o),u=+e(t[a],a,t);return u+(+e(t[a+1],a+1,t)-u)*(o-a)}}function G(t,n){let e,r=-1,i=-1;if(void 0===n)for(const n of t)++i,null!=n&&(e=n)&&(e=n,r=i);else for(let o of t)null!=(o=n(o,++i,t))&&(e=o)&&(e=o,r=i);return r}function V(t){return Array.from(function*(t){for(const n of t)yield*n}(t))}function $(t,n){let e,r=-1,i=-1;if(void 0===n)for(const n of t)++i,null!=n&&(e>n||void 0===e&&n>=n)&&(e=n,r=i);else for(let o of t)null!=(o=n(o,++i,t))&&(e>o||void 0===e&&o>=o)&&(e=o,r=i);return r}function W(t,n){return[t,n]}function Z(t,n,e){t=+t,n=+n,e=(i=arguments.length)<2?(n=t,t=0,1):i<3?1:+e;for(var r=-1,i=0|Math.max(0,Math.ceil((n-t)/e)),o=new Array(i);++r+t(n)}function st(t,n){return n=Math.max(0,t.bandwidth()-2*n)/2,t.round()&&(n=Math.round(n)),e=>+t(e)+n}function lt(){return!this.__axis}function ht(t,n){var e=[],r=null,i=null,o=6,a=6,u=3,c="undefined"!=typeof window&&window.devicePixelRatio>1?0:.5,f=1===t||4===t?-1:1,s=4===t||2===t?"x":"y",l=1===t||3===t?ut:ct;function h(h){var d=null==r?n.ticks?n.ticks.apply(n,e):n.domain():r,p=null==i?n.tickFormat?n.tickFormat.apply(n,e):ot:i,g=Math.max(o,0)+u,y=n.range(),v=+y[0]+c,_=+y[y.length-1]+c,b=(n.bandwidth?st:ft)(n.copy(),c),m=h.selection?h.selection():h,x=m.selectAll(".domain").data([null]),w=m.selectAll(".tick").data(d,n).order(),M=w.exit(),A=w.enter().append("g").attr("class","tick"),T=w.select("line"),S=w.select("text");x=x.merge(x.enter().insert("path",".tick").attr("class","domain").attr("stroke","currentColor")),w=w.merge(A),T=T.merge(A.append("line").attr("stroke","currentColor").attr(s+"2",f*o)),S=S.merge(A.append("text").attr("fill","currentColor").attr(s,f*g).attr("dy",1===t?"0em":3===t?"0.71em":"0.32em")),h!==m&&(x=x.transition(h),w=w.transition(h),T=T.transition(h),S=S.transition(h),M=M.transition(h).attr("opacity",at).attr("transform",(function(t){return isFinite(t=b(t))?l(t+c):this.getAttribute("transform")})),A.attr("opacity",at).attr("transform",(function(t){var n=this.parentNode.__axis;return l((n&&isFinite(n=n(t))?n:b(t))+c)}))),M.remove(),x.attr("d",4===t||2===t?a?"M"+f*a+","+v+"H"+c+"V"+_+"H"+f*a:"M"+c+","+v+"V"+_:a?"M"+v+","+f*a+"V"+c+"H"+_+"V"+f*a:"M"+v+","+c+"H"+_),w.attr("opacity",1).attr("transform",(function(t){return l(b(t)+c)})),T.attr(s+"2",f*o),S.attr(s,f*g).text(p),m.filter(lt).attr("fill","none").attr("font-size",10).attr("font-family","sans-serif").attr("text-anchor",2===t?"start":4===t?"end":"middle"),m.each((function(){this.__axis=b}))}return h.scale=function(t){return arguments.length?(n=t,h):n},h.ticks=function(){return e=it.call(arguments),h},h.tickArguments=function(t){return arguments.length?(e=null==t?[]:it.call(t),h):e.slice()},h.tickValues=function(t){return arguments.length?(r=null==t?null:it.call(t),h):r&&r.slice()},h.tickFormat=function(t){return arguments.length?(i=t,h):i},h.tickSize=function(t){return arguments.length?(o=a=+t,h):o},h.tickSizeInner=function(t){return arguments.length?(o=+t,h):o},h.tickSizeOuter=function(t){return arguments.length?(a=+t,h):a},h.tickPadding=function(t){return arguments.length?(u=+t,h):u},h.offset=function(t){return arguments.length?(c=+t,h):c},h}var dt={value:()=>{}};function pt(){for(var t,n=0,e=arguments.length,r={};n=0&&(e=t.slice(r+1),t=t.slice(0,r)),t&&!n.hasOwnProperty(t))throw new Error("unknown type: "+t);return{type:t,name:e}}))}function vt(t,n){for(var e,r=0,i=t.length;r0)for(var e,r,i=new Array(e),o=0;o=0&&"xmlns"!==(n=t.slice(0,e))&&(t=t.slice(e+1)),mt.hasOwnProperty(n)?{space:mt[n],local:t}:t}function wt(t){return function(){var n=this.ownerDocument,e=this.namespaceURI;return e===bt&&n.documentElement.namespaceURI===bt?n.createElement(t):n.createElementNS(e,t)}}function Mt(t){return function(){return this.ownerDocument.createElementNS(t.space,t.local)}}function At(t){var n=xt(t);return(n.local?Mt:wt)(n)}function Tt(){}function St(t){return null==t?Tt:function(){return this.querySelector(t)}}function Et(t){return"object"==typeof t&&"length"in t?t:Array.from(t)}function kt(){return[]}function Nt(t){return null==t?kt:function(){return this.querySelectorAll(t)}}function Ct(t){return function(){return this.matches(t)}}function Pt(t){return function(n){return n.matches(t)}}var zt=Array.prototype.find;function Dt(){return this.firstElementChild}var qt=Array.prototype.filter;function Rt(){return this.children}function Ft(t){return new Array(t.length)}function Ot(t,n){this.ownerDocument=t.ownerDocument,this.namespaceURI=t.namespaceURI,this._next=null,this._parent=t,this.__data__=n}function It(t){return function(){return t}}function Ut(t,n,e,r,i,o){for(var a,u=0,c=n.length,f=o.length;un?1:t>=n?0:NaN}function jt(t){return function(){this.removeAttribute(t)}}function Ht(t){return function(){this.removeAttributeNS(t.space,t.local)}}function Xt(t,n){return function(){this.setAttribute(t,n)}}function Gt(t,n){return function(){this.setAttributeNS(t.space,t.local,n)}}function Vt(t,n){return function(){var e=n.apply(this,arguments);null==e?this.removeAttribute(t):this.setAttribute(t,e)}}function $t(t,n){return function(){var e=n.apply(this,arguments);null==e?this.removeAttributeNS(t.space,t.local):this.setAttributeNS(t.space,t.local,e)}}function Wt(t){return t.ownerDocument&&t.ownerDocument.defaultView||t.document&&t||t.defaultView}function Zt(t){return function(){this.style.removeProperty(t)}}function Kt(t,n,e){return function(){this.style.setProperty(t,n,e)}}function Qt(t,n,e){return function(){var r=n.apply(this,arguments);null==r?this.style.removeProperty(t):this.style.setProperty(t,r,e)}}function Jt(t,n){return t.style.getPropertyValue(n)||Wt(t).getComputedStyle(t,null).getPropertyValue(n)}function tn(t){return function(){delete this[t]}}function nn(t,n){return function(){this[t]=n}}function en(t,n){return function(){var e=n.apply(this,arguments);null==e?delete this[t]:this[t]=e}}function rn(t){return t.trim().split(/^|\s+/)}function on(t){return t.classList||new an(t)}function an(t){this._node=t,this._names=rn(t.getAttribute("class")||"")}function un(t,n){for(var e=on(t),r=-1,i=n.length;++r=0&&(n=t.slice(e+1),t=t.slice(0,e)),{type:t,name:n}}))}function Tn(t){return function(){var n=this.__on;if(n){for(var e,r=0,i=-1,o=n.length;r=0&&(this._names.splice(n,1),this._node.setAttribute("class",this._names.join(" ")))},contains:function(t){return this._names.indexOf(t)>=0}};var Cn=[null];function Pn(t,n){this._groups=t,this._parents=n}function zn(){return new Pn([[document.documentElement]],Cn)}function Dn(t){return"string"==typeof t?new Pn([[document.querySelector(t)]],[document.documentElement]):new Pn([[t]],Cn)}Pn.prototype=zn.prototype={constructor:Pn,select:function(t){"function"!=typeof t&&(t=St(t));for(var n=this._groups,e=n.length,r=new Array(e),i=0;i=x&&(x=m+1);!(b=y[x])&&++x=0;)(r=i[o])&&(a&&4^r.compareDocumentPosition(a)&&a.parentNode.insertBefore(r,a),a=r);return this},sort:function(t){function n(n,e){return n&&e?t(n.__data__,e.__data__):!n-!e}t||(t=Lt);for(var e=this._groups,r=e.length,i=new Array(r),o=0;o1?this.each((null==n?Zt:"function"==typeof n?Qt:Kt)(t,n,null==e?"":e)):Jt(this.node(),t)},property:function(t,n){return arguments.length>1?this.each((null==n?tn:"function"==typeof n?en:nn)(t,n)):this.node()[t]},classed:function(t,n){var e=rn(t+"");if(arguments.length<2){for(var r=on(this.node()),i=-1,o=e.length;++i()=>t;function Hn(t,{sourceEvent:n,subject:e,target:r,identifier:i,active:o,x:a,y:u,dx:c,dy:f,dispatch:s}){Object.defineProperties(this,{type:{value:t,enumerable:!0,configurable:!0},sourceEvent:{value:n,enumerable:!0,configurable:!0},subject:{value:e,enumerable:!0,configurable:!0},target:{value:r,enumerable:!0,configurable:!0},identifier:{value:i,enumerable:!0,configurable:!0},active:{value:o,enumerable:!0,configurable:!0},x:{value:a,enumerable:!0,configurable:!0},y:{value:u,enumerable:!0,configurable:!0},dx:{value:c,enumerable:!0,configurable:!0},dy:{value:f,enumerable:!0,configurable:!0},_:{value:s}})}function Xn(t){return!t.ctrlKey&&!t.button}function Gn(){return this.parentNode}function Vn(t,n){return null==n?{x:t.x,y:t.y}:n}function $n(){return navigator.maxTouchPoints||"ontouchstart"in this}function Wn(t,n,e){t.prototype=n.prototype=e,e.constructor=t}function Zn(t,n){var e=Object.create(t.prototype);for(var r in n)e[r]=n[r];return e}function Kn(){}Hn.prototype.on=function(){var t=this._.on.apply(this._,arguments);return t===this._?this:t};var Qn=.7,Jn=1/Qn,te="\\s*([+-]?\\d+)\\s*",ne="\\s*([+-]?\\d*\\.?\\d+(?:[eE][+-]?\\d+)?)\\s*",ee="\\s*([+-]?\\d*\\.?\\d+(?:[eE][+-]?\\d+)?)%\\s*",re=/^#([0-9a-f]{3,8})$/,ie=new RegExp("^rgb\\("+[te,te,te]+"\\)$"),oe=new RegExp("^rgb\\("+[ee,ee,ee]+"\\)$"),ae=new RegExp("^rgba\\("+[te,te,te,ne]+"\\)$"),ue=new RegExp("^rgba\\("+[ee,ee,ee,ne]+"\\)$"),ce=new RegExp("^hsl\\("+[ne,ee,ee]+"\\)$"),fe=new RegExp("^hsla\\("+[ne,ee,ee,ne]+"\\)$"),se={aliceblue:15792383,antiquewhite:16444375,aqua:65535,aquamarine:8388564,azure:15794175,beige:16119260,bisque:16770244,black:0,blanchedalmond:16772045,blue:255,blueviolet:9055202,brown:10824234,burlywood:14596231,cadetblue:6266528,chartreuse:8388352,chocolate:13789470,coral:16744272,cornflowerblue:6591981,cornsilk:16775388,crimson:14423100,cyan:65535,darkblue:139,darkcyan:35723,darkgoldenrod:12092939,darkgray:11119017,darkgreen:25600,darkgrey:11119017,darkkhaki:12433259,darkmagenta:9109643,darkolivegreen:5597999,darkorange:16747520,darkorchid:10040012,darkred:9109504,darksalmon:15308410,darkseagreen:9419919,darkslateblue:4734347,darkslategray:3100495,darkslategrey:3100495,darkturquoise:52945,darkviolet:9699539,deeppink:16716947,deepskyblue:49151,dimgray:6908265,dimgrey:6908265,dodgerblue:2003199,firebrick:11674146,floralwhite:16775920,forestgreen:2263842,fuchsia:16711935,gainsboro:14474460,ghostwhite:16316671,gold:16766720,goldenrod:14329120,gray:8421504,green:32768,greenyellow:11403055,grey:8421504,honeydew:15794160,hotpink:16738740,indianred:13458524,indigo:4915330,ivory:16777200,khaki:15787660,lavender:15132410,lavenderblush:16773365,lawngreen:8190976,lemonchiffon:16775885,lightblue:11393254,lightcoral:15761536,lightcyan:14745599,lightgoldenrodyellow:16448210,lightgray:13882323,lightgreen:9498256,lightgrey:13882323,lightpink:16758465,lightsalmon:16752762,lightseagreen:2142890,lightskyblue:8900346,lightslategray:7833753,lightslategrey:7833753,lightsteelblue:11584734,lightyellow:16777184,lime:65280,limegreen:3329330,linen:16445670,magenta:16711935,maroon:8388608,mediumaquamarine:6737322,mediumblue:205,mediumorchid:12211667,mediumpurple:9662683,mediumseagreen:3978097,mediumslateblue:8087790,mediumspringgreen:64154,mediumturquoise:4772300,mediumvioletred:13047173,midnightblue:1644912,mintcream:16121850,mistyrose:16770273,moccasin:16770229,navajowhite:16768685,navy:128,oldlace:16643558,olive:8421376,olivedrab:7048739,orange:16753920,orangered:16729344,orchid:14315734,palegoldenrod:15657130,palegreen:10025880,paleturquoise:11529966,palevioletred:14381203,papayawhip:16773077,peachpuff:16767673,peru:13468991,pink:16761035,plum:14524637,powderblue:11591910,purple:8388736,rebeccapurple:6697881,red:16711680,rosybrown:12357519,royalblue:4286945,saddlebrown:9127187,salmon:16416882,sandybrown:16032864,seagreen:3050327,seashell:16774638,sienna:10506797,silver:12632256,skyblue:8900331,slateblue:6970061,slategray:7372944,slategrey:7372944,snow:16775930,springgreen:65407,steelblue:4620980,tan:13808780,teal:32896,thistle:14204888,tomato:16737095,turquoise:4251856,violet:15631086,wheat:16113331,white:16777215,whitesmoke:16119285,yellow:16776960,yellowgreen:10145074};function le(){return this.rgb().formatHex()}function he(){return this.rgb().formatRgb()}function de(t){var n,e;return t=(t+"").trim().toLowerCase(),(n=re.exec(t))?(e=n[1].length,n=parseInt(n[1],16),6===e?pe(n):3===e?new _e(n>>8&15|n>>4&240,n>>4&15|240&n,(15&n)<<4|15&n,1):8===e?ge(n>>24&255,n>>16&255,n>>8&255,(255&n)/255):4===e?ge(n>>12&15|n>>8&240,n>>8&15|n>>4&240,n>>4&15|240&n,((15&n)<<4|15&n)/255):null):(n=ie.exec(t))?new _e(n[1],n[2],n[3],1):(n=oe.exec(t))?new _e(255*n[1]/100,255*n[2]/100,255*n[3]/100,1):(n=ae.exec(t))?ge(n[1],n[2],n[3],n[4]):(n=ue.exec(t))?ge(255*n[1]/100,255*n[2]/100,255*n[3]/100,n[4]):(n=ce.exec(t))?we(n[1],n[2]/100,n[3]/100,1):(n=fe.exec(t))?we(n[1],n[2]/100,n[3]/100,n[4]):se.hasOwnProperty(t)?pe(se[t]):"transparent"===t?new _e(NaN,NaN,NaN,0):null}function pe(t){return new _e(t>>16&255,t>>8&255,255&t,1)}function ge(t,n,e,r){return r<=0&&(t=n=e=NaN),new _e(t,n,e,r)}function ye(t){return t instanceof Kn||(t=de(t)),t?new _e((t=t.rgb()).r,t.g,t.b,t.opacity):new _e}function ve(t,n,e,r){return 1===arguments.length?ye(t):new _e(t,n,e,null==r?1:r)}function _e(t,n,e,r){this.r=+t,this.g=+n,this.b=+e,this.opacity=+r}function be(){return"#"+xe(this.r)+xe(this.g)+xe(this.b)}function me(){var t=this.opacity;return(1===(t=isNaN(t)?1:Math.max(0,Math.min(1,t)))?"rgb(":"rgba(")+Math.max(0,Math.min(255,Math.round(this.r)||0))+", "+Math.max(0,Math.min(255,Math.round(this.g)||0))+", "+Math.max(0,Math.min(255,Math.round(this.b)||0))+(1===t?")":", "+t+")")}function xe(t){return((t=Math.max(0,Math.min(255,Math.round(t)||0)))<16?"0":"")+t.toString(16)}function we(t,n,e,r){return r<=0?t=n=e=NaN:e<=0||e>=1?t=n=NaN:n<=0&&(t=NaN),new Te(t,n,e,r)}function Me(t){if(t instanceof Te)return new Te(t.h,t.s,t.l,t.opacity);if(t instanceof Kn||(t=de(t)),!t)return new Te;if(t instanceof Te)return t;var n=(t=t.rgb()).r/255,e=t.g/255,r=t.b/255,i=Math.min(n,e,r),o=Math.max(n,e,r),a=NaN,u=o-i,c=(o+i)/2;return u?(a=n===o?(e-r)/u+6*(e0&&c<1?0:a,new Te(a,u,c,t.opacity)}function Ae(t,n,e,r){return 1===arguments.length?Me(t):new Te(t,n,e,null==r?1:r)}function Te(t,n,e,r){this.h=+t,this.s=+n,this.l=+e,this.opacity=+r}function Se(t,n,e){return 255*(t<60?n+(e-n)*t/60:t<180?e:t<240?n+(e-n)*(240-t)/60:n)}Wn(Kn,de,{copy:function(t){return Object.assign(new this.constructor,this,t)},displayable:function(){return this.rgb().displayable()},hex:le,formatHex:le,formatHsl:function(){return Me(this).formatHsl()},formatRgb:he,toString:he}),Wn(_e,ve,Zn(Kn,{brighter:function(t){return t=null==t?Jn:Math.pow(Jn,t),new _e(this.r*t,this.g*t,this.b*t,this.opacity)},darker:function(t){return t=null==t?Qn:Math.pow(Qn,t),new _e(this.r*t,this.g*t,this.b*t,this.opacity)},rgb:function(){return this},displayable:function(){return-.5<=this.r&&this.r<255.5&&-.5<=this.g&&this.g<255.5&&-.5<=this.b&&this.b<255.5&&0<=this.opacity&&this.opacity<=1},hex:be,formatHex:be,formatRgb:me,toString:me})),Wn(Te,Ae,Zn(Kn,{brighter:function(t){return t=null==t?Jn:Math.pow(Jn,t),new Te(this.h,this.s,this.l*t,this.opacity)},darker:function(t){return t=null==t?Qn:Math.pow(Qn,t),new Te(this.h,this.s,this.l*t,this.opacity)},rgb:function(){var t=this.h%360+360*(this.h<0),n=isNaN(t)||isNaN(this.s)?0:this.s,e=this.l,r=e+(e<.5?e:1-e)*n,i=2*e-r;return new _e(Se(t>=240?t-240:t+120,i,r),Se(t,i,r),Se(t<120?t+240:t-120,i,r),this.opacity)},displayable:function(){return(0<=this.s&&this.s<=1||isNaN(this.s))&&0<=this.l&&this.l<=1&&0<=this.opacity&&this.opacity<=1},formatHsl:function(){var t=this.opacity;return(1===(t=isNaN(t)?1:Math.max(0,Math.min(1,t)))?"hsl(":"hsla(")+(this.h||0)+", "+100*(this.s||0)+"%, "+100*(this.l||0)+"%"+(1===t?")":", "+t+")")}}));const Ee=Math.PI/180,ke=180/Math.PI,Ne=.96422,Ce=.82521,Pe=4/29,ze=6/29,De=3*ze*ze;function qe(t){if(t instanceof Fe)return new Fe(t.l,t.a,t.b,t.opacity);if(t instanceof je)return He(t);t instanceof _e||(t=ye(t));var n,e,r=Be(t.r),i=Be(t.g),o=Be(t.b),a=Oe((.2225045*r+.7168786*i+.0606169*o)/1);return r===i&&i===o?n=e=a:(n=Oe((.4360747*r+.3850649*i+.1430804*o)/Ne),e=Oe((.0139322*r+.0971045*i+.7141733*o)/Ce)),new Fe(116*a-16,500*(n-a),200*(a-e),t.opacity)}function Re(t,n,e,r){return 1===arguments.length?qe(t):new Fe(t,n,e,null==r?1:r)}function Fe(t,n,e,r){this.l=+t,this.a=+n,this.b=+e,this.opacity=+r}function Oe(t){return t>.008856451679035631?Math.pow(t,1/3):t/De+Pe}function Ie(t){return t>ze?t*t*t:De*(t-Pe)}function Ue(t){return 255*(t<=.0031308?12.92*t:1.055*Math.pow(t,1/2.4)-.055)}function Be(t){return(t/=255)<=.04045?t/12.92:Math.pow((t+.055)/1.055,2.4)}function Ye(t){if(t instanceof je)return new je(t.h,t.c,t.l,t.opacity);if(t instanceof Fe||(t=qe(t)),0===t.a&&0===t.b)return new je(NaN,0=1?(e=1,n-1):Math.floor(e*n),i=t[r],o=t[r+1],a=r>0?t[r-1]:2*i-o,u=r()=>t;function ar(t,n){return function(e){return t+e*n}}function ur(t,n){var e=n-t;return e?ar(t,e>180||e<-180?e-360*Math.round(e/360):e):or(isNaN(t)?n:t)}function cr(t){return 1==(t=+t)?fr:function(n,e){return e-n?function(t,n,e){return t=Math.pow(t,e),n=Math.pow(n,e)-t,e=1/e,function(r){return Math.pow(t+r*n,e)}}(n,e,t):or(isNaN(n)?e:n)}}function fr(t,n){var e=n-t;return e?ar(t,e):or(isNaN(t)?n:t)}var sr=function t(n){var e=cr(n);function r(t,n){var r=e((t=ve(t)).r,(n=ve(n)).r),i=e(t.g,n.g),o=e(t.b,n.b),a=fr(t.opacity,n.opacity);return function(n){return t.r=r(n),t.g=i(n),t.b=o(n),t.opacity=a(n),t+""}}return r.gamma=t,r}(1);function lr(t){return function(n){var e,r,i=n.length,o=new Array(i),a=new Array(i),u=new Array(i);for(e=0;eo&&(i=n.slice(o,i),u[a]?u[a]+=i:u[++a]=i),(e=e[0])===(r=r[0])?u[a]?u[a]+=r:u[++a]=r:(u[++a]=null,c.push({i:a,x:_r(e,r)})),o=xr.lastIndex;return o180?n+=360:n-t>180&&(t+=360),o.push({i:e.push(i(e)+"rotate(",null,r)-2,x:_r(t,n)})):n&&e.push(i(e)+"rotate("+n+r)}(o.rotate,a.rotate,u,c),function(t,n,e,o){t!==n?o.push({i:e.push(i(e)+"skewX(",null,r)-2,x:_r(t,n)}):n&&e.push(i(e)+"skewX("+n+r)}(o.skewX,a.skewX,u,c),function(t,n,e,r,o,a){if(t!==e||n!==r){var u=o.push(i(o)+"scale(",null,",",null,")");a.push({i:u-4,x:_r(t,e)},{i:u-2,x:_r(n,r)})}else 1===e&&1===r||o.push(i(o)+"scale("+e+","+r+")")}(o.scaleX,o.scaleY,a.scaleX,a.scaleY,u,c),o=a=null,function(t){for(var n,e=-1,r=c.length;++e=0&&n._call.call(null,t),n=n._next;--Gr}function oi(){Zr=(Wr=Qr.now())+Kr,Gr=Vr=0;try{ii()}finally{Gr=0,function(){var t,n,e=Hr,r=1/0;for(;e;)e._call?(r>e._time&&(r=e._time),t=e,e=e._next):(n=e._next,e._next=null,e=t?t._next=n:Hr=n);Xr=t,ui(r)}(),Zr=0}}function ai(){var t=Qr.now(),n=t-Wr;n>1e3&&(Kr-=n,Wr=t)}function ui(t){Gr||(Vr&&(Vr=clearTimeout(Vr)),t-Zr>24?(t<1/0&&(Vr=setTimeout(oi,t-Qr.now()-Kr)),$r&&($r=clearInterval($r))):($r||(Wr=Qr.now(),$r=setInterval(ai,1e3)),Gr=1,Jr(oi)))}function ci(t,n,e){var r=new ei;return n=null==n?0:+n,r.restart((e=>{r.stop(),t(e+n)}),n,e),r}ei.prototype=ri.prototype={constructor:ei,restart:function(t,n,e){if("function"!=typeof t)throw new TypeError("callback is not a function");e=(null==e?ti():+e)+(null==n?0:+n),this._next||Xr===this||(Xr?Xr._next=this:Hr=this,Xr=this),this._call=t,this._time=e,ui()},stop:function(){this._call&&(this._call=null,this._time=1/0,ui())}};var fi=pt("start","end","cancel","interrupt"),si=[];function li(t,n,e,r,i,o){var a=t.__transition;if(a){if(e in a)return}else t.__transition={};!function(t,n,e){var r,i=t.__transition;function o(t){e.state=1,e.timer.restart(a,e.delay,e.time),e.delay<=t&&a(t-e.delay)}function a(o){var f,s,l,h;if(1!==e.state)return c();for(f in i)if((h=i[f]).name===e.name){if(3===h.state)return ci(a);4===h.state?(h.state=6,h.timer.stop(),h.on.call("interrupt",t,t.__data__,h.index,h.group),delete i[f]):+f0)throw new Error("too late; already scheduled");return e}function di(t,n){var e=pi(t,n);if(e.state>3)throw new Error("too late; already running");return e}function pi(t,n){var e=t.__transition;if(!e||!(e=e[n]))throw new Error("transition not found");return e}function gi(t,n){var e,r,i,o=t.__transition,a=!0;if(o){for(i in n=null==n?null:n+"",o)(e=o[i]).name===n?(r=e.state>2&&e.state<5,e.state=6,e.timer.stop(),e.on.call(r?"interrupt":"cancel",t,t.__data__,e.index,e.group),delete o[i]):a=!1;a&&delete t.__transition}}function yi(t,n){var e,r;return function(){var i=di(this,t),o=i.tween;if(o!==e)for(var a=0,u=(r=e=o).length;a=0&&(t=t.slice(0,n)),!t||"start"===t}))}(n)?hi:di;return function(){var a=o(this,t),u=a.on;u!==r&&(i=(r=u).copy()).on(n,e),a.on=i}}var Fi=zn.prototype.constructor;function Oi(t){return function(){this.style.removeProperty(t)}}function Ii(t,n,e){return function(r){this.style.setProperty(t,n.call(this,r),e)}}function Ui(t,n,e){var r,i;function o(){var o=n.apply(this,arguments);return o!==i&&(r=(i=o)&&Ii(t,o,e)),r}return o._value=n,o}function Bi(t){return function(n){this.textContent=t.call(this,n)}}function Yi(t){var n,e;function r(){var r=t.apply(this,arguments);return r!==e&&(n=(e=r)&&Bi(r)),n}return r._value=t,r}var Li=0;function ji(t,n,e,r){this._groups=t,this._parents=n,this._name=e,this._id=r}function Hi(t){return zn().transition(t)}function Xi(){return++Li}var Gi=zn.prototype;ji.prototype=Hi.prototype={constructor:ji,select:function(t){var n=this._name,e=this._id;"function"!=typeof t&&(t=St(t));for(var r=this._groups,i=r.length,o=new Array(i),a=0;a()=>t;function mo(t,{sourceEvent:n,target:e,selection:r,mode:i,dispatch:o}){Object.defineProperties(this,{type:{value:t,enumerable:!0,configurable:!0},sourceEvent:{value:n,enumerable:!0,configurable:!0},target:{value:e,enumerable:!0,configurable:!0},selection:{value:r,enumerable:!0,configurable:!0},mode:{value:i,enumerable:!0,configurable:!0},_:{value:o}})}function xo(t){t.stopImmediatePropagation()}function wo(t){t.preventDefault(),t.stopImmediatePropagation()}var Mo={name:"drag"},Ao={name:"space"},To={name:"handle"},So={name:"center"};const{abs:Eo,max:ko,min:No}=Math;function Co(t){return[+t[0],+t[1]]}function Po(t){return[Co(t[0]),Co(t[1])]}var zo={name:"x",handles:["w","e"].map(Bo),input:function(t,n){return null==t?null:[[+t[0],n[0][1]],[+t[1],n[1][1]]]},output:function(t){return t&&[t[0][0],t[1][0]]}},Do={name:"y",handles:["n","s"].map(Bo),input:function(t,n){return null==t?null:[[n[0][0],+t[0]],[n[1][0],+t[1]]]},output:function(t){return t&&[t[0][1],t[1][1]]}},qo={name:"xy",handles:["n","w","e","s","nw","ne","sw","se"].map(Bo),input:function(t){return null==t?null:Po(t)},output:function(t){return t}},Ro={overlay:"crosshair",selection:"move",n:"ns-resize",e:"ew-resize",s:"ns-resize",w:"ew-resize",nw:"nwse-resize",ne:"nesw-resize",se:"nwse-resize",sw:"nesw-resize"},Fo={e:"w",w:"e",nw:"ne",ne:"nw",se:"sw",sw:"se"},Oo={n:"s",s:"n",nw:"sw",ne:"se",se:"ne",sw:"nw"},Io={overlay:1,selection:1,n:null,e:1,s:null,w:-1,nw:-1,ne:1,se:1,sw:-1},Uo={overlay:1,selection:1,n:-1,e:null,s:1,w:null,nw:-1,ne:-1,se:1,sw:1};function Bo(t){return{type:t}}function Yo(t){return!t.ctrlKey&&!t.button}function Lo(){var t=this.ownerSVGElement||this;return t.hasAttribute("viewBox")?[[(t=t.viewBox.baseVal).x,t.y],[t.x+t.width,t.y+t.height]]:[[0,0],[t.width.baseVal.value,t.height.baseVal.value]]}function jo(){return navigator.maxTouchPoints||"ontouchstart"in this}function Ho(t){for(;!t.__brush;)if(!(t=t.parentNode))return;return t.__brush}function Xo(t){return t[0][0]===t[1][0]||t[0][1]===t[1][1]}function Go(t){var n,e=Lo,r=Yo,i=jo,o=!0,a=pt("start","brush","end"),u=6;function c(n){var e=n.property("__brush",g).selectAll(".overlay").data([Bo("overlay")]);e.enter().append("rect").attr("class","overlay").attr("pointer-events","all").attr("cursor",Ro.overlay).merge(e).each((function(){var t=Ho(this).extent;Dn(this).attr("x",t[0][0]).attr("y",t[0][1]).attr("width",t[1][0]-t[0][0]).attr("height",t[1][1]-t[0][1])})),n.selectAll(".selection").data([Bo("selection")]).enter().append("rect").attr("class","selection").attr("cursor",Ro.selection).attr("fill","#777").attr("fill-opacity",.3).attr("stroke","#fff").attr("shape-rendering","crispEdges");var r=n.selectAll(".handle").data(t.handles,(function(t){return t.type}));r.exit().remove(),r.enter().append("rect").attr("class",(function(t){return"handle handle--"+t.type})).attr("cursor",(function(t){return Ro[t.type]})),n.each(f).attr("fill","none").attr("pointer-events","all").on("mousedown.brush",h).filter(i).on("touchstart.brush",h).on("touchmove.brush",d).on("touchend.brush touchcancel.brush",p).style("touch-action","none").style("-webkit-tap-highlight-color","rgba(0,0,0,0)")}function f(){var t=Dn(this),n=Ho(this).selection;n?(t.selectAll(".selection").style("display",null).attr("x",n[0][0]).attr("y",n[0][1]).attr("width",n[1][0]-n[0][0]).attr("height",n[1][1]-n[0][1]),t.selectAll(".handle").style("display",null).attr("x",(function(t){return"e"===t.type[t.type.length-1]?n[1][0]-u/2:n[0][0]-u/2})).attr("y",(function(t){return"s"===t.type[0]?n[1][1]-u/2:n[0][1]-u/2})).attr("width",(function(t){return"n"===t.type||"s"===t.type?n[1][0]-n[0][0]+u:u})).attr("height",(function(t){return"e"===t.type||"w"===t.type?n[1][1]-n[0][1]+u:u}))):t.selectAll(".selection,.handle").style("display","none").attr("x",null).attr("y",null).attr("width",null).attr("height",null)}function s(t,n,e){var r=t.__brush.emitter;return!r||e&&r.clean?new l(t,n,e):r}function l(t,n,e){this.that=t,this.args=n,this.state=t.__brush,this.active=0,this.clean=e}function h(e){if((!n||e.touches)&&r.apply(this,arguments)){var i,a,u,c,l,h,d,p,g,y,v,_=this,b=e.target.__data__.type,m="selection"===(o&&e.metaKey?b="overlay":b)?Mo:o&&e.altKey?So:To,x=t===Do?null:Io[b],w=t===zo?null:Uo[b],M=Ho(_),A=M.extent,T=M.selection,S=A[0][0],E=A[0][1],k=A[1][0],N=A[1][1],C=0,P=0,z=x&&w&&o&&e.shiftKey,D=Array.from(e.touches||[e],(t=>{const n=t.identifier;return(t=In(t,_)).point0=t.slice(),t.identifier=n,t}));if("overlay"===b){T&&(g=!0);const n=[D[0],D[1]||D[0]];M.selection=T=[[i=t===Do?S:No(n[0][0],n[1][0]),u=t===zo?E:No(n[0][1],n[1][1])],[l=t===Do?k:ko(n[0][0],n[1][0]),d=t===zo?N:ko(n[0][1],n[1][1])]],D.length>1&&U()}else i=T[0][0],u=T[0][1],l=T[1][0],d=T[1][1];a=i,c=u,h=l,p=d;var q=Dn(_).attr("pointer-events","none"),R=q.selectAll(".overlay").attr("cursor",Ro[b]);gi(_);var F=s(_,arguments,!0).beforestart();if(e.touches)F.moved=I,F.ended=B;else{var O=Dn(e.view).on("mousemove.brush",I,!0).on("mouseup.brush",B,!0);o&&O.on("keydown.brush",Y,!0).on("keyup.brush",L,!0),Yn(e.view)}f.call(_),F.start(e,m.name)}function I(t){for(const n of t.changedTouches||[t])for(const t of D)t.identifier===n.identifier&&(t.cur=In(n,_));if(z&&!y&&!v&&1===D.length){const t=D[0];Eo(t.cur[0]-t[0])>Eo(t.cur[1]-t[1])?v=!0:y=!0}for(const t of D)t.cur&&(t[0]=t.cur[0],t[1]=t.cur[1]);g=!0,wo(t),U(t)}function U(t){const n=D[0],e=n.point0;var r;switch(C=n[0]-e[0],P=n[1]-e[1],m){case Ao:case Mo:x&&(C=ko(S-i,No(k-l,C)),a=i+C,h=l+C),w&&(P=ko(E-u,No(N-d,P)),c=u+P,p=d+P);break;case To:D[1]?(x&&(a=ko(S,No(k,D[0][0])),h=ko(S,No(k,D[1][0])),x=1),w&&(c=ko(E,No(N,D[0][1])),p=ko(E,No(N,D[1][1])),w=1)):(x<0?(C=ko(S-i,No(k-i,C)),a=i+C,h=l):x>0&&(C=ko(S-l,No(k-l,C)),a=i,h=l+C),w<0?(P=ko(E-u,No(N-u,P)),c=u+P,p=d):w>0&&(P=ko(E-d,No(N-d,P)),c=u,p=d+P));break;case So:x&&(a=ko(S,No(k,i-C*x)),h=ko(S,No(k,l+C*x))),w&&(c=ko(E,No(N,u-P*w)),p=ko(E,No(N,d+P*w)))}h0&&(i=a-C),w<0?d=p-P:w>0&&(u=c-P),m=Ao,R.attr("cursor",Ro.selection),U());break;default:return}wo(t)}function L(t){switch(t.keyCode){case 16:z&&(y=v=z=!1,U());break;case 18:m===So&&(x<0?l=h:x>0&&(i=a),w<0?d=p:w>0&&(u=c),m=To,U());break;case 32:m===Ao&&(t.altKey?(x&&(l=h-C*x,i=a+C*x),w&&(d=p-P*w,u=c+P*w),m=So):(x<0?l=h:x>0&&(i=a),w<0?d=p:w>0&&(u=c),m=To),R.attr("cursor",Ro[b]),U());break;default:return}wo(t)}}function d(t){s(this,arguments).moved(t)}function p(t){s(this,arguments).ended(t)}function g(){var n=this.__brush||{selection:null};return n.extent=Po(e.apply(this,arguments)),n.dim=t,n}return c.move=function(n,e){n.tween?n.on("start.brush",(function(t){s(this,arguments).beforestart().start(t)})).on("interrupt.brush end.brush",(function(t){s(this,arguments).end(t)})).tween("brush",(function(){var n=this,r=n.__brush,i=s(n,arguments),o=r.selection,a=t.input("function"==typeof e?e.apply(this,arguments):e,r.extent),u=Mr(o,a);function c(t){r.selection=1===t&&null===a?null:u(t),f.call(n),i.brush()}return null!==o&&null!==a?c:c(1)})):n.each((function(){var n=this,r=arguments,i=n.__brush,o=t.input("function"==typeof e?e.apply(n,r):e,i.extent),a=s(n,r).beforestart();gi(n),i.selection=null===o?null:o,f.call(n),a.start().brush().end()}))},c.clear=function(t){c.move(t,null)},l.prototype={beforestart:function(){return 1==++this.active&&(this.state.emitter=this,this.starting=!0),this},start:function(t,n){return this.starting?(this.starting=!1,this.emit("start",t,n)):this.emit("brush",t),this},brush:function(t,n){return this.emit("brush",t,n),this},end:function(t,n){return 0==--this.active&&(delete this.state.emitter,this.emit("end",t,n)),this},emit:function(n,e,r){var i=Dn(this.that).datum();a.call(n,this.that,new mo(n,{sourceEvent:e,target:c,selection:t.output(this.state.selection),mode:r,dispatch:a}),i)}},c.extent=function(t){return arguments.length?(e="function"==typeof t?t:bo(Po(t)),c):e},c.filter=function(t){return arguments.length?(r="function"==typeof t?t:bo(!!t),c):r},c.touchable=function(t){return arguments.length?(i="function"==typeof t?t:bo(!!t),c):i},c.handleSize=function(t){return arguments.length?(u=+t,c):u},c.keyModifiers=function(t){return arguments.length?(o=!!t,c):o},c.on=function(){var t=a.on.apply(a,arguments);return t===a?c:t},c}var Vo=Math.abs,$o=Math.cos,Wo=Math.sin,Zo=Math.PI,Ko=Zo/2,Qo=2*Zo,Jo=Math.max,ta=1e-12;function na(t,n){return Array.from({length:n-t},((n,e)=>t+e))}function ea(t){return function(n,e){return t(n.source.value+n.target.value,e.source.value+e.target.value)}}function ra(t,n){var e=0,r=null,i=null,o=null;function a(a){var u,c=a.length,f=new Array(c),s=na(0,c),l=new Array(c*c),h=new Array(c),d=0;a=Float64Array.from({length:c*c},n?(t,n)=>a[n%c][n/c|0]:(t,n)=>a[n/c|0][n%c]);for(let n=0;nr(f[t],f[n])));for(const e of s){const r=n;if(t){const t=na(1+~c,c).filter((t=>t<0?a[~t*c+e]:a[e*c+t]));i&&t.sort(((t,n)=>i(t<0?-a[~t*c+e]:a[e*c+t],n<0?-a[~n*c+e]:a[e*c+n])));for(const r of t)if(r<0){(l[~r*c+e]||(l[~r*c+e]={source:null,target:null})).target={index:e,startAngle:n,endAngle:n+=a[~r*c+e]*d,value:a[~r*c+e]}}else{(l[e*c+r]||(l[e*c+r]={source:null,target:null})).source={index:e,startAngle:n,endAngle:n+=a[e*c+r]*d,value:a[e*c+r]}}h[e]={index:e,startAngle:r,endAngle:n,value:f[e]}}else{const t=na(0,c).filter((t=>a[e*c+t]||a[t*c+e]));i&&t.sort(((t,n)=>i(a[e*c+t],a[e*c+n])));for(const r of t){let t;if(eaa)if(Math.abs(s*u-c*f)>aa&&i){var h=e-o,d=r-a,p=u*u+c*c,g=h*h+d*d,y=Math.sqrt(p),v=Math.sqrt(l),_=i*Math.tan((ia-Math.acos((p+l-g)/(2*y*v)))/2),b=_/v,m=_/y;Math.abs(b-1)>aa&&(this._+="L"+(t+b*f)+","+(n+b*s)),this._+="A"+i+","+i+",0,0,"+ +(s*h>f*d)+","+(this._x1=t+m*u)+","+(this._y1=n+m*c)}else this._+="L"+(this._x1=t)+","+(this._y1=n);else;},arc:function(t,n,e,r,i,o){t=+t,n=+n,o=!!o;var a=(e=+e)*Math.cos(r),u=e*Math.sin(r),c=t+a,f=n+u,s=1^o,l=o?r-i:i-r;if(e<0)throw new Error("negative radius: "+e);null===this._x1?this._+="M"+c+","+f:(Math.abs(this._x1-c)>aa||Math.abs(this._y1-f)>aa)&&(this._+="L"+c+","+f),e&&(l<0&&(l=l%oa+oa),l>ua?this._+="A"+e+","+e+",0,1,"+s+","+(t-a)+","+(n-u)+"A"+e+","+e+",0,1,"+s+","+(this._x1=c)+","+(this._y1=f):l>aa&&(this._+="A"+e+","+e+",0,"+ +(l>=ia)+","+s+","+(this._x1=t+e*Math.cos(i))+","+(this._y1=n+e*Math.sin(i))))},rect:function(t,n,e,r){this._+="M"+(this._x0=this._x1=+t)+","+(this._y0=this._y1=+n)+"h"+ +e+"v"+ +r+"h"+-e+"Z"},toString:function(){return this._}};var sa=Array.prototype.slice;function la(t){return function(){return t}}function ha(t){return t.source}function da(t){return t.target}function pa(t){return t.radius}function ga(t){return t.startAngle}function ya(t){return t.endAngle}function va(){return 0}function _a(){return 10}function ba(t){var n=ha,e=da,r=pa,i=pa,o=ga,a=ya,u=va,c=null;function f(){var f,s=n.apply(this,arguments),l=e.apply(this,arguments),h=u.apply(this,arguments)/2,d=sa.call(arguments),p=+r.apply(this,(d[0]=s,d)),g=o.apply(this,d)-Ko,y=a.apply(this,d)-Ko,v=+i.apply(this,(d[0]=l,d)),_=o.apply(this,d)-Ko,b=a.apply(this,d)-Ko;if(c||(c=f=fa()),h>ta&&(Vo(y-g)>2*h+ta?y>g?(g+=h,y-=h):(g-=h,y+=h):g=y=(g+y)/2,Vo(b-_)>2*h+ta?b>_?(_+=h,b-=h):(_-=h,b+=h):_=b=(_+b)/2),c.moveTo(p*$o(g),p*Wo(g)),c.arc(0,0,p,g,y),g!==_||y!==b)if(t){var m=+t.apply(this,arguments),x=v-m,w=(_+b)/2;c.quadraticCurveTo(0,0,x*$o(_),x*Wo(_)),c.lineTo(v*$o(w),v*Wo(w)),c.lineTo(x*$o(b),x*Wo(b))}else c.quadraticCurveTo(0,0,v*$o(_),v*Wo(_)),c.arc(0,0,v,_,b);if(c.quadraticCurveTo(0,0,p*$o(g),p*Wo(g)),c.closePath(),f)return c=null,f+""||null}return t&&(f.headRadius=function(n){return arguments.length?(t="function"==typeof n?n:la(+n),f):t}),f.radius=function(t){return arguments.length?(r=i="function"==typeof t?t:la(+t),f):r},f.sourceRadius=function(t){return arguments.length?(r="function"==typeof t?t:la(+t),f):r},f.targetRadius=function(t){return arguments.length?(i="function"==typeof t?t:la(+t),f):i},f.startAngle=function(t){return arguments.length?(o="function"==typeof t?t:la(+t),f):o},f.endAngle=function(t){return arguments.length?(a="function"==typeof t?t:la(+t),f):a},f.padAngle=function(t){return arguments.length?(u="function"==typeof t?t:la(+t),f):u},f.source=function(t){return arguments.length?(n=t,f):n},f.target=function(t){return arguments.length?(e=t,f):e},f.context=function(t){return arguments.length?(c=null==t?null:t,f):c},f}var ma=Array.prototype.slice;function xa(t,n){return t-n}var wa=t=>()=>t;function Ma(t,n){for(var e,r=-1,i=n.length;++rr!=d>r&&e<(h-f)*(r-s)/(d-s)+f&&(i=-i)}return i}function Ta(t,n,e){var r,i,o,a;return function(t,n,e){return(n[0]-t[0])*(e[1]-t[1])==(e[0]-t[0])*(n[1]-t[1])}(t,n,e)&&(i=t[r=+(t[0]===n[0])],o=e[r],a=n[r],i<=o&&o<=a||a<=o&&o<=i)}function Sa(){}var Ea=[[],[[[1,1.5],[.5,1]]],[[[1.5,1],[1,1.5]]],[[[1.5,1],[.5,1]]],[[[1,.5],[1.5,1]]],[[[1,1.5],[.5,1]],[[1,.5],[1.5,1]]],[[[1,.5],[1,1.5]]],[[[1,.5],[.5,1]]],[[[.5,1],[1,.5]]],[[[1,1.5],[1,.5]]],[[[.5,1],[1,.5]],[[1.5,1],[1,1.5]]],[[[1.5,1],[1,.5]]],[[[.5,1],[1.5,1]]],[[[1,1.5],[1.5,1]]],[[[.5,1],[1,1.5]]],[]];function ka(){var t=1,n=1,e=I,r=u;function i(t){var n=e(t);if(Array.isArray(n))n=n.slice().sort(xa);else{var r=p(t),i=r[0],a=r[1];n=F(i,a,n),n=Z(Math.floor(i/n)*n,Math.floor(a/n)*n,n)}return n.map((function(n){return o(t,n)}))}function o(e,i){var o=[],u=[];return function(e,r,i){var o,u,c,f,s,l,h=new Array,d=new Array;o=u=-1,f=e[0]>=r,Ea[f<<1].forEach(p);for(;++o=r,Ea[c|f<<1].forEach(p);Ea[f<<0].forEach(p);for(;++u=r,s=e[u*t]>=r,Ea[f<<1|s<<2].forEach(p);++o=r,l=s,s=e[u*t+o+1]>=r,Ea[c|f<<1|s<<2|l<<3].forEach(p);Ea[f|s<<3].forEach(p)}o=-1,s=e[u*t]>=r,Ea[s<<2].forEach(p);for(;++o=r,Ea[s<<2|l<<3].forEach(p);function p(t){var n,e,r=[t[0][0]+o,t[0][1]+u],c=[t[1][0]+o,t[1][1]+u],f=a(r),s=a(c);(n=d[f])?(e=h[s])?(delete d[n.end],delete h[e.start],n===e?(n.ring.push(c),i(n.ring)):h[n.start]=d[e.end]={start:n.start,end:e.end,ring:n.ring.concat(e.ring)}):(delete d[n.end],n.ring.push(c),d[n.end=s]=n):(n=h[s])?(e=d[f])?(delete h[n.start],delete d[e.end],n===e?(n.ring.push(c),i(n.ring)):h[e.start]=d[n.end]={start:e.start,end:n.end,ring:e.ring.concat(n.ring)}):(delete h[n.start],n.ring.unshift(r),h[n.start=f]=n):h[f]=d[s]={start:f,end:s,ring:[r,c]}}Ea[s<<3].forEach(p)}(e,i,(function(t){r(t,e,i),function(t){for(var n=0,e=t.length,r=t[e-1][1]*t[0][0]-t[e-1][0]*t[0][1];++n0?o.push([t]):u.push(t)})),u.forEach((function(t){for(var n,e=0,r=o.length;e0&&a0&&u=0&&o>=0))throw new Error("invalid size");return t=r,n=o,i},i.thresholds=function(t){return arguments.length?(e="function"==typeof t?t:Array.isArray(t)?wa(ma.call(t)):wa(t),i):e},i.smooth=function(t){return arguments.length?(r=t?u:Sa,i):r===u},i}function Na(t,n,e){for(var r=t.width,i=t.height,o=1+(e<<1),a=0;a=e&&(u>=o&&(c-=t.data[u-o+a*r]),n.data[u-e+a*r]=c/Math.min(u+1,r-1+o-u,o))}function Ca(t,n,e){for(var r=t.width,i=t.height,o=1+(e<<1),a=0;a=e&&(u>=o&&(c-=t.data[a+(u-o)*r]),n.data[a+(u-e)*r]=c/Math.min(u+1,i-1+o-u,o))}function Pa(t){return t[0]}function za(t){return t[1]}function Da(){return 1}const qa=Math.pow(2,-52),Ra=new Uint32Array(512);class Fa{static from(t,n=Ha,e=Xa){const r=t.length,i=new Float64Array(2*r);for(let o=0;o>1;if(n>0&&"number"!=typeof t[0])throw new Error("Expected coords to contain numbers.");this.coords=t;const e=Math.max(2*n-5,0);this._triangles=new Uint32Array(3*e),this._halfedges=new Int32Array(3*e),this._hashSize=Math.ceil(Math.sqrt(n)),this._hullPrev=new Uint32Array(n),this._hullNext=new Uint32Array(n),this._hullTri=new Uint32Array(n),this._hullHash=new Int32Array(this._hashSize).fill(-1),this._ids=new Uint32Array(n),this._dists=new Float64Array(n),this.update()}update(){const{coords:t,_hullPrev:n,_hullNext:e,_hullTri:r,_hullHash:i}=this,o=t.length>>1;let a=1/0,u=1/0,c=-1/0,f=-1/0;for(let n=0;nc&&(c=e),r>f&&(f=r),this._ids[n]=n}const s=(a+c)/2,l=(u+f)/2;let h,d,p,g=1/0;for(let n=0;n0&&(d=n,g=e)}let _=t[2*d],b=t[2*d+1],m=1/0;for(let n=0;nr&&(n[e++]=i,r=this._dists[i])}return this.hull=n.subarray(0,e),this.triangles=new Uint32Array(0),void(this.halfedges=new Uint32Array(0))}if(Ua(y,v,_,b,x,w)){const t=d,n=_,e=b;d=p,_=x,b=w,p=t,x=n,w=e}const M=function(t,n,e,r,i,o){const a=e-t,u=r-n,c=i-t,f=o-n,s=a*a+u*u,l=c*c+f*f,h=.5/(a*f-u*c);return{x:t+(f*s-u*l)*h,y:n+(a*l-c*s)*h}}(y,v,_,b,x,w);this._cx=M.x,this._cy=M.y;for(let n=0;n0&&Math.abs(f-o)<=qa&&Math.abs(s-a)<=qa)continue;if(o=f,a=s,c===h||c===d||c===p)continue;let l=0;for(let t=0,n=this._hashKey(f,s);t0?3-e:1+e)/4}(t-this._cx,n-this._cy)*this._hashSize)%this._hashSize}_legalize(t){const{_triangles:n,_halfedges:e,coords:r}=this;let i=0,o=0;for(;;){const a=e[t],u=t-t%3;if(o=u+(t+2)%3,-1===a){if(0===i)break;t=Ra[--i];continue}const c=a-a%3,f=u+(t+1)%3,s=c+(a+2)%3,l=n[o],h=n[t],d=n[f],p=n[s];if(Ba(r[2*l],r[2*l+1],r[2*h],r[2*h+1],r[2*d],r[2*d+1],r[2*p],r[2*p+1])){n[t]=p,n[a]=l;const r=e[s];if(-1===r){let n=this._hullStart;do{if(this._hullTri[n]===s){this._hullTri[n]=t;break}n=this._hullPrev[n]}while(n!==this._hullStart)}this._link(t,r),this._link(a,e[o]),this._link(o,s);const u=c+(a+1)%3;i=33306690738754716e-32*Math.abs(a+u)?a-u:0}function Ua(t,n,e,r,i,o){return(Ia(i,o,t,n,e,r)||Ia(t,n,e,r,i,o)||Ia(e,r,i,o,t,n))<0}function Ba(t,n,e,r,i,o,a,u){const c=t-a,f=n-u,s=e-a,l=r-u,h=i-a,d=o-u,p=s*s+l*l,g=h*h+d*d;return c*(l*g-p*d)-f*(s*g-p*h)+(c*c+f*f)*(s*d-l*h)<0}function Ya(t,n,e,r,i,o){const a=e-t,u=r-n,c=i-t,f=o-n,s=a*a+u*u,l=c*c+f*f,h=.5/(a*f-u*c),d=(f*s-u*l)*h,p=(a*l-c*s)*h;return d*d+p*p}function La(t,n,e,r){if(r-e<=20)for(let i=e+1;i<=r;i++){const r=t[i],o=n[r];let a=i-1;for(;a>=e&&n[t[a]]>o;)t[a+1]=t[a--];t[a+1]=r}else{let i=e+1,o=r;ja(t,e+r>>1,i),n[t[e]]>n[t[r]]&&ja(t,e,r),n[t[i]]>n[t[r]]&&ja(t,i,r),n[t[e]]>n[t[i]]&&ja(t,e,i);const a=t[i],u=n[a];for(;;){do{i++}while(n[t[i]]u);if(o=o-e?(La(t,n,i,r),La(t,n,e,o-1)):(La(t,n,e,o-1),La(t,n,i,r))}}function ja(t,n,e){const r=t[n];t[n]=t[e],t[e]=r}function Ha(t){return t[0]}function Xa(t){return t[1]}const Ga=1e-6;class Va{constructor(){this._x0=this._y0=this._x1=this._y1=null,this._=""}moveTo(t,n){this._+=`M${this._x0=this._x1=+t},${this._y0=this._y1=+n}`}closePath(){null!==this._x1&&(this._x1=this._x0,this._y1=this._y0,this._+="Z")}lineTo(t,n){this._+=`L${this._x1=+t},${this._y1=+n}`}arc(t,n,e){const r=(t=+t)+(e=+e),i=n=+n;if(e<0)throw new Error("negative radius");null===this._x1?this._+=`M${r},${i}`:(Math.abs(this._x1-r)>Ga||Math.abs(this._y1-i)>Ga)&&(this._+="L"+r+","+i),e&&(this._+=`A${e},${e},0,1,1,${t-e},${n}A${e},${e},0,1,1,${this._x1=r},${this._y1=i}`)}rect(t,n,e,r){this._+=`M${this._x0=this._x1=+t},${this._y0=this._y1=+n}h${+e}v${+r}h${-e}Z`}value(){return this._||null}}class $a{constructor(){this._=[]}moveTo(t,n){this._.push([t,n])}closePath(){this._.push(this._[0].slice())}lineTo(t,n){this._.push([t,n])}value(){return this._.length?this._:null}}class Wa{constructor(t,[n,e,r,i]=[0,0,960,500]){if(!((r=+r)>=(n=+n)&&(i=+i)>=(e=+e)))throw new Error("invalid bounds");this.delaunay=t,this._circumcenters=new Float64Array(2*t.points.length),this.vectors=new Float64Array(2*t.points.length),this.xmax=r,this.xmin=n,this.ymax=i,this.ymin=e,this._init()}update(){return this.delaunay.update(),this._init(),this}_init(){const{delaunay:{points:t,hull:n,triangles:e},vectors:r}=this,i=this.circumcenters=this._circumcenters.subarray(0,e.length/3*2);for(let n,r,o=0,a=0,u=e.length;o1;)i-=2;for(let t=2;t4)for(let t=0;t0){if(n>=this.ymax)return null;(i=(this.ymax-n)/r)0){if(t>=this.xmax)return null;(i=(this.xmax-t)/e)this.xmax?2:0)|(nthis.ymax?8:0)}}const Za=2*Math.PI,Ka=Math.pow;function Qa(t){return t[0]}function Ja(t){return t[1]}function tu(t,n,e){return[t+Math.sin(t+n)*e,n+Math.cos(t-n)*e]}class nu{static from(t,n=Qa,e=Ja,r){return new nu("length"in t?function(t,n,e,r){const i=t.length,o=new Float64Array(2*i);for(let a=0;a2&&function(t){const{triangles:n,coords:e}=t;for(let t=0;t1e-10)return!1}return!0}(t)){this.collinear=Int32Array.from({length:n.length/2},((t,n)=>n)).sort(((t,e)=>n[2*t]-n[2*e]||n[2*t+1]-n[2*e+1]));const t=this.collinear[0],e=this.collinear[this.collinear.length-1],r=[n[2*t],n[2*t+1],n[2*e],n[2*e+1]],i=1e-8*Math.hypot(r[3]-r[1],r[2]-r[0]);for(let t=0,e=n.length/2;t0&&(this.triangles=new Int32Array(3).fill(-1),this.halfedges=new Int32Array(3).fill(-1),this.triangles[0]=r[0],this.triangles[1]=r[1],this.triangles[2]=r[1],o[r[0]]=1,2===r.length&&(o[r[1]]=0))}voronoi(t){return new Wa(this,t)}*neighbors(t){const{inedges:n,hull:e,_hullIndex:r,halfedges:i,triangles:o,collinear:a}=this;if(a){const n=a.indexOf(t);return n>0&&(yield a[n-1]),void(n=0&&i!==e&&i!==r;)e=i;return i}_step(t,n,e){const{inedges:r,hull:i,_hullIndex:o,halfedges:a,triangles:u,points:c}=this;if(-1===r[t]||!c.length)return(t+1)%(c.length>>1);let f=t,s=Ka(n-c[2*t],2)+Ka(e-c[2*t+1],2);const l=r[t];let h=l;do{let r=u[h];const l=Ka(n-c[2*r],2)+Ka(e-c[2*r+1],2);if(l9999?"+"+au(t,6):au(t,4)}(t.getUTCFullYear())+"-"+au(t.getUTCMonth()+1,2)+"-"+au(t.getUTCDate(),2)+(i?"T"+au(n,2)+":"+au(e,2)+":"+au(r,2)+"."+au(i,3)+"Z":r?"T"+au(n,2)+":"+au(e,2)+":"+au(r,2)+"Z":e||n?"T"+au(n,2)+":"+au(e,2)+"Z":"")}function cu(t){var n=new RegExp('["'+t+"\n\r]"),e=t.charCodeAt(0);function r(t,n){var r,i=[],o=t.length,a=0,u=0,c=o<=0,f=!1;function s(){if(c)return ru;if(f)return f=!1,eu;var n,r,i=a;if(34===t.charCodeAt(i)){for(;a++=o?c=!0:10===(r=t.charCodeAt(a++))?f=!0:13===r&&(f=!0,10===t.charCodeAt(a)&&++a),t.slice(i+1,n-1).replace(/""/g,'"')}for(;aNu(n,e).then((n=>(new DOMParser).parseFromString(n,t)))}var Ru=qu("application/xml"),Fu=qu("text/html"),Ou=qu("image/svg+xml");function Iu(t,n,e,r){if(isNaN(n)||isNaN(e))return t;var i,o,a,u,c,f,s,l,h,d=t._root,p={data:r},g=t._x0,y=t._y0,v=t._x1,_=t._y1;if(!d)return t._root=p,t;for(;d.length;)if((f=n>=(o=(g+v)/2))?g=o:v=o,(s=e>=(a=(y+_)/2))?y=a:_=a,i=d,!(d=d[l=s<<1|f]))return i[l]=p,t;if(u=+t._x.call(null,d.data),c=+t._y.call(null,d.data),n===u&&e===c)return p.next=d,i?i[l]=p:t._root=p,t;do{i=i?i[l]=new Array(4):t._root=new Array(4),(f=n>=(o=(g+v)/2))?g=o:v=o,(s=e>=(a=(y+_)/2))?y=a:_=a}while((l=s<<1|f)==(h=(c>=a)<<1|u>=o));return i[h]=d,i[l]=p,t}function Uu(t,n,e,r,i){this.node=t,this.x0=n,this.y0=e,this.x1=r,this.y1=i}function Bu(t){return t[0]}function Yu(t){return t[1]}function Lu(t,n,e){var r=new ju(null==n?Bu:n,null==e?Yu:e,NaN,NaN,NaN,NaN);return null==t?r:r.addAll(t)}function ju(t,n,e,r,i,o){this._x=t,this._y=n,this._x0=e,this._y0=r,this._x1=i,this._y1=o,this._root=void 0}function Hu(t){for(var n={data:t.data},e=n;t=t.next;)e=e.next={data:t.data};return n}var Xu=Lu.prototype=ju.prototype;function Gu(t){return function(){return t}}function Vu(t){return 1e-6*(t()-.5)}function $u(t){return t.x+t.vx}function Wu(t){return t.y+t.vy}function Zu(t){return t.index}function Ku(t,n){var e=t.get(n);if(!e)throw new Error("node not found: "+n);return e}Xu.copy=function(){var t,n,e=new ju(this._x,this._y,this._x0,this._y0,this._x1,this._y1),r=this._root;if(!r)return e;if(!r.length)return e._root=Hu(r),e;for(t=[{source:r,target:e._root=new Array(4)}];r=t.pop();)for(var i=0;i<4;++i)(n=r.source[i])&&(n.length?t.push({source:n,target:r.target[i]=new Array(4)}):r.target[i]=Hu(n));return e},Xu.add=function(t){const n=+this._x.call(null,t),e=+this._y.call(null,t);return Iu(this.cover(n,e),n,e,t)},Xu.addAll=function(t){var n,e,r,i,o=t.length,a=new Array(o),u=new Array(o),c=1/0,f=1/0,s=-1/0,l=-1/0;for(e=0;es&&(s=r),il&&(l=i));if(c>s||f>l)return this;for(this.cover(c,f).cover(s,l),e=0;et||t>=i||r>n||n>=o;)switch(u=(nh||(o=c.y0)>d||(a=c.x1)=v)<<1|t>=y)&&(c=p[p.length-1],p[p.length-1]=p[p.length-1-f],p[p.length-1-f]=c)}else{var _=t-+this._x.call(null,g.data),b=n-+this._y.call(null,g.data),m=_*_+b*b;if(m=(u=(p+y)/2))?p=u:y=u,(s=a>=(c=(g+v)/2))?g=c:v=c,n=d,!(d=d[l=s<<1|f]))return this;if(!d.length)break;(n[l+1&3]||n[l+2&3]||n[l+3&3])&&(e=n,h=l)}for(;d.data!==t;)if(r=d,!(d=d.next))return this;return(i=d.next)&&delete d.next,r?(i?r.next=i:delete r.next,this):n?(i?n[l]=i:delete n[l],(d=n[0]||n[1]||n[2]||n[3])&&d===(n[3]||n[2]||n[1]||n[0])&&!d.length&&(e?e[h]=d:this._root=d),this):(this._root=i,this)},Xu.removeAll=function(t){for(var n=0,e=t.length;n1?r[0]+r.slice(2):r,+t.slice(e+1)]}function rc(t){return(t=ec(Math.abs(t)))?t[1]:NaN}var ic,oc=/^(?:(.)?([<>=^]))?([+\-( ])?([$#])?(0)?(\d+)?(,)?(\.\d+)?(~)?([a-z%])?$/i;function ac(t){if(!(n=oc.exec(t)))throw new Error("invalid format: "+t);var n;return new uc({fill:n[1],align:n[2],sign:n[3],symbol:n[4],zero:n[5],width:n[6],comma:n[7],precision:n[8]&&n[8].slice(1),trim:n[9],type:n[10]})}function uc(t){this.fill=void 0===t.fill?" ":t.fill+"",this.align=void 0===t.align?">":t.align+"",this.sign=void 0===t.sign?"-":t.sign+"",this.symbol=void 0===t.symbol?"":t.symbol+"",this.zero=!!t.zero,this.width=void 0===t.width?void 0:+t.width,this.comma=!!t.comma,this.precision=void 0===t.precision?void 0:+t.precision,this.trim=!!t.trim,this.type=void 0===t.type?"":t.type+""}function cc(t,n){var e=ec(t,n);if(!e)return t+"";var r=e[0],i=e[1];return i<0?"0."+new Array(-i).join("0")+r:r.length>i+1?r.slice(0,i+1)+"."+r.slice(i+1):r+new Array(i-r.length+2).join("0")}ac.prototype=uc.prototype,uc.prototype.toString=function(){return this.fill+this.align+this.sign+this.symbol+(this.zero?"0":"")+(void 0===this.width?"":Math.max(1,0|this.width))+(this.comma?",":"")+(void 0===this.precision?"":"."+Math.max(0,0|this.precision))+(this.trim?"~":"")+this.type};var fc={"%":(t,n)=>(100*t).toFixed(n),b:t=>Math.round(t).toString(2),c:t=>t+"",d:function(t){return Math.abs(t=Math.round(t))>=1e21?t.toLocaleString("en").replace(/,/g,""):t.toString(10)},e:(t,n)=>t.toExponential(n),f:(t,n)=>t.toFixed(n),g:(t,n)=>t.toPrecision(n),o:t=>Math.round(t).toString(8),p:(t,n)=>cc(100*t,n),r:cc,s:function(t,n){var e=ec(t,n);if(!e)return t+"";var r=e[0],i=e[1],o=i-(ic=3*Math.max(-8,Math.min(8,Math.floor(i/3))))+1,a=r.length;return o===a?r:o>a?r+new Array(o-a+1).join("0"):o>0?r.slice(0,o)+"."+r.slice(o):"0."+new Array(1-o).join("0")+ec(t,Math.max(0,n+o-1))[0]},X:t=>Math.round(t).toString(16).toUpperCase(),x:t=>Math.round(t).toString(16)};function sc(t){return t}var lc,hc=Array.prototype.map,dc=["y","z","a","f","p","n","µ","m","","k","M","G","T","P","E","Z","Y"];function pc(t){var n,e,r=void 0===t.grouping||void 0===t.thousands?sc:(n=hc.call(t.grouping,Number),e=t.thousands+"",function(t,r){for(var i=t.length,o=[],a=0,u=n[0],c=0;i>0&&u>0&&(c+u+1>r&&(u=Math.max(1,r-c)),o.push(t.substring(i-=u,i+u)),!((c+=u+1)>r));)u=n[a=(a+1)%n.length];return o.reverse().join(e)}),i=void 0===t.currency?"":t.currency[0]+"",o=void 0===t.currency?"":t.currency[1]+"",a=void 0===t.decimal?".":t.decimal+"",u=void 0===t.numerals?sc:function(t){return function(n){return n.replace(/[0-9]/g,(function(n){return t[+n]}))}}(hc.call(t.numerals,String)),c=void 0===t.percent?"%":t.percent+"",f=void 0===t.minus?"−":t.minus+"",s=void 0===t.nan?"NaN":t.nan+"";function l(t){var n=(t=ac(t)).fill,e=t.align,l=t.sign,h=t.symbol,d=t.zero,p=t.width,g=t.comma,y=t.precision,v=t.trim,_=t.type;"n"===_?(g=!0,_="g"):fc[_]||(void 0===y&&(y=12),v=!0,_="g"),(d||"0"===n&&"="===e)&&(d=!0,n="0",e="=");var b="$"===h?i:"#"===h&&/[boxX]/.test(_)?"0"+_.toLowerCase():"",m="$"===h?o:/[%p]/.test(_)?c:"",x=fc[_],w=/[defgprs%]/.test(_);function M(t){var i,o,c,h=b,M=m;if("c"===_)M=x(t)+M,t="";else{var A=(t=+t)<0||1/t<0;if(t=isNaN(t)?s:x(Math.abs(t),y),v&&(t=function(t){t:for(var n,e=t.length,r=1,i=-1;r0&&(i=0)}return i>0?t.slice(0,i)+t.slice(n+1):t}(t)),A&&0==+t&&"+"!==l&&(A=!1),h=(A?"("===l?l:f:"-"===l||"("===l?"":l)+h,M=("s"===_?dc[8+ic/3]:"")+M+(A&&"("===l?")":""),w)for(i=-1,o=t.length;++i(c=t.charCodeAt(i))||c>57){M=(46===c?a+t.slice(i+1):t.slice(i))+M,t=t.slice(0,i);break}}g&&!d&&(t=r(t,1/0));var T=h.length+t.length+M.length,S=T>1)+h+t+M+S.slice(T);break;default:t=S+h+t+M}return u(t)}return y=void 0===y?6:/[gprs]/.test(_)?Math.max(1,Math.min(21,y)):Math.max(0,Math.min(20,y)),M.toString=function(){return t+""},M}return{format:l,formatPrefix:function(t,n){var e=l(((t=ac(t)).type="f",t)),r=3*Math.max(-8,Math.min(8,Math.floor(rc(n)/3))),i=Math.pow(10,-r),o=dc[8+r/3];return function(t){return e(i*t)+o}}}}function gc(n){return lc=pc(n),t.format=lc.format,t.formatPrefix=lc.formatPrefix,lc}function yc(t){return Math.max(0,-rc(Math.abs(t)))}function vc(t,n){return Math.max(0,3*Math.max(-8,Math.min(8,Math.floor(rc(n)/3)))-rc(Math.abs(t)))}function _c(t,n){return t=Math.abs(t),n=Math.abs(n)-t,Math.max(0,rc(n)-rc(t))+1}t.format=void 0,t.formatPrefix=void 0,gc({thousands:",",grouping:[3],currency:["$",""]});var bc=1e-6,mc=1e-12,xc=Math.PI,wc=xc/2,Mc=xc/4,Ac=2*xc,Tc=180/xc,Sc=xc/180,Ec=Math.abs,kc=Math.atan,Nc=Math.atan2,Cc=Math.cos,Pc=Math.ceil,zc=Math.exp,Dc=Math.hypot,qc=Math.log,Rc=Math.pow,Fc=Math.sin,Oc=Math.sign||function(t){return t>0?1:t<0?-1:0},Ic=Math.sqrt,Uc=Math.tan;function Bc(t){return t>1?0:t<-1?xc:Math.acos(t)}function Yc(t){return t>1?wc:t<-1?-wc:Math.asin(t)}function Lc(t){return(t=Fc(t/2))*t}function jc(){}function Hc(t,n){t&&Gc.hasOwnProperty(t.type)&&Gc[t.type](t,n)}var Xc={Feature:function(t,n){Hc(t.geometry,n)},FeatureCollection:function(t,n){for(var e=t.features,r=-1,i=e.length;++r=0?1:-1,i=r*e,o=Cc(n=(n*=Sc)/2+Mc),a=Fc(n),u=tf*a,c=Jc*o+u*Cc(i),f=u*r*Fc(i);df.add(Nc(f,c)),Qc=t,Jc=o,tf=a}function mf(t){return[Nc(t[1],t[0]),Yc(t[2])]}function xf(t){var n=t[0],e=t[1],r=Cc(e);return[r*Cc(n),r*Fc(n),Fc(e)]}function wf(t,n){return t[0]*n[0]+t[1]*n[1]+t[2]*n[2]}function Mf(t,n){return[t[1]*n[2]-t[2]*n[1],t[2]*n[0]-t[0]*n[2],t[0]*n[1]-t[1]*n[0]]}function Af(t,n){t[0]+=n[0],t[1]+=n[1],t[2]+=n[2]}function Tf(t,n){return[t[0]*n,t[1]*n,t[2]*n]}function Sf(t){var n=Ic(t[0]*t[0]+t[1]*t[1]+t[2]*t[2]);t[0]/=n,t[1]/=n,t[2]/=n}var Ef,kf,Nf,Cf,Pf,zf,Df,qf,Rf,Ff,Of,If,Uf,Bf,Yf,Lf,jf={point:Hf,lineStart:Gf,lineEnd:Vf,polygonStart:function(){jf.point=$f,jf.lineStart=Wf,jf.lineEnd=Zf,sf=new g,gf.polygonStart()},polygonEnd:function(){gf.polygonEnd(),jf.point=Hf,jf.lineStart=Gf,jf.lineEnd=Vf,df<0?(nf=-(rf=180),ef=-(of=90)):sf>bc?of=90:sf<-1e-6&&(ef=-90),hf[0]=nf,hf[1]=rf},sphere:function(){nf=-(rf=180),ef=-(of=90)}};function Hf(t,n){lf.push(hf=[nf=t,rf=t]),nof&&(of=n)}function Xf(t,n){var e=xf([t*Sc,n*Sc]);if(ff){var r=Mf(ff,e),i=Mf([r[1],-r[0],0],r);Sf(i),i=mf(i);var o,a=t-af,u=a>0?1:-1,c=i[0]*Tc*u,f=Ec(a)>180;f^(u*afof&&(of=o):f^(u*af<(c=(c+360)%360-180)&&cof&&(of=n)),f?tKf(nf,rf)&&(rf=t):Kf(t,rf)>Kf(nf,rf)&&(nf=t):rf>=nf?(trf&&(rf=t)):t>af?Kf(nf,t)>Kf(nf,rf)&&(rf=t):Kf(t,rf)>Kf(nf,rf)&&(nf=t)}else lf.push(hf=[nf=t,rf=t]);nof&&(of=n),ff=e,af=t}function Gf(){jf.point=Xf}function Vf(){hf[0]=nf,hf[1]=rf,jf.point=Hf,ff=null}function $f(t,n){if(ff){var e=t-af;sf.add(Ec(e)>180?e+(e>0?360:-360):e)}else uf=t,cf=n;gf.point(t,n),Xf(t,n)}function Wf(){gf.lineStart()}function Zf(){$f(uf,cf),gf.lineEnd(),Ec(sf)>bc&&(nf=-(rf=180)),hf[0]=nf,hf[1]=rf,ff=null}function Kf(t,n){return(n-=t)<0?n+360:n}function Qf(t,n){return t[0]-n[0]}function Jf(t,n){return t[0]<=t[1]?t[0]<=n&&n<=t[1]:nxc?t+Math.round(-t/Ac)*Ac:t,n]}function ps(t,n,e){return(t%=Ac)?n||e?hs(ys(t),vs(n,e)):ys(t):n||e?vs(n,e):ds}function gs(t){return function(n,e){return[(n+=t)>xc?n-Ac:n<-xc?n+Ac:n,e]}}function ys(t){var n=gs(t);return n.invert=gs(-t),n}function vs(t,n){var e=Cc(t),r=Fc(t),i=Cc(n),o=Fc(n);function a(t,n){var a=Cc(n),u=Cc(t)*a,c=Fc(t)*a,f=Fc(n),s=f*e+u*r;return[Nc(c*i-s*o,u*e-f*r),Yc(s*i+c*o)]}return a.invert=function(t,n){var a=Cc(n),u=Cc(t)*a,c=Fc(t)*a,f=Fc(n),s=f*i-c*o;return[Nc(c*i+f*o,u*e+s*r),Yc(s*e-u*r)]},a}function _s(t){function n(n){return(n=t(n[0]*Sc,n[1]*Sc))[0]*=Tc,n[1]*=Tc,n}return t=ps(t[0]*Sc,t[1]*Sc,t.length>2?t[2]*Sc:0),n.invert=function(n){return(n=t.invert(n[0]*Sc,n[1]*Sc))[0]*=Tc,n[1]*=Tc,n},n}function bs(t,n,e,r,i,o){if(e){var a=Cc(n),u=Fc(n),c=r*e;null==i?(i=n+r*Ac,o=n-c/2):(i=ms(a,i),o=ms(a,o),(r>0?io)&&(i+=r*Ac));for(var f,s=i;r>0?s>o:s1&&n.push(n.pop().concat(n.shift()))},result:function(){var e=n;return n=[],t=null,e}}}function ws(t,n){return Ec(t[0]-n[0])=0;--o)i.point((s=f[o])[0],s[1]);else r(h.x,h.p.x,-1,i);h=h.p}f=(h=h.o).z,d=!d}while(!h.v);i.lineEnd()}}}function Ts(t){if(n=t.length){for(var n,e,r=0,i=t[0];++r=0?1:-1,E=S*T,k=E>xc,N=v*M;if(c.add(Nc(N*S*Fc(E),_*A+N*Cc(E))),a+=k?T+S*Ac:T,k^p>=e^x>=e){var C=Mf(xf(d),xf(m));Sf(C);var P=Mf(o,C);Sf(P);var z=(k^T>=0?-1:1)*Yc(P[2]);(r>z||r===z&&(C[0]||C[1]))&&(u+=k^T>=0?1:-1)}}return(a<-1e-6||a0){for(l||(i.polygonStart(),l=!0),i.lineStart(),t=0;t1&&2&c&&h.push(h.pop().concat(h.shift())),a.push(h.filter(Ns))}return h}}function Ns(t){return t.length>1}function Cs(t,n){return((t=t.x)[0]<0?t[1]-wc-bc:wc-t[1])-((n=n.x)[0]<0?n[1]-wc-bc:wc-n[1])}ds.invert=ds;var Ps=ks((function(){return!0}),(function(t){var n,e=NaN,r=NaN,i=NaN;return{lineStart:function(){t.lineStart(),n=1},point:function(o,a){var u=o>0?xc:-xc,c=Ec(o-e);Ec(c-xc)0?wc:-wc),t.point(i,r),t.lineEnd(),t.lineStart(),t.point(u,r),t.point(o,r),n=0):i!==u&&c>=xc&&(Ec(e-i)bc?kc((Fc(n)*(o=Cc(r))*Fc(e)-Fc(r)*(i=Cc(n))*Fc(t))/(i*o*a)):(n+r)/2}(e,r,o,a),t.point(i,r),t.lineEnd(),t.lineStart(),t.point(u,r),n=0),t.point(e=o,r=a),i=u},lineEnd:function(){t.lineEnd(),e=r=NaN},clean:function(){return 2-n}}}),(function(t,n,e,r){var i;if(null==t)i=e*wc,r.point(-xc,i),r.point(0,i),r.point(xc,i),r.point(xc,0),r.point(xc,-i),r.point(0,-i),r.point(-xc,-i),r.point(-xc,0),r.point(-xc,i);else if(Ec(t[0]-n[0])>bc){var o=t[0]0,i=Ec(n)>bc;function o(t,e){return Cc(t)*Cc(e)>n}function a(t,e,r){var i=[1,0,0],o=Mf(xf(t),xf(e)),a=wf(o,o),u=o[0],c=a-u*u;if(!c)return!r&&t;var f=n*a/c,s=-n*u/c,l=Mf(i,o),h=Tf(i,f);Af(h,Tf(o,s));var d=l,p=wf(h,d),g=wf(d,d),y=p*p-g*(wf(h,h)-1);if(!(y<0)){var v=Ic(y),_=Tf(d,(-p-v)/g);if(Af(_,h),_=mf(_),!r)return _;var b,m=t[0],x=e[0],w=t[1],M=e[1];x0^_[1]<(Ec(_[0]-m)xc^(m<=_[0]&&_[0]<=x)){var S=Tf(d,(-p+v)/g);return Af(S,h),[_,mf(S)]}}}function u(n,e){var i=r?t:xc-t,o=0;return n<-i?o|=1:n>i&&(o|=2),e<-i?o|=4:e>i&&(o|=8),o}return ks(o,(function(t){var n,e,c,f,s;return{lineStart:function(){f=c=!1,s=1},point:function(l,h){var d,p=[l,h],g=o(l,h),y=r?g?0:u(l,h):g?u(l+(l<0?xc:-xc),h):0;if(!n&&(f=c=g)&&t.lineStart(),g!==c&&(!(d=a(n,p))||ws(n,d)||ws(p,d))&&(p[2]=1),g!==c)s=0,g?(t.lineStart(),d=a(p,n),t.point(d[0],d[1])):(d=a(n,p),t.point(d[0],d[1],2),t.lineEnd()),n=d;else if(i&&n&&r^g){var v;y&e||!(v=a(p,n,!0))||(s=0,r?(t.lineStart(),t.point(v[0][0],v[0][1]),t.point(v[1][0],v[1][1]),t.lineEnd()):(t.point(v[1][0],v[1][1]),t.lineEnd(),t.lineStart(),t.point(v[0][0],v[0][1],3)))}!g||n&&ws(n,p)||t.point(p[0],p[1]),n=p,c=g,e=y},lineEnd:function(){c&&t.lineEnd(),n=null},clean:function(){return s|(f&&c)<<1}}}),(function(n,r,i,o){bs(o,t,e,i,n,r)}),r?[0,-t]:[-xc,t-xc])}var Ds,qs,Rs,Fs,Os=1e9,Is=-Os;function Us(t,n,e,r){function i(i,o){return t<=i&&i<=e&&n<=o&&o<=r}function o(i,o,u,f){var s=0,l=0;if(null==i||(s=a(i,u))!==(l=a(o,u))||c(i,o)<0^u>0)do{f.point(0===s||3===s?t:e,s>1?r:n)}while((s=(s+u+4)%4)!==l);else f.point(o[0],o[1])}function a(r,i){return Ec(r[0]-t)0?0:3:Ec(r[0]-e)0?2:1:Ec(r[1]-n)0?1:0:i>0?3:2}function u(t,n){return c(t.x,n.x)}function c(t,n){var e=a(t,1),r=a(n,1);return e!==r?e-r:0===e?n[1]-t[1]:1===e?t[0]-n[0]:2===e?t[1]-n[1]:n[0]-t[0]}return function(a){var c,f,s,l,h,d,p,g,y,v,_,b=a,m=xs(),x={point:w,lineStart:function(){x.point=M,f&&f.push(s=[]);v=!0,y=!1,p=g=NaN},lineEnd:function(){c&&(M(l,h),d&&y&&m.rejoin(),c.push(m.result()));x.point=w,y&&b.lineEnd()},polygonStart:function(){b=m,c=[],f=[],_=!0},polygonEnd:function(){var n=function(){for(var n=0,e=0,i=f.length;er&&(h-o)*(r-a)>(d-a)*(t-o)&&++n:d<=r&&(h-o)*(r-a)<(d-a)*(t-o)&&--n;return n}(),e=_&&n,i=(c=V(c)).length;(e||i)&&(a.polygonStart(),e&&(a.lineStart(),o(null,null,1,a),a.lineEnd()),i&&As(c,u,n,o,a),a.polygonEnd());b=a,c=f=s=null}};function w(t,n){i(t,n)&&b.point(t,n)}function M(o,a){var u=i(o,a);if(f&&s.push([o,a]),v)l=o,h=a,d=u,v=!1,u&&(b.lineStart(),b.point(o,a));else if(u&&y)b.point(o,a);else{var c=[p=Math.max(Is,Math.min(Os,p)),g=Math.max(Is,Math.min(Os,g))],m=[o=Math.max(Is,Math.min(Os,o)),a=Math.max(Is,Math.min(Os,a))];!function(t,n,e,r,i,o){var a,u=t[0],c=t[1],f=0,s=1,l=n[0]-u,h=n[1]-c;if(a=e-u,l||!(a>0)){if(a/=l,l<0){if(a0){if(a>s)return;a>f&&(f=a)}if(a=i-u,l||!(a<0)){if(a/=l,l<0){if(a>s)return;a>f&&(f=a)}else if(l>0){if(a0)){if(a/=h,h<0){if(a0){if(a>s)return;a>f&&(f=a)}if(a=o-c,h||!(a<0)){if(a/=h,h<0){if(a>s)return;a>f&&(f=a)}else if(h>0){if(a0&&(t[0]=u+f*l,t[1]=c+f*h),s<1&&(n[0]=u+s*l,n[1]=c+s*h),!0}}}}}(c,m,t,n,e,r)?u&&(b.lineStart(),b.point(o,a),_=!1):(y||(b.lineStart(),b.point(c[0],c[1])),b.point(m[0],m[1]),u||b.lineEnd(),_=!1)}p=o,g=a,y=u}return x}}var Bs={sphere:jc,point:jc,lineStart:function(){Bs.point=Ls,Bs.lineEnd=Ys},lineEnd:jc,polygonStart:jc,polygonEnd:jc};function Ys(){Bs.point=Bs.lineEnd=jc}function Ls(t,n){qs=t*=Sc,Rs=Fc(n*=Sc),Fs=Cc(n),Bs.point=js}function js(t,n){t*=Sc;var e=Fc(n*=Sc),r=Cc(n),i=Ec(t-qs),o=Cc(i),a=r*Fc(i),u=Fs*e-Rs*r*o,c=Rs*e+Fs*r*o;Ds.add(Nc(Ic(a*a+u*u),c)),qs=t,Rs=e,Fs=r}function Hs(t){return Ds=new g,Wc(t,Bs),+Ds}var Xs=[null,null],Gs={type:"LineString",coordinates:Xs};function Vs(t,n){return Xs[0]=t,Xs[1]=n,Hs(Gs)}var $s={Feature:function(t,n){return Zs(t.geometry,n)},FeatureCollection:function(t,n){for(var e=t.features,r=-1,i=e.length;++r0&&(i=Vs(t[o],t[o-1]))>0&&e<=i&&r<=i&&(e+r-i)*(1-Math.pow((e-r)/i,2))bc})).map(c)).concat(Z(Pc(o/d)*d,i,d).filter((function(t){return Ec(t%g)>bc})).map(f))}return v.lines=function(){return _().map((function(t){return{type:"LineString",coordinates:t}}))},v.outline=function(){return{type:"Polygon",coordinates:[s(r).concat(l(a).slice(1),s(e).reverse().slice(1),l(u).reverse().slice(1))]}},v.extent=function(t){return arguments.length?v.extentMajor(t).extentMinor(t):v.extentMinor()},v.extentMajor=function(t){return arguments.length?(r=+t[0][0],e=+t[1][0],u=+t[0][1],a=+t[1][1],r>e&&(t=r,r=e,e=t),u>a&&(t=u,u=a,a=t),v.precision(y)):[[r,u],[e,a]]},v.extentMinor=function(e){return arguments.length?(n=+e[0][0],t=+e[1][0],o=+e[0][1],i=+e[1][1],n>t&&(e=n,n=t,t=e),o>i&&(e=o,o=i,i=e),v.precision(y)):[[n,o],[t,i]]},v.step=function(t){return arguments.length?v.stepMajor(t).stepMinor(t):v.stepMinor()},v.stepMajor=function(t){return arguments.length?(p=+t[0],g=+t[1],v):[p,g]},v.stepMinor=function(t){return arguments.length?(h=+t[0],d=+t[1],v):[h,d]},v.precision=function(h){return arguments.length?(y=+h,c=el(o,i,90),f=rl(n,t,y),s=el(u,a,90),l=rl(r,e,y),v):y},v.extentMajor([[-180,-89.999999],[180,89.999999]]).extentMinor([[-180,-80.000001],[180,80.000001]])}var ol,al,ul,cl,fl=t=>t,sl=new g,ll=new g,hl={point:jc,lineStart:jc,lineEnd:jc,polygonStart:function(){hl.lineStart=dl,hl.lineEnd=yl},polygonEnd:function(){hl.lineStart=hl.lineEnd=hl.point=jc,sl.add(Ec(ll)),ll=new g},result:function(){var t=sl/2;return sl=new g,t}};function dl(){hl.point=pl}function pl(t,n){hl.point=gl,ol=ul=t,al=cl=n}function gl(t,n){ll.add(cl*t-ul*n),ul=t,cl=n}function yl(){gl(ol,al)}var vl=1/0,_l=vl,bl=-vl,ml=bl,xl={point:function(t,n){tbl&&(bl=t);n<_l&&(_l=n);n>ml&&(ml=n)},lineStart:jc,lineEnd:jc,polygonStart:jc,polygonEnd:jc,result:function(){var t=[[vl,_l],[bl,ml]];return bl=ml=-(_l=vl=1/0),t}};var wl,Ml,Al,Tl,Sl=0,El=0,kl=0,Nl=0,Cl=0,Pl=0,zl=0,Dl=0,ql=0,Rl={point:Fl,lineStart:Ol,lineEnd:Bl,polygonStart:function(){Rl.lineStart=Yl,Rl.lineEnd=Ll},polygonEnd:function(){Rl.point=Fl,Rl.lineStart=Ol,Rl.lineEnd=Bl},result:function(){var t=ql?[zl/ql,Dl/ql]:Pl?[Nl/Pl,Cl/Pl]:kl?[Sl/kl,El/kl]:[NaN,NaN];return Sl=El=kl=Nl=Cl=Pl=zl=Dl=ql=0,t}};function Fl(t,n){Sl+=t,El+=n,++kl}function Ol(){Rl.point=Il}function Il(t,n){Rl.point=Ul,Fl(Al=t,Tl=n)}function Ul(t,n){var e=t-Al,r=n-Tl,i=Ic(e*e+r*r);Nl+=i*(Al+t)/2,Cl+=i*(Tl+n)/2,Pl+=i,Fl(Al=t,Tl=n)}function Bl(){Rl.point=Fl}function Yl(){Rl.point=jl}function Ll(){Hl(wl,Ml)}function jl(t,n){Rl.point=Hl,Fl(wl=Al=t,Ml=Tl=n)}function Hl(t,n){var e=t-Al,r=n-Tl,i=Ic(e*e+r*r);Nl+=i*(Al+t)/2,Cl+=i*(Tl+n)/2,Pl+=i,zl+=(i=Tl*t-Al*n)*(Al+t),Dl+=i*(Tl+n),ql+=3*i,Fl(Al=t,Tl=n)}function Xl(t){this._context=t}Xl.prototype={_radius:4.5,pointRadius:function(t){return this._radius=t,this},polygonStart:function(){this._line=0},polygonEnd:function(){this._line=NaN},lineStart:function(){this._point=0},lineEnd:function(){0===this._line&&this._context.closePath(),this._point=NaN},point:function(t,n){switch(this._point){case 0:this._context.moveTo(t,n),this._point=1;break;case 1:this._context.lineTo(t,n);break;default:this._context.moveTo(t+this._radius,n),this._context.arc(t,n,this._radius,0,Ac)}},result:jc};var Gl,Vl,$l,Wl,Zl,Kl=new g,Ql={point:jc,lineStart:function(){Ql.point=Jl},lineEnd:function(){Gl&&th(Vl,$l),Ql.point=jc},polygonStart:function(){Gl=!0},polygonEnd:function(){Gl=null},result:function(){var t=+Kl;return Kl=new g,t}};function Jl(t,n){Ql.point=th,Vl=Wl=t,$l=Zl=n}function th(t,n){Wl-=t,Zl-=n,Kl.add(Ic(Wl*Wl+Zl*Zl)),Wl=t,Zl=n}function nh(){this._string=[]}function eh(t){return"m0,"+t+"a"+t+","+t+" 0 1,1 0,"+-2*t+"a"+t+","+t+" 0 1,1 0,"+2*t+"z"}function rh(t){return function(n){var e=new ih;for(var r in t)e[r]=t[r];return e.stream=n,e}}function ih(){}function oh(t,n,e){var r=t.clipExtent&&t.clipExtent();return t.scale(150).translate([0,0]),null!=r&&t.clipExtent(null),Wc(e,t.stream(xl)),n(xl.result()),null!=r&&t.clipExtent(r),t}function ah(t,n,e){return oh(t,(function(e){var r=n[1][0]-n[0][0],i=n[1][1]-n[0][1],o=Math.min(r/(e[1][0]-e[0][0]),i/(e[1][1]-e[0][1])),a=+n[0][0]+(r-o*(e[1][0]+e[0][0]))/2,u=+n[0][1]+(i-o*(e[1][1]+e[0][1]))/2;t.scale(150*o).translate([a,u])}),e)}function uh(t,n,e){return ah(t,[[0,0],n],e)}function ch(t,n,e){return oh(t,(function(e){var r=+n,i=r/(e[1][0]-e[0][0]),o=(r-i*(e[1][0]+e[0][0]))/2,a=-i*e[0][1];t.scale(150*i).translate([o,a])}),e)}function fh(t,n,e){return oh(t,(function(e){var r=+n,i=r/(e[1][1]-e[0][1]),o=-i*e[0][0],a=(r-i*(e[1][1]+e[0][1]))/2;t.scale(150*i).translate([o,a])}),e)}nh.prototype={_radius:4.5,_circle:eh(4.5),pointRadius:function(t){return(t=+t)!==this._radius&&(this._radius=t,this._circle=null),this},polygonStart:function(){this._line=0},polygonEnd:function(){this._line=NaN},lineStart:function(){this._point=0},lineEnd:function(){0===this._line&&this._string.push("Z"),this._point=NaN},point:function(t,n){switch(this._point){case 0:this._string.push("M",t,",",n),this._point=1;break;case 1:this._string.push("L",t,",",n);break;default:null==this._circle&&(this._circle=eh(this._radius)),this._string.push("M",t,",",n,this._circle)}},result:function(){if(this._string.length){var t=this._string.join("");return this._string=[],t}return null}},ih.prototype={constructor:ih,point:function(t,n){this.stream.point(t,n)},sphere:function(){this.stream.sphere()},lineStart:function(){this.stream.lineStart()},lineEnd:function(){this.stream.lineEnd()},polygonStart:function(){this.stream.polygonStart()},polygonEnd:function(){this.stream.polygonEnd()}};var sh=Cc(30*Sc);function lh(t,n){return+n?function(t,n){function e(r,i,o,a,u,c,f,s,l,h,d,p,g,y){var v=f-r,_=s-i,b=v*v+_*_;if(b>4*n&&g--){var m=a+h,x=u+d,w=c+p,M=Ic(m*m+x*x+w*w),A=Yc(w/=M),T=Ec(Ec(w)-1)n||Ec((v*N+_*C)/b-.5)>.3||a*h+u*d+c*p2?t[2]%360*Sc:0,N()):[y*Tc,v*Tc,_*Tc]},E.angle=function(t){return arguments.length?(b=t%360*Sc,N()):b*Tc},E.reflectX=function(t){return arguments.length?(m=t?-1:1,N()):m<0},E.reflectY=function(t){return arguments.length?(x=t?-1:1,N()):x<0},E.precision=function(t){return arguments.length?(a=lh(u,S=t*t),C()):Ic(S)},E.fitExtent=function(t,n){return ah(E,t,n)},E.fitSize=function(t,n){return uh(E,t,n)},E.fitWidth=function(t,n){return ch(E,t,n)},E.fitHeight=function(t,n){return fh(E,t,n)},function(){return n=t.apply(this,arguments),E.invert=n.invert&&k,N()}}function yh(t){var n=0,e=xc/3,r=gh(t),i=r(n,e);return i.parallels=function(t){return arguments.length?r(n=t[0]*Sc,e=t[1]*Sc):[n*Tc,e*Tc]},i}function vh(t,n){var e=Fc(t),r=(e+Fc(n))/2;if(Ec(r)0?n<-wc+bc&&(n=-wc+bc):n>wc-bc&&(n=wc-bc);var e=i/Rc(Sh(n),r);return[e*Fc(r*t),i-e*Cc(r*t)]}return o.invert=function(t,n){var e=i-n,o=Oc(r)*Ic(t*t+e*e),a=Nc(t,Ec(e))*Oc(e);return e*r<0&&(a-=xc*Oc(t)*Oc(e)),[a/r,2*kc(Rc(i/o,1/r))-wc]},o}function kh(t,n){return[t,n]}function Nh(t,n){var e=Cc(t),r=t===n?Fc(t):(e-Cc(n))/(n-t),i=e/r+t;if(Ec(r)=0;)n+=e[r].value;else n=1;t.value=n}function Xh(t,n){t instanceof Map?(t=[void 0,t],void 0===n&&(n=Vh)):void 0===n&&(n=Gh);for(var e,r,i,o,a,u=new Zh(t),c=[u];e=c.pop();)if((i=n(e.data))&&(a=(i=Array.from(i)).length))for(e.children=i,o=a-1;o>=0;--o)c.push(r=i[o]=new Zh(i[o])),r.parent=e,r.depth=e.depth+1;return u.eachBefore(Wh)}function Gh(t){return t.children}function Vh(t){return Array.isArray(t)?t[1]:null}function $h(t){void 0!==t.data.value&&(t.value=t.data.value),t.data=t.data.data}function Wh(t){var n=0;do{t.height=n}while((t=t.parent)&&t.height<++n)}function Zh(t){this.data=t,this.depth=this.height=0,this.parent=null}function Kh(t){for(var n,e,r=0,i=(t=function(t){for(var n,e,r=t.length;r;)e=Math.random()*r--|0,n=t[r],t[r]=t[e],t[e]=n;return t}(Array.from(t))).length,o=[];r0&&e*e>r*r+i*i}function nd(t,n){for(var e=0;e(a*=a)?(r=(f+a-i)/(2*f),o=Math.sqrt(Math.max(0,a/f-r*r)),e.x=t.x-r*u-o*c,e.y=t.y-r*c+o*u):(r=(f+i-a)/(2*f),o=Math.sqrt(Math.max(0,i/f-r*r)),e.x=n.x+r*u-o*c,e.y=n.y+r*c+o*u)):(e.x=n.x+e.r,e.y=n.y)}function ad(t,n){var e=t.r+n.r-1e-6,r=n.x-t.x,i=n.y-t.y;return e>0&&e*e>r*r+i*i}function ud(t){var n=t._,e=t.next._,r=n.r+e.r,i=(n.x*e.r+e.x*n.r)/r,o=(n.y*e.r+e.y*n.r)/r;return i*i+o*o}function cd(t){this._=t,this.next=null,this.previous=null}function fd(t){if(!(i=(t=function(t){return"object"==typeof t&&"length"in t?t:Array.from(t)}(t)).length))return 0;var n,e,r,i,o,a,u,c,f,s,l;if((n=t[0]).x=0,n.y=0,!(i>1))return n.r;if(e=t[1],n.x=-e.r,e.x=n.r,e.y=0,!(i>2))return n.r+e.r;od(e,n,r=t[2]),n=new cd(n),e=new cd(e),r=new cd(r),n.next=r.previous=e,e.next=n.previous=r,r.next=e.previous=n;t:for(u=3;ubc&&--i>0);return[t/(.8707+(o=r*r)*(o*(o*o*o*(.003971-.001529*o)-.013791)-.131979)),r]},Ih.invert=xh(Yc),Uh.invert=xh((function(t){return 2*kc(t)})),Bh.invert=function(t,n){return[-n,2*kc(zc(t))-wc]},Zh.prototype=Xh.prototype={constructor:Zh,count:function(){return this.eachAfter(Hh)},each:function(t,n){let e=-1;for(const r of this)t.call(n,r,++e,this);return this},eachAfter:function(t,n){for(var e,r,i,o=this,a=[o],u=[],c=-1;o=a.pop();)if(u.push(o),e=o.children)for(r=0,i=e.length;r=0;--r)o.push(e[r]);return this},find:function(t,n){let e=-1;for(const r of this)if(t.call(n,r,++e,this))return r},sum:function(t){return this.eachAfter((function(n){for(var e=+t(n.data)||0,r=n.children,i=r&&r.length;--i>=0;)e+=r[i].value;n.value=e}))},sort:function(t){return this.eachBefore((function(n){n.children&&n.children.sort(t)}))},path:function(t){for(var n=this,e=function(t,n){if(t===n)return t;var e=t.ancestors(),r=n.ancestors(),i=null;t=e.pop(),n=r.pop();for(;t===n;)i=t,t=e.pop(),n=r.pop();return i}(n,t),r=[n];n!==e;)n=n.parent,r.push(n);for(var i=r.length;t!==e;)r.splice(i,0,t),t=t.parent;return r},ancestors:function(){for(var t=this,n=[t];t=t.parent;)n.push(t);return n},descendants:function(){return Array.from(this)},leaves:function(){var t=[];return this.eachBefore((function(n){n.children||t.push(n)})),t},links:function(){var t=this,n=[];return t.each((function(e){e!==t&&n.push({source:e.parent,target:e})})),n},copy:function(){return Xh(this).eachBefore($h)},[Symbol.iterator]:function*(){var t,n,e,r,i=this,o=[i];do{for(t=o.reverse(),o=[];i=t.pop();)if(yield i,n=i.children)for(e=0,r=n.length;eh&&(h=u),y=s*s*g,(d=Math.max(h/y,y/l))>p){s-=u;break}p=d}v.push(a={value:s,dice:c1?n:1)},e}(Pd);var qd=function t(n){function e(t,e,r,i,o){if((a=t._squarify)&&a.ratio===n)for(var a,u,c,f,s,l=-1,h=a.length,d=t.value;++l1?n:1)},e}(Pd);function Rd(t,n,e){return(n[0]-t[0])*(e[1]-t[1])-(n[1]-t[1])*(e[0]-t[0])}function Fd(t,n){return t[0]-n[0]||t[1]-n[1]}function Od(t){const n=t.length,e=[0,1];let r,i=2;for(r=2;r1&&Rd(t[e[i-2]],t[e[i-1]],t[r])<=0;)--i;e[i++]=r}return e.slice(0,i)}var Id=Math.random,Ud=function t(n){function e(t,e){return t=null==t?0:+t,e=null==e?1:+e,1===arguments.length?(e=t,t=0):e-=t,function(){return n()*e+t}}return e.source=t,e}(Id),Bd=function t(n){function e(t,e){return arguments.length<2&&(e=t,t=0),t=Math.floor(t),e=Math.floor(e)-t,function(){return Math.floor(n()*e+t)}}return e.source=t,e}(Id),Yd=function t(n){function e(t,e){var r,i;return t=null==t?0:+t,e=null==e?1:+e,function(){var o;if(null!=r)o=r,r=null;else do{r=2*n()-1,o=2*n()-1,i=r*r+o*o}while(!i||i>1);return t+e*o*Math.sqrt(-2*Math.log(i)/i)}}return e.source=t,e}(Id),Ld=function t(n){var e=Yd.source(n);function r(){var t=e.apply(this,arguments);return function(){return Math.exp(t())}}return r.source=t,r}(Id),jd=function t(n){function e(t){return(t=+t)<=0?()=>0:function(){for(var e=0,r=t;r>1;--r)e+=n();return e+r*n()}}return e.source=t,e}(Id),Hd=function t(n){var e=jd.source(n);function r(t){if(0==(t=+t))return n;var r=e(t);return function(){return r()/t}}return r.source=t,r}(Id),Xd=function t(n){function e(t){return function(){return-Math.log1p(-n())/t}}return e.source=t,e}(Id),Gd=function t(n){function e(t){if((t=+t)<0)throw new RangeError("invalid alpha");return t=1/-t,function(){return Math.pow(1-n(),t)}}return e.source=t,e}(Id),Vd=function t(n){function e(t){if((t=+t)<0||t>1)throw new RangeError("invalid p");return function(){return Math.floor(n()+t)}}return e.source=t,e}(Id),$d=function t(n){function e(t){if((t=+t)<0||t>1)throw new RangeError("invalid p");return 0===t?()=>1/0:1===t?()=>1:(t=Math.log1p(-t),function(){return 1+Math.floor(Math.log1p(-n())/t)})}return e.source=t,e}(Id),Wd=function t(n){var e=Yd.source(n)();function r(t,r){if((t=+t)<0)throw new RangeError("invalid k");if(0===t)return()=>0;if(r=null==r?1:+r,1===t)return()=>-Math.log1p(-n())*r;var i=(t<1?t+1:t)-1/3,o=1/(3*Math.sqrt(i)),a=t<1?()=>Math.pow(n(),1/t):()=>1;return function(){do{do{var t=e(),u=1+o*t}while(u<=0);u*=u*u;var c=1-n()}while(c>=1-.0331*t*t*t*t&&Math.log(c)>=.5*t*t+i*(1-u+Math.log(u)));return i*u*a()*r}}return r.source=t,r}(Id),Zd=function t(n){var e=Wd.source(n);function r(t,n){var r=e(t),i=e(n);return function(){var t=r();return 0===t?0:t/(t+i())}}return r.source=t,r}(Id),Kd=function t(n){var e=$d.source(n),r=Zd.source(n);function i(t,n){return t=+t,(n=+n)>=1?()=>t:n<=0?()=>0:function(){for(var i=0,o=t,a=n;o*a>16&&o*(1-a)>16;){var u=Math.floor((o+1)*a),c=r(u,o-u+1)();c<=a?(i+=u,o-=u,a=(a-c)/(1-c)):(o=u-1,a/=c)}for(var f=a<.5,s=e(f?a:1-a),l=s(),h=0;l<=o;++h)l+=s();return i+(f?h:o-h)}}return i.source=t,i}(Id),Qd=function t(n){function e(t,e,r){var i;return 0==(t=+t)?i=t=>-Math.log(t):(t=1/t,i=n=>Math.pow(n,t)),e=null==e?0:+e,r=null==r?1:+r,function(){return e+r*i(-Math.log1p(-n()))}}return e.source=t,e}(Id),Jd=function t(n){function e(t,e){return t=null==t?0:+t,e=null==e?1:+e,function(){return t+e*Math.tan(Math.PI*n())}}return e.source=t,e}(Id),tp=function t(n){function e(t,e){return t=null==t?0:+t,e=null==e?1:+e,function(){var r=n();return t+e*Math.log(r/(1-r))}}return e.source=t,e}(Id),np=function t(n){var e=Wd.source(n),r=Kd.source(n);function i(t){return function(){for(var i=0,o=t;o>16;){var a=Math.floor(.875*o),u=e(a)();if(u>o)return i+r(a-1,o/u)();i+=a,o-=u}for(var c=-Math.log1p(-n()),f=0;c<=o;++f)c-=Math.log1p(-n());return i+f}}return i.source=t,i}(Id);const ep=1/4294967296;function rp(t,n){switch(arguments.length){case 0:break;case 1:this.range(t);break;default:this.range(n).domain(t)}return this}function ip(t,n){switch(arguments.length){case 0:break;case 1:"function"==typeof t?this.interpolator(t):this.range(t);break;default:this.domain(t),"function"==typeof n?this.interpolator(n):this.range(n)}return this}const op=Symbol("implicit");function ap(){var t=new Map,n=[],e=[],r=op;function i(i){var o=i+"",a=t.get(o);if(!a){if(r!==op)return r;t.set(o,a=n.push(i))}return e[(a-1)%e.length]}return i.domain=function(e){if(!arguments.length)return n.slice();n=[],t=new Map;for(const r of e){const e=r+"";t.has(e)||t.set(e,n.push(r))}return i},i.range=function(t){return arguments.length?(e=Array.from(t),i):e.slice()},i.unknown=function(t){return arguments.length?(r=t,i):r},i.copy=function(){return ap(n,e).unknown(r)},rp.apply(i,arguments),i}function up(){var t,n,e=ap().unknown(void 0),r=e.domain,i=e.range,o=0,a=1,u=!1,c=0,f=0,s=.5;function l(){var e=r().length,l=an&&(e=t,t=n,n=e),function(e){return Math.max(t,Math.min(n,e))}}(a[0],a[t-1])),r=t>2?pp:dp,i=o=null,l}function l(n){return null==n||isNaN(n=+n)?e:(i||(i=r(a.map(t),u,c)))(t(f(n)))}return l.invert=function(e){return f(n((o||(o=r(u,a.map(t),_r)))(e)))},l.domain=function(t){return arguments.length?(a=Array.from(t,fp),s()):a.slice()},l.range=function(t){return arguments.length?(u=Array.from(t),s()):u.slice()},l.rangeRound=function(t){return u=Array.from(t),c=Ar,s()},l.clamp=function(t){return arguments.length?(f=!!t||lp,s()):f!==lp},l.interpolate=function(t){return arguments.length?(c=t,s()):c},l.unknown=function(t){return arguments.length?(e=t,l):e},function(e,r){return t=e,n=r,s()}}function vp(){return yp()(lp,lp)}function _p(n,e,r,i){var o,a=F(n,e,r);switch((i=ac(null==i?",f":i)).type){case"s":var u=Math.max(Math.abs(n),Math.abs(e));return null!=i.precision||isNaN(o=vc(a,u))||(i.precision=o),t.formatPrefix(i,u);case"":case"e":case"g":case"p":case"r":null!=i.precision||isNaN(o=_c(a,Math.max(Math.abs(n),Math.abs(e))))||(i.precision=o-("e"===i.type));break;case"f":case"%":null!=i.precision||isNaN(o=yc(a))||(i.precision=o-2*("%"===i.type))}return t.format(i)}function bp(t){var n=t.domain;return t.ticks=function(t){var e=n();return q(e[0],e[e.length-1],null==t?10:t)},t.tickFormat=function(t,e){var r=n();return _p(r[0],r[r.length-1],null==t?10:t,e)},t.nice=function(e){null==e&&(e=10);var r,i,o=n(),a=0,u=o.length-1,c=o[a],f=o[u],s=10;for(f0;){if((i=R(c,f,e))===r)return o[a]=c,o[u]=f,n(o);if(i>0)c=Math.floor(c/i)*i,f=Math.ceil(f/i)*i;else{if(!(i<0))break;c=Math.ceil(c*i)/i,f=Math.floor(f*i)/i}r=i}return t},t}function mp(t,n){var e,r=0,i=(t=t.slice()).length-1,o=t[r],a=t[i];return a0){for(;h<=d;++h)for(s=1,f=r(h);sc)break;g.push(l)}}else for(;h<=d;++h)for(s=a-1,f=r(h);s>=1;--s)if(!((l=f*s)c)break;g.push(l)}2*g.length0))return u;do{u.push(a=new Date(+e)),n(e,o),t(e)}while(a=n)for(;t(n),!e(n);)n.setTime(n-1)}),(function(t,r){if(t>=t)if(r<0)for(;++r<=0;)for(;n(t,-1),!e(t););else for(;--r>=0;)for(;n(t,1),!e(t););}))},e&&(i.count=function(n,r){return Ip.setTime(+n),Up.setTime(+r),t(Ip),t(Up),Math.floor(e(Ip,Up))},i.every=function(t){return t=Math.floor(t),isFinite(t)&&t>0?t>1?i.filter(r?function(n){return r(n)%t==0}:function(n){return i.count(0,n)%t==0}):i:null}),i}var Yp=Bp((function(){}),(function(t,n){t.setTime(+t+n)}),(function(t,n){return n-t}));Yp.every=function(t){return t=Math.floor(t),isFinite(t)&&t>0?t>1?Bp((function(n){n.setTime(Math.floor(n/t)*t)}),(function(n,e){n.setTime(+n+e*t)}),(function(n,e){return(e-n)/t})):Yp:null};var Lp=Yp.range;const jp=1e3,Hp=6e4,Xp=36e5,Gp=864e5,Vp=6048e5,$p=2592e6,Wp=31536e6;var Zp=Bp((function(t){t.setTime(t-t.getMilliseconds())}),(function(t,n){t.setTime(+t+n*jp)}),(function(t,n){return(n-t)/jp}),(function(t){return t.getUTCSeconds()})),Kp=Zp.range,Qp=Bp((function(t){t.setTime(t-t.getMilliseconds()-t.getSeconds()*jp)}),(function(t,n){t.setTime(+t+n*Hp)}),(function(t,n){return(n-t)/Hp}),(function(t){return t.getMinutes()})),Jp=Qp.range,tg=Bp((function(t){t.setTime(t-t.getMilliseconds()-t.getSeconds()*jp-t.getMinutes()*Hp)}),(function(t,n){t.setTime(+t+n*Xp)}),(function(t,n){return(n-t)/Xp}),(function(t){return t.getHours()})),ng=tg.range,eg=Bp((t=>t.setHours(0,0,0,0)),((t,n)=>t.setDate(t.getDate()+n)),((t,n)=>(n-t-(n.getTimezoneOffset()-t.getTimezoneOffset())*Hp)/Gp),(t=>t.getDate()-1)),rg=eg.range;function ig(t){return Bp((function(n){n.setDate(n.getDate()-(n.getDay()+7-t)%7),n.setHours(0,0,0,0)}),(function(t,n){t.setDate(t.getDate()+7*n)}),(function(t,n){return(n-t-(n.getTimezoneOffset()-t.getTimezoneOffset())*Hp)/Vp}))}var og=ig(0),ag=ig(1),ug=ig(2),cg=ig(3),fg=ig(4),sg=ig(5),lg=ig(6),hg=og.range,dg=ag.range,pg=ug.range,gg=cg.range,yg=fg.range,vg=sg.range,_g=lg.range,bg=Bp((function(t){t.setDate(1),t.setHours(0,0,0,0)}),(function(t,n){t.setMonth(t.getMonth()+n)}),(function(t,n){return n.getMonth()-t.getMonth()+12*(n.getFullYear()-t.getFullYear())}),(function(t){return t.getMonth()})),mg=bg.range,xg=Bp((function(t){t.setMonth(0,1),t.setHours(0,0,0,0)}),(function(t,n){t.setFullYear(t.getFullYear()+n)}),(function(t,n){return n.getFullYear()-t.getFullYear()}),(function(t){return t.getFullYear()}));xg.every=function(t){return isFinite(t=Math.floor(t))&&t>0?Bp((function(n){n.setFullYear(Math.floor(n.getFullYear()/t)*t),n.setMonth(0,1),n.setHours(0,0,0,0)}),(function(n,e){n.setFullYear(n.getFullYear()+e*t)})):null};var wg=xg.range,Mg=Bp((function(t){t.setUTCSeconds(0,0)}),(function(t,n){t.setTime(+t+n*Hp)}),(function(t,n){return(n-t)/Hp}),(function(t){return t.getUTCMinutes()})),Ag=Mg.range,Tg=Bp((function(t){t.setUTCMinutes(0,0,0)}),(function(t,n){t.setTime(+t+n*Xp)}),(function(t,n){return(n-t)/Xp}),(function(t){return t.getUTCHours()})),Sg=Tg.range,Eg=Bp((function(t){t.setUTCHours(0,0,0,0)}),(function(t,n){t.setUTCDate(t.getUTCDate()+n)}),(function(t,n){return(n-t)/Gp}),(function(t){return t.getUTCDate()-1})),kg=Eg.range;function Ng(t){return Bp((function(n){n.setUTCDate(n.getUTCDate()-(n.getUTCDay()+7-t)%7),n.setUTCHours(0,0,0,0)}),(function(t,n){t.setUTCDate(t.getUTCDate()+7*n)}),(function(t,n){return(n-t)/Vp}))}var Cg=Ng(0),Pg=Ng(1),zg=Ng(2),Dg=Ng(3),qg=Ng(4),Rg=Ng(5),Fg=Ng(6),Og=Cg.range,Ig=Pg.range,Ug=zg.range,Bg=Dg.range,Yg=qg.range,Lg=Rg.range,jg=Fg.range,Hg=Bp((function(t){t.setUTCDate(1),t.setUTCHours(0,0,0,0)}),(function(t,n){t.setUTCMonth(t.getUTCMonth()+n)}),(function(t,n){return n.getUTCMonth()-t.getUTCMonth()+12*(n.getUTCFullYear()-t.getUTCFullYear())}),(function(t){return t.getUTCMonth()})),Xg=Hg.range,Gg=Bp((function(t){t.setUTCMonth(0,1),t.setUTCHours(0,0,0,0)}),(function(t,n){t.setUTCFullYear(t.getUTCFullYear()+n)}),(function(t,n){return n.getUTCFullYear()-t.getUTCFullYear()}),(function(t){return t.getUTCFullYear()}));Gg.every=function(t){return isFinite(t=Math.floor(t))&&t>0?Bp((function(n){n.setUTCFullYear(Math.floor(n.getUTCFullYear()/t)*t),n.setUTCMonth(0,1),n.setUTCHours(0,0,0,0)}),(function(n,e){n.setUTCFullYear(n.getUTCFullYear()+e*t)})):null};var Vg=Gg.range;function $g(t,n,r,i,o,a){const u=[[Zp,1,jp],[Zp,5,5e3],[Zp,15,15e3],[Zp,30,3e4],[a,1,Hp],[a,5,3e5],[a,15,9e5],[a,30,18e5],[o,1,Xp],[o,3,108e5],[o,6,216e5],[o,12,432e5],[i,1,Gp],[i,2,1728e5],[r,1,Vp],[n,1,$p],[n,3,7776e6],[t,1,Wp]];function c(n,r,i){const o=Math.abs(r-n)/i,a=e((([,,t])=>t)).right(u,o);if(a===u.length)return t.every(F(n/Wp,r/Wp,i));if(0===a)return Yp.every(Math.max(F(n,r,i),1));const[c,f]=u[o/u[a-1][2]=12)]},q:function(t){return 1+~~(t.getMonth()/3)},Q:bv,s:mv,S:By,u:Yy,U:Ly,V:Hy,w:Xy,W:Gy,x:null,X:null,y:Vy,Y:Wy,Z:Ky,"%":_v},m={a:function(t){return a[t.getUTCDay()]},A:function(t){return o[t.getUTCDay()]},b:function(t){return c[t.getUTCMonth()]},B:function(t){return u[t.getUTCMonth()]},c:null,d:Qy,e:Qy,f:rv,g:pv,G:yv,H:Jy,I:tv,j:nv,L:ev,m:iv,M:ov,p:function(t){return i[+(t.getUTCHours()>=12)]},q:function(t){return 1+~~(t.getUTCMonth()/3)},Q:bv,s:mv,S:av,u:uv,U:cv,V:sv,w:lv,W:hv,x:null,X:null,y:dv,Y:gv,Z:vv,"%":_v},x={a:function(t,n,e){var r=d.exec(n.slice(e));return r?(t.w=p.get(r[0].toLowerCase()),e+r[0].length):-1},A:function(t,n,e){var r=l.exec(n.slice(e));return r?(t.w=h.get(r[0].toLowerCase()),e+r[0].length):-1},b:function(t,n,e){var r=v.exec(n.slice(e));return r?(t.m=_.get(r[0].toLowerCase()),e+r[0].length):-1},B:function(t,n,e){var r=g.exec(n.slice(e));return r?(t.m=y.get(r[0].toLowerCase()),e+r[0].length):-1},c:function(t,e,r){return A(t,n,e,r)},d:wy,e:wy,f:ky,g:_y,G:vy,H:Ay,I:Ay,j:My,L:Ey,m:xy,M:Ty,p:function(t,n,e){var r=f.exec(n.slice(e));return r?(t.p=s.get(r[0].toLowerCase()),e+r[0].length):-1},q:my,Q:Cy,s:Py,S:Sy,u:dy,U:py,V:gy,w:hy,W:yy,x:function(t,n,r){return A(t,e,n,r)},X:function(t,n,e){return A(t,r,n,e)},y:_y,Y:vy,Z:by,"%":Ny};function w(t,n){return function(e){var r,i,o,a=[],u=-1,c=0,f=t.length;for(e instanceof Date||(e=new Date(+e));++u53)return null;"w"in o||(o.w=1),"Z"in o?(i=(r=ty(ny(o.y,0,1))).getUTCDay(),r=i>4||0===i?Pg.ceil(r):Pg(r),r=Eg.offset(r,7*(o.V-1)),o.y=r.getUTCFullYear(),o.m=r.getUTCMonth(),o.d=r.getUTCDate()+(o.w+6)%7):(i=(r=Jg(ny(o.y,0,1))).getDay(),r=i>4||0===i?ag.ceil(r):ag(r),r=eg.offset(r,7*(o.V-1)),o.y=r.getFullYear(),o.m=r.getMonth(),o.d=r.getDate()+(o.w+6)%7)}else("W"in o||"U"in o)&&("w"in o||(o.w="u"in o?o.u%7:"W"in o?1:0),i="Z"in o?ty(ny(o.y,0,1)).getUTCDay():Jg(ny(o.y,0,1)).getDay(),o.m=0,o.d="W"in o?(o.w+6)%7+7*o.W-(i+5)%7:o.w+7*o.U-(i+6)%7);return"Z"in o?(o.H+=o.Z/100|0,o.M+=o.Z%100,ty(o)):Jg(o)}}function A(t,n,e,r){for(var i,o,a=0,u=n.length,c=e.length;a=c)return-1;if(37===(i=n.charCodeAt(a++))){if(i=n.charAt(a++),!(o=x[i in iy?n.charAt(a++):i])||(r=o(t,e,r))<0)return-1}else if(i!=e.charCodeAt(r++))return-1}return r}return b.x=w(e,b),b.X=w(r,b),b.c=w(n,b),m.x=w(e,m),m.X=w(r,m),m.c=w(n,m),{format:function(t){var n=w(t+="",b);return n.toString=function(){return t},n},parse:function(t){var n=M(t+="",!1);return n.toString=function(){return t},n},utcFormat:function(t){var n=w(t+="",m);return n.toString=function(){return t},n},utcParse:function(t){var n=M(t+="",!0);return n.toString=function(){return t},n}}}var ry,iy={"-":"",_:" ",0:"0"},oy=/^\s*\d+/,ay=/^%/,uy=/[\\^$*+?|[\]().{}]/g;function cy(t,n,e){var r=t<0?"-":"",i=(r?-t:t)+"",o=i.length;return r+(o[t.toLowerCase(),n])))}function hy(t,n,e){var r=oy.exec(n.slice(e,e+1));return r?(t.w=+r[0],e+r[0].length):-1}function dy(t,n,e){var r=oy.exec(n.slice(e,e+1));return r?(t.u=+r[0],e+r[0].length):-1}function py(t,n,e){var r=oy.exec(n.slice(e,e+2));return r?(t.U=+r[0],e+r[0].length):-1}function gy(t,n,e){var r=oy.exec(n.slice(e,e+2));return r?(t.V=+r[0],e+r[0].length):-1}function yy(t,n,e){var r=oy.exec(n.slice(e,e+2));return r?(t.W=+r[0],e+r[0].length):-1}function vy(t,n,e){var r=oy.exec(n.slice(e,e+4));return r?(t.y=+r[0],e+r[0].length):-1}function _y(t,n,e){var r=oy.exec(n.slice(e,e+2));return r?(t.y=+r[0]+(+r[0]>68?1900:2e3),e+r[0].length):-1}function by(t,n,e){var r=/^(Z)|([+-]\d\d)(?::?(\d\d))?/.exec(n.slice(e,e+6));return r?(t.Z=r[1]?0:-(r[2]+(r[3]||"00")),e+r[0].length):-1}function my(t,n,e){var r=oy.exec(n.slice(e,e+1));return r?(t.q=3*r[0]-3,e+r[0].length):-1}function xy(t,n,e){var r=oy.exec(n.slice(e,e+2));return r?(t.m=r[0]-1,e+r[0].length):-1}function wy(t,n,e){var r=oy.exec(n.slice(e,e+2));return r?(t.d=+r[0],e+r[0].length):-1}function My(t,n,e){var r=oy.exec(n.slice(e,e+3));return r?(t.m=0,t.d=+r[0],e+r[0].length):-1}function Ay(t,n,e){var r=oy.exec(n.slice(e,e+2));return r?(t.H=+r[0],e+r[0].length):-1}function Ty(t,n,e){var r=oy.exec(n.slice(e,e+2));return r?(t.M=+r[0],e+r[0].length):-1}function Sy(t,n,e){var r=oy.exec(n.slice(e,e+2));return r?(t.S=+r[0],e+r[0].length):-1}function Ey(t,n,e){var r=oy.exec(n.slice(e,e+3));return r?(t.L=+r[0],e+r[0].length):-1}function ky(t,n,e){var r=oy.exec(n.slice(e,e+6));return r?(t.L=Math.floor(r[0]/1e3),e+r[0].length):-1}function Ny(t,n,e){var r=ay.exec(n.slice(e,e+1));return r?e+r[0].length:-1}function Cy(t,n,e){var r=oy.exec(n.slice(e));return r?(t.Q=+r[0],e+r[0].length):-1}function Py(t,n,e){var r=oy.exec(n.slice(e));return r?(t.s=+r[0],e+r[0].length):-1}function zy(t,n){return cy(t.getDate(),n,2)}function Dy(t,n){return cy(t.getHours(),n,2)}function qy(t,n){return cy(t.getHours()%12||12,n,2)}function Ry(t,n){return cy(1+eg.count(xg(t),t),n,3)}function Fy(t,n){return cy(t.getMilliseconds(),n,3)}function Oy(t,n){return Fy(t,n)+"000"}function Iy(t,n){return cy(t.getMonth()+1,n,2)}function Uy(t,n){return cy(t.getMinutes(),n,2)}function By(t,n){return cy(t.getSeconds(),n,2)}function Yy(t){var n=t.getDay();return 0===n?7:n}function Ly(t,n){return cy(og.count(xg(t)-1,t),n,2)}function jy(t){var n=t.getDay();return n>=4||0===n?fg(t):fg.ceil(t)}function Hy(t,n){return t=jy(t),cy(fg.count(xg(t),t)+(4===xg(t).getDay()),n,2)}function Xy(t){return t.getDay()}function Gy(t,n){return cy(ag.count(xg(t)-1,t),n,2)}function Vy(t,n){return cy(t.getFullYear()%100,n,2)}function $y(t,n){return cy((t=jy(t)).getFullYear()%100,n,2)}function Wy(t,n){return cy(t.getFullYear()%1e4,n,4)}function Zy(t,n){var e=t.getDay();return cy((t=e>=4||0===e?fg(t):fg.ceil(t)).getFullYear()%1e4,n,4)}function Ky(t){var n=t.getTimezoneOffset();return(n>0?"-":(n*=-1,"+"))+cy(n/60|0,"0",2)+cy(n%60,"0",2)}function Qy(t,n){return cy(t.getUTCDate(),n,2)}function Jy(t,n){return cy(t.getUTCHours(),n,2)}function tv(t,n){return cy(t.getUTCHours()%12||12,n,2)}function nv(t,n){return cy(1+Eg.count(Gg(t),t),n,3)}function ev(t,n){return cy(t.getUTCMilliseconds(),n,3)}function rv(t,n){return ev(t,n)+"000"}function iv(t,n){return cy(t.getUTCMonth()+1,n,2)}function ov(t,n){return cy(t.getUTCMinutes(),n,2)}function av(t,n){return cy(t.getUTCSeconds(),n,2)}function uv(t){var n=t.getUTCDay();return 0===n?7:n}function cv(t,n){return cy(Cg.count(Gg(t)-1,t),n,2)}function fv(t){var n=t.getUTCDay();return n>=4||0===n?qg(t):qg.ceil(t)}function sv(t,n){return t=fv(t),cy(qg.count(Gg(t),t)+(4===Gg(t).getUTCDay()),n,2)}function lv(t){return t.getUTCDay()}function hv(t,n){return cy(Pg.count(Gg(t)-1,t),n,2)}function dv(t,n){return cy(t.getUTCFullYear()%100,n,2)}function pv(t,n){return cy((t=fv(t)).getUTCFullYear()%100,n,2)}function gv(t,n){return cy(t.getUTCFullYear()%1e4,n,4)}function yv(t,n){var e=t.getUTCDay();return cy((t=e>=4||0===e?qg(t):qg.ceil(t)).getUTCFullYear()%1e4,n,4)}function vv(){return"+0000"}function _v(){return"%"}function bv(t){return+t}function mv(t){return Math.floor(+t/1e3)}function xv(n){return ry=ey(n),t.timeFormat=ry.format,t.timeParse=ry.parse,t.utcFormat=ry.utcFormat,t.utcParse=ry.utcParse,ry}t.timeFormat=void 0,t.timeParse=void 0,t.utcFormat=void 0,t.utcParse=void 0,xv({dateTime:"%x, %X",date:"%-m/%-d/%Y",time:"%-I:%M:%S %p",periods:["AM","PM"],days:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],shortDays:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],months:["January","February","March","April","May","June","July","August","September","October","November","December"],shortMonths:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"]});var wv="%Y-%m-%dT%H:%M:%S.%LZ";var Mv=Date.prototype.toISOString?function(t){return t.toISOString()}:t.utcFormat(wv);var Av=+new Date("2000-01-01T00:00:00.000Z")?function(t){var n=new Date(t);return isNaN(n)?null:n}:t.utcParse(wv);function Tv(t){return new Date(t)}function Sv(t){return t instanceof Date?+t:+new Date(+t)}function Ev(t,n,e,r,i,o,a,u,c,f){var s=vp(),l=s.invert,h=s.domain,d=f(".%L"),p=f(":%S"),g=f("%I:%M"),y=f("%I %p"),v=f("%a %d"),_=f("%b %d"),b=f("%B"),m=f("%Y");function x(t){return(c(t)hr(t[t.length-1]),Xv=new Array(3).concat("d8b365f5f5f55ab4ac","a6611adfc27d80cdc1018571","a6611adfc27df5f5f580cdc1018571","8c510ad8b365f6e8c3c7eae55ab4ac01665e","8c510ad8b365f6e8c3f5f5f5c7eae55ab4ac01665e","8c510abf812ddfc27df6e8c3c7eae580cdc135978f01665e","8c510abf812ddfc27df6e8c3f5f5f5c7eae580cdc135978f01665e","5430058c510abf812ddfc27df6e8c3c7eae580cdc135978f01665e003c30","5430058c510abf812ddfc27df6e8c3f5f5f5c7eae580cdc135978f01665e003c30").map(Dv),Gv=Hv(Xv),Vv=new Array(3).concat("af8dc3f7f7f77fbf7b","7b3294c2a5cfa6dba0008837","7b3294c2a5cff7f7f7a6dba0008837","762a83af8dc3e7d4e8d9f0d37fbf7b1b7837","762a83af8dc3e7d4e8f7f7f7d9f0d37fbf7b1b7837","762a839970abc2a5cfe7d4e8d9f0d3a6dba05aae611b7837","762a839970abc2a5cfe7d4e8f7f7f7d9f0d3a6dba05aae611b7837","40004b762a839970abc2a5cfe7d4e8d9f0d3a6dba05aae611b783700441b","40004b762a839970abc2a5cfe7d4e8f7f7f7d9f0d3a6dba05aae611b783700441b").map(Dv),$v=Hv(Vv),Wv=new Array(3).concat("e9a3c9f7f7f7a1d76a","d01c8bf1b6dab8e1864dac26","d01c8bf1b6daf7f7f7b8e1864dac26","c51b7de9a3c9fde0efe6f5d0a1d76a4d9221","c51b7de9a3c9fde0eff7f7f7e6f5d0a1d76a4d9221","c51b7dde77aef1b6dafde0efe6f5d0b8e1867fbc414d9221","c51b7dde77aef1b6dafde0eff7f7f7e6f5d0b8e1867fbc414d9221","8e0152c51b7dde77aef1b6dafde0efe6f5d0b8e1867fbc414d9221276419","8e0152c51b7dde77aef1b6dafde0eff7f7f7e6f5d0b8e1867fbc414d9221276419").map(Dv),Zv=Hv(Wv),Kv=new Array(3).concat("998ec3f7f7f7f1a340","5e3c99b2abd2fdb863e66101","5e3c99b2abd2f7f7f7fdb863e66101","542788998ec3d8daebfee0b6f1a340b35806","542788998ec3d8daebf7f7f7fee0b6f1a340b35806","5427888073acb2abd2d8daebfee0b6fdb863e08214b35806","5427888073acb2abd2d8daebf7f7f7fee0b6fdb863e08214b35806","2d004b5427888073acb2abd2d8daebfee0b6fdb863e08214b358067f3b08","2d004b5427888073acb2abd2d8daebf7f7f7fee0b6fdb863e08214b358067f3b08").map(Dv),Qv=Hv(Kv),Jv=new Array(3).concat("ef8a62f7f7f767a9cf","ca0020f4a58292c5de0571b0","ca0020f4a582f7f7f792c5de0571b0","b2182bef8a62fddbc7d1e5f067a9cf2166ac","b2182bef8a62fddbc7f7f7f7d1e5f067a9cf2166ac","b2182bd6604df4a582fddbc7d1e5f092c5de4393c32166ac","b2182bd6604df4a582fddbc7f7f7f7d1e5f092c5de4393c32166ac","67001fb2182bd6604df4a582fddbc7d1e5f092c5de4393c32166ac053061","67001fb2182bd6604df4a582fddbc7f7f7f7d1e5f092c5de4393c32166ac053061").map(Dv),t_=Hv(Jv),n_=new Array(3).concat("ef8a62ffffff999999","ca0020f4a582bababa404040","ca0020f4a582ffffffbababa404040","b2182bef8a62fddbc7e0e0e09999994d4d4d","b2182bef8a62fddbc7ffffffe0e0e09999994d4d4d","b2182bd6604df4a582fddbc7e0e0e0bababa8787874d4d4d","b2182bd6604df4a582fddbc7ffffffe0e0e0bababa8787874d4d4d","67001fb2182bd6604df4a582fddbc7e0e0e0bababa8787874d4d4d1a1a1a","67001fb2182bd6604df4a582fddbc7ffffffe0e0e0bababa8787874d4d4d1a1a1a").map(Dv),e_=Hv(n_),r_=new Array(3).concat("fc8d59ffffbf91bfdb","d7191cfdae61abd9e92c7bb6","d7191cfdae61ffffbfabd9e92c7bb6","d73027fc8d59fee090e0f3f891bfdb4575b4","d73027fc8d59fee090ffffbfe0f3f891bfdb4575b4","d73027f46d43fdae61fee090e0f3f8abd9e974add14575b4","d73027f46d43fdae61fee090ffffbfe0f3f8abd9e974add14575b4","a50026d73027f46d43fdae61fee090e0f3f8abd9e974add14575b4313695","a50026d73027f46d43fdae61fee090ffffbfe0f3f8abd9e974add14575b4313695").map(Dv),i_=Hv(r_),o_=new Array(3).concat("fc8d59ffffbf91cf60","d7191cfdae61a6d96a1a9641","d7191cfdae61ffffbfa6d96a1a9641","d73027fc8d59fee08bd9ef8b91cf601a9850","d73027fc8d59fee08bffffbfd9ef8b91cf601a9850","d73027f46d43fdae61fee08bd9ef8ba6d96a66bd631a9850","d73027f46d43fdae61fee08bffffbfd9ef8ba6d96a66bd631a9850","a50026d73027f46d43fdae61fee08bd9ef8ba6d96a66bd631a9850006837","a50026d73027f46d43fdae61fee08bffffbfd9ef8ba6d96a66bd631a9850006837").map(Dv),a_=Hv(o_),u_=new Array(3).concat("fc8d59ffffbf99d594","d7191cfdae61abdda42b83ba","d7191cfdae61ffffbfabdda42b83ba","d53e4ffc8d59fee08be6f59899d5943288bd","d53e4ffc8d59fee08bffffbfe6f59899d5943288bd","d53e4ff46d43fdae61fee08be6f598abdda466c2a53288bd","d53e4ff46d43fdae61fee08bffffbfe6f598abdda466c2a53288bd","9e0142d53e4ff46d43fdae61fee08be6f598abdda466c2a53288bd5e4fa2","9e0142d53e4ff46d43fdae61fee08bffffbfe6f598abdda466c2a53288bd5e4fa2").map(Dv),c_=Hv(u_),f_=new Array(3).concat("e5f5f999d8c92ca25f","edf8fbb2e2e266c2a4238b45","edf8fbb2e2e266c2a42ca25f006d2c","edf8fbccece699d8c966c2a42ca25f006d2c","edf8fbccece699d8c966c2a441ae76238b45005824","f7fcfde5f5f9ccece699d8c966c2a441ae76238b45005824","f7fcfde5f5f9ccece699d8c966c2a441ae76238b45006d2c00441b").map(Dv),s_=Hv(f_),l_=new Array(3).concat("e0ecf49ebcda8856a7","edf8fbb3cde38c96c688419d","edf8fbb3cde38c96c68856a7810f7c","edf8fbbfd3e69ebcda8c96c68856a7810f7c","edf8fbbfd3e69ebcda8c96c68c6bb188419d6e016b","f7fcfde0ecf4bfd3e69ebcda8c96c68c6bb188419d6e016b","f7fcfde0ecf4bfd3e69ebcda8c96c68c6bb188419d810f7c4d004b").map(Dv),h_=Hv(l_),d_=new Array(3).concat("e0f3dba8ddb543a2ca","f0f9e8bae4bc7bccc42b8cbe","f0f9e8bae4bc7bccc443a2ca0868ac","f0f9e8ccebc5a8ddb57bccc443a2ca0868ac","f0f9e8ccebc5a8ddb57bccc44eb3d32b8cbe08589e","f7fcf0e0f3dbccebc5a8ddb57bccc44eb3d32b8cbe08589e","f7fcf0e0f3dbccebc5a8ddb57bccc44eb3d32b8cbe0868ac084081").map(Dv),p_=Hv(d_),g_=new Array(3).concat("fee8c8fdbb84e34a33","fef0d9fdcc8afc8d59d7301f","fef0d9fdcc8afc8d59e34a33b30000","fef0d9fdd49efdbb84fc8d59e34a33b30000","fef0d9fdd49efdbb84fc8d59ef6548d7301f990000","fff7ecfee8c8fdd49efdbb84fc8d59ef6548d7301f990000","fff7ecfee8c8fdd49efdbb84fc8d59ef6548d7301fb300007f0000").map(Dv),y_=Hv(g_),v_=new Array(3).concat("ece2f0a6bddb1c9099","f6eff7bdc9e167a9cf02818a","f6eff7bdc9e167a9cf1c9099016c59","f6eff7d0d1e6a6bddb67a9cf1c9099016c59","f6eff7d0d1e6a6bddb67a9cf3690c002818a016450","fff7fbece2f0d0d1e6a6bddb67a9cf3690c002818a016450","fff7fbece2f0d0d1e6a6bddb67a9cf3690c002818a016c59014636").map(Dv),__=Hv(v_),b_=new Array(3).concat("ece7f2a6bddb2b8cbe","f1eef6bdc9e174a9cf0570b0","f1eef6bdc9e174a9cf2b8cbe045a8d","f1eef6d0d1e6a6bddb74a9cf2b8cbe045a8d","f1eef6d0d1e6a6bddb74a9cf3690c00570b0034e7b","fff7fbece7f2d0d1e6a6bddb74a9cf3690c00570b0034e7b","fff7fbece7f2d0d1e6a6bddb74a9cf3690c00570b0045a8d023858").map(Dv),m_=Hv(b_),x_=new Array(3).concat("e7e1efc994c7dd1c77","f1eef6d7b5d8df65b0ce1256","f1eef6d7b5d8df65b0dd1c77980043","f1eef6d4b9dac994c7df65b0dd1c77980043","f1eef6d4b9dac994c7df65b0e7298ace125691003f","f7f4f9e7e1efd4b9dac994c7df65b0e7298ace125691003f","f7f4f9e7e1efd4b9dac994c7df65b0e7298ace125698004367001f").map(Dv),w_=Hv(x_),M_=new Array(3).concat("fde0ddfa9fb5c51b8a","feebe2fbb4b9f768a1ae017e","feebe2fbb4b9f768a1c51b8a7a0177","feebe2fcc5c0fa9fb5f768a1c51b8a7a0177","feebe2fcc5c0fa9fb5f768a1dd3497ae017e7a0177","fff7f3fde0ddfcc5c0fa9fb5f768a1dd3497ae017e7a0177","fff7f3fde0ddfcc5c0fa9fb5f768a1dd3497ae017e7a017749006a").map(Dv),A_=Hv(M_),T_=new Array(3).concat("edf8b17fcdbb2c7fb8","ffffcca1dab441b6c4225ea8","ffffcca1dab441b6c42c7fb8253494","ffffccc7e9b47fcdbb41b6c42c7fb8253494","ffffccc7e9b47fcdbb41b6c41d91c0225ea80c2c84","ffffd9edf8b1c7e9b47fcdbb41b6c41d91c0225ea80c2c84","ffffd9edf8b1c7e9b47fcdbb41b6c41d91c0225ea8253494081d58").map(Dv),S_=Hv(T_),E_=new Array(3).concat("f7fcb9addd8e31a354","ffffccc2e69978c679238443","ffffccc2e69978c67931a354006837","ffffccd9f0a3addd8e78c67931a354006837","ffffccd9f0a3addd8e78c67941ab5d238443005a32","ffffe5f7fcb9d9f0a3addd8e78c67941ab5d238443005a32","ffffe5f7fcb9d9f0a3addd8e78c67941ab5d238443006837004529").map(Dv),k_=Hv(E_),N_=new Array(3).concat("fff7bcfec44fd95f0e","ffffd4fed98efe9929cc4c02","ffffd4fed98efe9929d95f0e993404","ffffd4fee391fec44ffe9929d95f0e993404","ffffd4fee391fec44ffe9929ec7014cc4c028c2d04","ffffe5fff7bcfee391fec44ffe9929ec7014cc4c028c2d04","ffffe5fff7bcfee391fec44ffe9929ec7014cc4c02993404662506").map(Dv),C_=Hv(N_),P_=new Array(3).concat("ffeda0feb24cf03b20","ffffb2fecc5cfd8d3ce31a1c","ffffb2fecc5cfd8d3cf03b20bd0026","ffffb2fed976feb24cfd8d3cf03b20bd0026","ffffb2fed976feb24cfd8d3cfc4e2ae31a1cb10026","ffffccffeda0fed976feb24cfd8d3cfc4e2ae31a1cb10026","ffffccffeda0fed976feb24cfd8d3cfc4e2ae31a1cbd0026800026").map(Dv),z_=Hv(P_),D_=new Array(3).concat("deebf79ecae13182bd","eff3ffbdd7e76baed62171b5","eff3ffbdd7e76baed63182bd08519c","eff3ffc6dbef9ecae16baed63182bd08519c","eff3ffc6dbef9ecae16baed64292c62171b5084594","f7fbffdeebf7c6dbef9ecae16baed64292c62171b5084594","f7fbffdeebf7c6dbef9ecae16baed64292c62171b508519c08306b").map(Dv),q_=Hv(D_),R_=new Array(3).concat("e5f5e0a1d99b31a354","edf8e9bae4b374c476238b45","edf8e9bae4b374c47631a354006d2c","edf8e9c7e9c0a1d99b74c47631a354006d2c","edf8e9c7e9c0a1d99b74c47641ab5d238b45005a32","f7fcf5e5f5e0c7e9c0a1d99b74c47641ab5d238b45005a32","f7fcf5e5f5e0c7e9c0a1d99b74c47641ab5d238b45006d2c00441b").map(Dv),F_=Hv(R_),O_=new Array(3).concat("f0f0f0bdbdbd636363","f7f7f7cccccc969696525252","f7f7f7cccccc969696636363252525","f7f7f7d9d9d9bdbdbd969696636363252525","f7f7f7d9d9d9bdbdbd969696737373525252252525","fffffff0f0f0d9d9d9bdbdbd969696737373525252252525","fffffff0f0f0d9d9d9bdbdbd969696737373525252252525000000").map(Dv),I_=Hv(O_),U_=new Array(3).concat("efedf5bcbddc756bb1","f2f0f7cbc9e29e9ac86a51a3","f2f0f7cbc9e29e9ac8756bb154278f","f2f0f7dadaebbcbddc9e9ac8756bb154278f","f2f0f7dadaebbcbddc9e9ac8807dba6a51a34a1486","fcfbfdefedf5dadaebbcbddc9e9ac8807dba6a51a34a1486","fcfbfdefedf5dadaebbcbddc9e9ac8807dba6a51a354278f3f007d").map(Dv),B_=Hv(U_),Y_=new Array(3).concat("fee0d2fc9272de2d26","fee5d9fcae91fb6a4acb181d","fee5d9fcae91fb6a4ade2d26a50f15","fee5d9fcbba1fc9272fb6a4ade2d26a50f15","fee5d9fcbba1fc9272fb6a4aef3b2ccb181d99000d","fff5f0fee0d2fcbba1fc9272fb6a4aef3b2ccb181d99000d","fff5f0fee0d2fcbba1fc9272fb6a4aef3b2ccb181da50f1567000d").map(Dv),L_=Hv(Y_),j_=new Array(3).concat("fee6cefdae6be6550d","feeddefdbe85fd8d3cd94701","feeddefdbe85fd8d3ce6550da63603","feeddefdd0a2fdae6bfd8d3ce6550da63603","feeddefdd0a2fdae6bfd8d3cf16913d948018c2d04","fff5ebfee6cefdd0a2fdae6bfd8d3cf16913d948018c2d04","fff5ebfee6cefdd0a2fdae6bfd8d3cf16913d94801a636037f2704").map(Dv),H_=Hv(j_);var X_=Lr(tr(300,.5,0),tr(-240,.5,1)),G_=Lr(tr(-100,.75,.35),tr(80,1.5,.8)),V_=Lr(tr(260,.75,.35),tr(80,1.5,.8)),$_=tr();var W_=ve(),Z_=Math.PI/3,K_=2*Math.PI/3;function Q_(t){var n=t.length;return function(e){return t[Math.max(0,Math.min(n-1,Math.floor(e*n)))]}}var J_=Q_(Dv("44015444025645045745055946075a46085c460a5d460b5e470d60470e6147106347116447136548146748166848176948186a481a6c481b6d481c6e481d6f481f70482071482173482374482475482576482677482878482979472a7a472c7a472d7b472e7c472f7d46307e46327e46337f463480453581453781453882443983443a83443b84433d84433e85423f854240864241864142874144874045884046883f47883f48893e49893e4a893e4c8a3d4d8a3d4e8a3c4f8a3c508b3b518b3b528b3a538b3a548c39558c39568c38588c38598c375a8c375b8d365c8d365d8d355e8d355f8d34608d34618d33628d33638d32648e32658e31668e31678e31688e30698e306a8e2f6b8e2f6c8e2e6d8e2e6e8e2e6f8e2d708e2d718e2c718e2c728e2c738e2b748e2b758e2a768e2a778e2a788e29798e297a8e297b8e287c8e287d8e277e8e277f8e27808e26818e26828e26828e25838e25848e25858e24868e24878e23888e23898e238a8d228b8d228c8d228d8d218e8d218f8d21908d21918c20928c20928c20938c1f948c1f958b1f968b1f978b1f988b1f998a1f9a8a1e9b8a1e9c891e9d891f9e891f9f881fa0881fa1881fa1871fa28720a38620a48621a58521a68522a78522a88423a98324aa8325ab8225ac8226ad8127ad8128ae8029af7f2ab07f2cb17e2db27d2eb37c2fb47c31b57b32b67a34b67935b77937b87838b9773aba763bbb753dbc743fbc7340bd7242be7144bf7046c06f48c16e4ac16d4cc26c4ec36b50c46a52c56954c56856c66758c7655ac8645cc8635ec96260ca6063cb5f65cb5e67cc5c69cd5b6ccd5a6ece5870cf5773d05675d05477d1537ad1517cd2507fd34e81d34d84d44b86d54989d5488bd6468ed64590d74393d74195d84098d83e9bd93c9dd93ba0da39a2da37a5db36a8db34aadc32addc30b0dd2fb2dd2db5de2bb8de29bade28bddf26c0df25c2df23c5e021c8e020cae11fcde11dd0e11cd2e21bd5e21ad8e219dae319dde318dfe318e2e418e5e419e7e419eae51aece51befe51cf1e51df4e61ef6e620f8e621fbe723fde725")),tb=Q_(Dv("00000401000501010601010802010902020b02020d03030f03031204041405041606051806051a07061c08071e0907200a08220b09240c09260d0a290e0b2b100b2d110c2f120d31130d34140e36150e38160f3b180f3d19103f1a10421c10441d11471e114920114b21114e22115024125325125527125829115a2a115c2c115f2d11612f116331116533106734106936106b38106c390f6e3b0f703d0f713f0f72400f74420f75440f764510774710784910784a10794c117a4e117b4f127b51127c52137c54137d56147d57157e59157e5a167e5c167f5d177f5f187f601880621980641a80651a80671b80681c816a1c816b1d816d1d816e1e81701f81721f817320817521817621817822817922827b23827c23827e24828025828125818326818426818627818827818928818b29818c29818e2a81902a81912b81932b80942c80962c80982d80992d809b2e7f9c2e7f9e2f7fa02f7fa1307ea3307ea5317ea6317da8327daa337dab337cad347cae347bb0357bb2357bb3367ab5367ab73779b83779ba3878bc3978bd3977bf3a77c03a76c23b75c43c75c53c74c73d73c83e73ca3e72cc3f71cd4071cf4070d0416fd2426fd3436ed5446dd6456cd8456cd9466bdb476adc4869de4968df4a68e04c67e24d66e34e65e44f64e55064e75263e85362e95462ea5661eb5760ec5860ed5a5fee5b5eef5d5ef05f5ef1605df2625df2645cf3655cf4675cf4695cf56b5cf66c5cf66e5cf7705cf7725cf8745cf8765cf9785df9795df97b5dfa7d5efa7f5efa815ffb835ffb8560fb8761fc8961fc8a62fc8c63fc8e64fc9065fd9266fd9467fd9668fd9869fd9a6afd9b6bfe9d6cfe9f6dfea16efea36ffea571fea772fea973feaa74feac76feae77feb078feb27afeb47bfeb67cfeb77efeb97ffebb81febd82febf84fec185fec287fec488fec68afec88cfeca8dfecc8ffecd90fecf92fed194fed395fed597fed799fed89afdda9cfddc9efddea0fde0a1fde2a3fde3a5fde5a7fde7a9fde9aafdebacfcecaefceeb0fcf0b2fcf2b4fcf4b6fcf6b8fcf7b9fcf9bbfcfbbdfcfdbf")),nb=Q_(Dv("00000401000501010601010802010a02020c02020e03021004031204031405041706041907051b08051d09061f0a07220b07240c08260d08290e092b10092d110a30120a32140b34150b37160b39180c3c190c3e1b0c411c0c431e0c451f0c48210c4a230c4c240c4f260c51280b53290b552b0b572d0b592f0a5b310a5c320a5e340a5f3609613809623909633b09643d09653e0966400a67420a68440a68450a69470b6a490b6a4a0c6b4c0c6b4d0d6c4f0d6c510e6c520e6d540f6d550f6d57106e59106e5a116e5c126e5d126e5f136e61136e62146e64156e65156e67166e69166e6a176e6c186e6d186e6f196e71196e721a6e741a6e751b6e771c6d781c6d7a1d6d7c1d6d7d1e6d7f1e6c801f6c82206c84206b85216b87216b88226a8a226a8c23698d23698f24699025689225689326679526679727669827669a28659b29649d29649f2a63a02a63a22b62a32c61a52c60a62d60a82e5fa92e5eab2f5ead305dae305cb0315bb1325ab3325ab43359b63458b73557b93556ba3655bc3754bd3853bf3952c03a51c13a50c33b4fc43c4ec63d4dc73e4cc83f4bca404acb4149cc4248ce4347cf4446d04545d24644d34743d44842d54a41d74b3fd84c3ed94d3dda4e3cdb503bdd513ade5238df5337e05536e15635e25734e35933e45a31e55c30e65d2fe75e2ee8602de9612bea632aeb6429eb6628ec6726ed6925ee6a24ef6c23ef6e21f06f20f1711ff1731df2741cf3761bf37819f47918f57b17f57d15f67e14f68013f78212f78410f8850ff8870ef8890cf98b0bf98c0af98e09fa9008fa9207fa9407fb9606fb9706fb9906fb9b06fb9d07fc9f07fca108fca309fca50afca60cfca80dfcaa0ffcac11fcae12fcb014fcb216fcb418fbb61afbb81dfbba1ffbbc21fbbe23fac026fac228fac42afac62df9c72ff9c932f9cb35f8cd37f8cf3af7d13df7d340f6d543f6d746f5d949f5db4cf4dd4ff4df53f4e156f3e35af3e55df2e661f2e865f2ea69f1ec6df1ed71f1ef75f1f179f2f27df2f482f3f586f3f68af4f88ef5f992f6fa96f8fb9af9fc9dfafda1fcffa4")),eb=Q_(Dv("0d088710078813078916078a19068c1b068d1d068e20068f2206902406912605912805922a05932c05942e05952f059631059733059735049837049938049a3a049a3c049b3e049c3f049c41049d43039e44039e46039f48039f4903a04b03a14c02a14e02a25002a25102a35302a35502a45601a45801a45901a55b01a55c01a65e01a66001a66100a76300a76400a76600a76700a86900a86a00a86c00a86e00a86f00a87100a87201a87401a87501a87701a87801a87a02a87b02a87d03a87e03a88004a88104a78305a78405a78606a68707a68808a68a09a58b0aa58d0ba58e0ca48f0da4910ea3920fa39410a29511a19613a19814a099159f9a169f9c179e9d189d9e199da01a9ca11b9ba21d9aa31e9aa51f99a62098a72197a82296aa2395ab2494ac2694ad2793ae2892b02991b12a90b22b8fb32c8eb42e8db52f8cb6308bb7318ab83289ba3388bb3488bc3587bd3786be3885bf3984c03a83c13b82c23c81c33d80c43e7fc5407ec6417dc7427cc8437bc9447aca457acb4679cc4778cc4977cd4a76ce4b75cf4c74d04d73d14e72d24f71d35171d45270d5536fd5546ed6556dd7566cd8576bd9586ada5a6ada5b69db5c68dc5d67dd5e66de5f65de6164df6263e06363e16462e26561e26660e3685fe4695ee56a5de56b5de66c5ce76e5be76f5ae87059e97158e97257ea7457eb7556eb7655ec7754ed7953ed7a52ee7b51ef7c51ef7e50f07f4ff0804ef1814df1834cf2844bf3854bf3874af48849f48948f58b47f58c46f68d45f68f44f79044f79143f79342f89441f89540f9973ff9983ef99a3efa9b3dfa9c3cfa9e3bfb9f3afba139fba238fca338fca537fca636fca835fca934fdab33fdac33fdae32fdaf31fdb130fdb22ffdb42ffdb52efeb72dfeb82cfeba2cfebb2bfebd2afebe2afec029fdc229fdc328fdc527fdc627fdc827fdca26fdcb26fccd25fcce25fcd025fcd225fbd324fbd524fbd724fad824fada24f9dc24f9dd25f8df25f8e125f7e225f7e425f6e626f6e826f5e926f5eb27f4ed27f3ee27f3f027f2f227f1f426f1f525f0f724f0f921"));function rb(t){return function(){return t}}var ib=Math.abs,ob=Math.atan2,ab=Math.cos,ub=Math.max,cb=Math.min,fb=Math.sin,sb=Math.sqrt,lb=1e-12,hb=Math.PI,db=hb/2,pb=2*hb;function gb(t){return t>1?0:t<-1?hb:Math.acos(t)}function yb(t){return t>=1?db:t<=-1?-db:Math.asin(t)}function vb(t){return t.innerRadius}function _b(t){return t.outerRadius}function bb(t){return t.startAngle}function mb(t){return t.endAngle}function xb(t){return t&&t.padAngle}function wb(t,n,e,r,i,o,a,u){var c=e-t,f=r-n,s=a-i,l=u-o,h=l*c-s*f;if(!(h*hC*C+P*P&&(A=S,T=E),{cx:A,cy:T,x01:-s,y01:-l,x11:A*(i/x-1),y11:T*(i/x-1)}}var Ab=Array.prototype.slice;function Tb(t){return"object"==typeof t&&"length"in t?t:Array.from(t)}function Sb(t){this._context=t}function Eb(t){return new Sb(t)}function kb(t){return t[0]}function Nb(t){return t[1]}function Cb(t,n){var e=rb(!0),r=null,i=Eb,o=null;function a(a){var u,c,f,s=(a=Tb(a)).length,l=!1;for(null==r&&(o=i(f=fa())),u=0;u<=s;++u)!(u=s;--l)u.point(y[l],v[l]);u.lineEnd(),u.areaEnd()}g&&(y[f]=+t(h,f,c),v[f]=+n(h,f,c),u.point(r?+r(h,f,c):y[f],e?+e(h,f,c):v[f]))}if(d)return u=null,d+""||null}function f(){return Cb().defined(i).curve(a).context(o)}return t="function"==typeof t?t:void 0===t?kb:rb(+t),n="function"==typeof n?n:rb(void 0===n?0:+n),e="function"==typeof e?e:void 0===e?Nb:rb(+e),c.x=function(n){return arguments.length?(t="function"==typeof n?n:rb(+n),r=null,c):t},c.x0=function(n){return arguments.length?(t="function"==typeof n?n:rb(+n),c):t},c.x1=function(t){return arguments.length?(r=null==t?null:"function"==typeof t?t:rb(+t),c):r},c.y=function(t){return arguments.length?(n="function"==typeof t?t:rb(+t),e=null,c):n},c.y0=function(t){return arguments.length?(n="function"==typeof t?t:rb(+t),c):n},c.y1=function(t){return arguments.length?(e=null==t?null:"function"==typeof t?t:rb(+t),c):e},c.lineX0=c.lineY0=function(){return f().x(t).y(n)},c.lineY1=function(){return f().x(t).y(e)},c.lineX1=function(){return f().x(r).y(n)},c.defined=function(t){return arguments.length?(i="function"==typeof t?t:rb(!!t),c):i},c.curve=function(t){return arguments.length?(a=t,null!=o&&(u=a(o)),c):a},c.context=function(t){return arguments.length?(null==t?o=u=null:u=a(o=t),c):o},c}function zb(t,n){return nt?1:n>=t?0:NaN}function Db(t){return t}Sb.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._point=0},lineEnd:function(){(this._line||0!==this._line&&1===this._point)&&this._context.closePath(),this._line=1-this._line},point:function(t,n){switch(t=+t,n=+n,this._point){case 0:this._point=1,this._line?this._context.lineTo(t,n):this._context.moveTo(t,n);break;case 1:this._point=2;default:this._context.lineTo(t,n)}}};var qb=Fb(Eb);function Rb(t){this._curve=t}function Fb(t){function n(n){return new Rb(t(n))}return n._curve=t,n}function Ob(t){var n=t.curve;return t.angle=t.x,delete t.x,t.radius=t.y,delete t.y,t.curve=function(t){return arguments.length?n(Fb(t)):n()._curve},t}function Ib(){return Ob(Cb().curve(qb))}function Ub(){var t=Pb().curve(qb),n=t.curve,e=t.lineX0,r=t.lineX1,i=t.lineY0,o=t.lineY1;return t.angle=t.x,delete t.x,t.startAngle=t.x0,delete t.x0,t.endAngle=t.x1,delete t.x1,t.radius=t.y,delete t.y,t.innerRadius=t.y0,delete t.y0,t.outerRadius=t.y1,delete t.y1,t.lineStartAngle=function(){return Ob(e())},delete t.lineX0,t.lineEndAngle=function(){return Ob(r())},delete t.lineX1,t.lineInnerRadius=function(){return Ob(i())},delete t.lineY0,t.lineOuterRadius=function(){return Ob(o())},delete t.lineY1,t.curve=function(t){return arguments.length?n(Fb(t)):n()._curve},t}function Bb(t,n){return[(n=+n)*Math.cos(t-=Math.PI/2),n*Math.sin(t)]}function Yb(t){return t.source}function Lb(t){return t.target}function jb(t){var n=Yb,e=Lb,r=kb,i=Nb,o=null;function a(){var a,u=Ab.call(arguments),c=n.apply(this,u),f=e.apply(this,u);if(o||(o=a=fa()),t(o,+r.apply(this,(u[0]=c,u)),+i.apply(this,u),+r.apply(this,(u[0]=f,u)),+i.apply(this,u)),a)return o=null,a+""||null}return a.source=function(t){return arguments.length?(n=t,a):n},a.target=function(t){return arguments.length?(e=t,a):e},a.x=function(t){return arguments.length?(r="function"==typeof t?t:rb(+t),a):r},a.y=function(t){return arguments.length?(i="function"==typeof t?t:rb(+t),a):i},a.context=function(t){return arguments.length?(o=null==t?null:t,a):o},a}function Hb(t,n,e,r,i){t.moveTo(n,e),t.bezierCurveTo(n=(n+r)/2,e,n,i,r,i)}function Xb(t,n,e,r,i){t.moveTo(n,e),t.bezierCurveTo(n,e=(e+i)/2,r,e,r,i)}function Gb(t,n,e,r,i){var o=Bb(n,e),a=Bb(n,e=(e+i)/2),u=Bb(r,e),c=Bb(r,i);t.moveTo(o[0],o[1]),t.bezierCurveTo(a[0],a[1],u[0],u[1],c[0],c[1])}Rb.prototype={areaStart:function(){this._curve.areaStart()},areaEnd:function(){this._curve.areaEnd()},lineStart:function(){this._curve.lineStart()},lineEnd:function(){this._curve.lineEnd()},point:function(t,n){this._curve.point(n*Math.sin(t),n*-Math.cos(t))}};var Vb={draw:function(t,n){var e=Math.sqrt(n/hb);t.moveTo(e,0),t.arc(0,0,e,0,pb)}},$b={draw:function(t,n){var e=Math.sqrt(n/5)/2;t.moveTo(-3*e,-e),t.lineTo(-e,-e),t.lineTo(-e,-3*e),t.lineTo(e,-3*e),t.lineTo(e,-e),t.lineTo(3*e,-e),t.lineTo(3*e,e),t.lineTo(e,e),t.lineTo(e,3*e),t.lineTo(-e,3*e),t.lineTo(-e,e),t.lineTo(-3*e,e),t.closePath()}},Wb=Math.sqrt(1/3),Zb=2*Wb,Kb={draw:function(t,n){var e=Math.sqrt(n/Zb),r=e*Wb;t.moveTo(0,-e),t.lineTo(r,0),t.lineTo(0,e),t.lineTo(-r,0),t.closePath()}},Qb=Math.sin(hb/10)/Math.sin(7*hb/10),Jb=Math.sin(pb/10)*Qb,tm=-Math.cos(pb/10)*Qb,nm={draw:function(t,n){var e=Math.sqrt(.8908130915292852*n),r=Jb*e,i=tm*e;t.moveTo(0,-e),t.lineTo(r,i);for(var o=1;o<5;++o){var a=pb*o/5,u=Math.cos(a),c=Math.sin(a);t.lineTo(c*e,-u*e),t.lineTo(u*r-c*i,c*r+u*i)}t.closePath()}},em={draw:function(t,n){var e=Math.sqrt(n),r=-e/2;t.rect(r,r,e,e)}},rm=Math.sqrt(3),im={draw:function(t,n){var e=-Math.sqrt(n/(3*rm));t.moveTo(0,2*e),t.lineTo(-rm*e,-e),t.lineTo(rm*e,-e),t.closePath()}},om=-.5,am=Math.sqrt(3)/2,um=1/Math.sqrt(12),cm=3*(um/2+1),fm={draw:function(t,n){var e=Math.sqrt(n/cm),r=e/2,i=e*um,o=r,a=e*um+e,u=-o,c=a;t.moveTo(r,i),t.lineTo(o,a),t.lineTo(u,c),t.lineTo(om*r-am*i,am*r+om*i),t.lineTo(om*o-am*a,am*o+om*a),t.lineTo(om*u-am*c,am*u+om*c),t.lineTo(om*r+am*i,om*i-am*r),t.lineTo(om*o+am*a,om*a-am*o),t.lineTo(om*u+am*c,om*c-am*u),t.closePath()}},sm=[Vb,$b,Kb,em,nm,im,fm];function lm(){}function hm(t,n,e){t._context.bezierCurveTo((2*t._x0+t._x1)/3,(2*t._y0+t._y1)/3,(t._x0+2*t._x1)/3,(t._y0+2*t._y1)/3,(t._x0+4*t._x1+n)/6,(t._y0+4*t._y1+e)/6)}function dm(t){this._context=t}function pm(t){this._context=t}function gm(t){this._context=t}dm.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x0=this._x1=this._y0=this._y1=NaN,this._point=0},lineEnd:function(){switch(this._point){case 3:hm(this,this._x1,this._y1);case 2:this._context.lineTo(this._x1,this._y1)}(this._line||0!==this._line&&1===this._point)&&this._context.closePath(),this._line=1-this._line},point:function(t,n){switch(t=+t,n=+n,this._point){case 0:this._point=1,this._line?this._context.lineTo(t,n):this._context.moveTo(t,n);break;case 1:this._point=2;break;case 2:this._point=3,this._context.lineTo((5*this._x0+this._x1)/6,(5*this._y0+this._y1)/6);default:hm(this,t,n)}this._x0=this._x1,this._x1=t,this._y0=this._y1,this._y1=n}},pm.prototype={areaStart:lm,areaEnd:lm,lineStart:function(){this._x0=this._x1=this._x2=this._x3=this._x4=this._y0=this._y1=this._y2=this._y3=this._y4=NaN,this._point=0},lineEnd:function(){switch(this._point){case 1:this._context.moveTo(this._x2,this._y2),this._context.closePath();break;case 2:this._context.moveTo((this._x2+2*this._x3)/3,(this._y2+2*this._y3)/3),this._context.lineTo((this._x3+2*this._x2)/3,(this._y3+2*this._y2)/3),this._context.closePath();break;case 3:this.point(this._x2,this._y2),this.point(this._x3,this._y3),this.point(this._x4,this._y4)}},point:function(t,n){switch(t=+t,n=+n,this._point){case 0:this._point=1,this._x2=t,this._y2=n;break;case 1:this._point=2,this._x3=t,this._y3=n;break;case 2:this._point=3,this._x4=t,this._y4=n,this._context.moveTo((this._x0+4*this._x1+t)/6,(this._y0+4*this._y1+n)/6);break;default:hm(this,t,n)}this._x0=this._x1,this._x1=t,this._y0=this._y1,this._y1=n}},gm.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x0=this._x1=this._y0=this._y1=NaN,this._point=0},lineEnd:function(){(this._line||0!==this._line&&3===this._point)&&this._context.closePath(),this._line=1-this._line},point:function(t,n){switch(t=+t,n=+n,this._point){case 0:this._point=1;break;case 1:this._point=2;break;case 2:this._point=3;var e=(this._x0+4*this._x1+t)/6,r=(this._y0+4*this._y1+n)/6;this._line?this._context.lineTo(e,r):this._context.moveTo(e,r);break;case 3:this._point=4;default:hm(this,t,n)}this._x0=this._x1,this._x1=t,this._y0=this._y1,this._y1=n}};class ym{constructor(t,n){this._context=t,this._x=n}areaStart(){this._line=0}areaEnd(){this._line=NaN}lineStart(){this._point=0}lineEnd(){(this._line||0!==this._line&&1===this._point)&&this._context.closePath(),this._line=1-this._line}point(t,n){switch(t=+t,n=+n,this._point){case 0:this._point=1,this._line?this._context.lineTo(t,n):this._context.moveTo(t,n);break;case 1:this._point=2;default:this._x?this._context.bezierCurveTo(this._x0=(this._x0+t)/2,this._y0,this._x0,n,t,n):this._context.bezierCurveTo(this._x0,this._y0=(this._y0+n)/2,t,this._y0,t,n)}this._x0=t,this._y0=n}}function vm(t,n){this._basis=new dm(t),this._beta=n}vm.prototype={lineStart:function(){this._x=[],this._y=[],this._basis.lineStart()},lineEnd:function(){var t=this._x,n=this._y,e=t.length-1;if(e>0)for(var r,i=t[0],o=n[0],a=t[e]-i,u=n[e]-o,c=-1;++c<=e;)r=c/e,this._basis.point(this._beta*t[c]+(1-this._beta)*(i+r*a),this._beta*n[c]+(1-this._beta)*(o+r*u));this._x=this._y=null,this._basis.lineEnd()},point:function(t,n){this._x.push(+t),this._y.push(+n)}};var _m=function t(n){function e(t){return 1===n?new dm(t):new vm(t,n)}return e.beta=function(n){return t(+n)},e}(.85);function bm(t,n,e){t._context.bezierCurveTo(t._x1+t._k*(t._x2-t._x0),t._y1+t._k*(t._y2-t._y0),t._x2+t._k*(t._x1-n),t._y2+t._k*(t._y1-e),t._x2,t._y2)}function mm(t,n){this._context=t,this._k=(1-n)/6}mm.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x0=this._x1=this._x2=this._y0=this._y1=this._y2=NaN,this._point=0},lineEnd:function(){switch(this._point){case 2:this._context.lineTo(this._x2,this._y2);break;case 3:bm(this,this._x1,this._y1)}(this._line||0!==this._line&&1===this._point)&&this._context.closePath(),this._line=1-this._line},point:function(t,n){switch(t=+t,n=+n,this._point){case 0:this._point=1,this._line?this._context.lineTo(t,n):this._context.moveTo(t,n);break;case 1:this._point=2,this._x1=t,this._y1=n;break;case 2:this._point=3;default:bm(this,t,n)}this._x0=this._x1,this._x1=this._x2,this._x2=t,this._y0=this._y1,this._y1=this._y2,this._y2=n}};var xm=function t(n){function e(t){return new mm(t,n)}return e.tension=function(n){return t(+n)},e}(0);function wm(t,n){this._context=t,this._k=(1-n)/6}wm.prototype={areaStart:lm,areaEnd:lm,lineStart:function(){this._x0=this._x1=this._x2=this._x3=this._x4=this._x5=this._y0=this._y1=this._y2=this._y3=this._y4=this._y5=NaN,this._point=0},lineEnd:function(){switch(this._point){case 1:this._context.moveTo(this._x3,this._y3),this._context.closePath();break;case 2:this._context.lineTo(this._x3,this._y3),this._context.closePath();break;case 3:this.point(this._x3,this._y3),this.point(this._x4,this._y4),this.point(this._x5,this._y5)}},point:function(t,n){switch(t=+t,n=+n,this._point){case 0:this._point=1,this._x3=t,this._y3=n;break;case 1:this._point=2,this._context.moveTo(this._x4=t,this._y4=n);break;case 2:this._point=3,this._x5=t,this._y5=n;break;default:bm(this,t,n)}this._x0=this._x1,this._x1=this._x2,this._x2=t,this._y0=this._y1,this._y1=this._y2,this._y2=n}};var Mm=function t(n){function e(t){return new wm(t,n)}return e.tension=function(n){return t(+n)},e}(0);function Am(t,n){this._context=t,this._k=(1-n)/6}Am.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x0=this._x1=this._x2=this._y0=this._y1=this._y2=NaN,this._point=0},lineEnd:function(){(this._line||0!==this._line&&3===this._point)&&this._context.closePath(),this._line=1-this._line},point:function(t,n){switch(t=+t,n=+n,this._point){case 0:this._point=1;break;case 1:this._point=2;break;case 2:this._point=3,this._line?this._context.lineTo(this._x2,this._y2):this._context.moveTo(this._x2,this._y2);break;case 3:this._point=4;default:bm(this,t,n)}this._x0=this._x1,this._x1=this._x2,this._x2=t,this._y0=this._y1,this._y1=this._y2,this._y2=n}};var Tm=function t(n){function e(t){return new Am(t,n)}return e.tension=function(n){return t(+n)},e}(0);function Sm(t,n,e){var r=t._x1,i=t._y1,o=t._x2,a=t._y2;if(t._l01_a>lb){var u=2*t._l01_2a+3*t._l01_a*t._l12_a+t._l12_2a,c=3*t._l01_a*(t._l01_a+t._l12_a);r=(r*u-t._x0*t._l12_2a+t._x2*t._l01_2a)/c,i=(i*u-t._y0*t._l12_2a+t._y2*t._l01_2a)/c}if(t._l23_a>lb){var f=2*t._l23_2a+3*t._l23_a*t._l12_a+t._l12_2a,s=3*t._l23_a*(t._l23_a+t._l12_a);o=(o*f+t._x1*t._l23_2a-n*t._l12_2a)/s,a=(a*f+t._y1*t._l23_2a-e*t._l12_2a)/s}t._context.bezierCurveTo(r,i,o,a,t._x2,t._y2)}function Em(t,n){this._context=t,this._alpha=n}Em.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x0=this._x1=this._x2=this._y0=this._y1=this._y2=NaN,this._l01_a=this._l12_a=this._l23_a=this._l01_2a=this._l12_2a=this._l23_2a=this._point=0},lineEnd:function(){switch(this._point){case 2:this._context.lineTo(this._x2,this._y2);break;case 3:this.point(this._x2,this._y2)}(this._line||0!==this._line&&1===this._point)&&this._context.closePath(),this._line=1-this._line},point:function(t,n){if(t=+t,n=+n,this._point){var e=this._x2-t,r=this._y2-n;this._l23_a=Math.sqrt(this._l23_2a=Math.pow(e*e+r*r,this._alpha))}switch(this._point){case 0:this._point=1,this._line?this._context.lineTo(t,n):this._context.moveTo(t,n);break;case 1:this._point=2;break;case 2:this._point=3;default:Sm(this,t,n)}this._l01_a=this._l12_a,this._l12_a=this._l23_a,this._l01_2a=this._l12_2a,this._l12_2a=this._l23_2a,this._x0=this._x1,this._x1=this._x2,this._x2=t,this._y0=this._y1,this._y1=this._y2,this._y2=n}};var km=function t(n){function e(t){return n?new Em(t,n):new mm(t,0)}return e.alpha=function(n){return t(+n)},e}(.5);function Nm(t,n){this._context=t,this._alpha=n}Nm.prototype={areaStart:lm,areaEnd:lm,lineStart:function(){this._x0=this._x1=this._x2=this._x3=this._x4=this._x5=this._y0=this._y1=this._y2=this._y3=this._y4=this._y5=NaN,this._l01_a=this._l12_a=this._l23_a=this._l01_2a=this._l12_2a=this._l23_2a=this._point=0},lineEnd:function(){switch(this._point){case 1:this._context.moveTo(this._x3,this._y3),this._context.closePath();break;case 2:this._context.lineTo(this._x3,this._y3),this._context.closePath();break;case 3:this.point(this._x3,this._y3),this.point(this._x4,this._y4),this.point(this._x5,this._y5)}},point:function(t,n){if(t=+t,n=+n,this._point){var e=this._x2-t,r=this._y2-n;this._l23_a=Math.sqrt(this._l23_2a=Math.pow(e*e+r*r,this._alpha))}switch(this._point){case 0:this._point=1,this._x3=t,this._y3=n;break;case 1:this._point=2,this._context.moveTo(this._x4=t,this._y4=n);break;case 2:this._point=3,this._x5=t,this._y5=n;break;default:Sm(this,t,n)}this._l01_a=this._l12_a,this._l12_a=this._l23_a,this._l01_2a=this._l12_2a,this._l12_2a=this._l23_2a,this._x0=this._x1,this._x1=this._x2,this._x2=t,this._y0=this._y1,this._y1=this._y2,this._y2=n}};var Cm=function t(n){function e(t){return n?new Nm(t,n):new wm(t,0)}return e.alpha=function(n){return t(+n)},e}(.5);function Pm(t,n){this._context=t,this._alpha=n}Pm.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x0=this._x1=this._x2=this._y0=this._y1=this._y2=NaN,this._l01_a=this._l12_a=this._l23_a=this._l01_2a=this._l12_2a=this._l23_2a=this._point=0},lineEnd:function(){(this._line||0!==this._line&&3===this._point)&&this._context.closePath(),this._line=1-this._line},point:function(t,n){if(t=+t,n=+n,this._point){var e=this._x2-t,r=this._y2-n;this._l23_a=Math.sqrt(this._l23_2a=Math.pow(e*e+r*r,this._alpha))}switch(this._point){case 0:this._point=1;break;case 1:this._point=2;break;case 2:this._point=3,this._line?this._context.lineTo(this._x2,this._y2):this._context.moveTo(this._x2,this._y2);break;case 3:this._point=4;default:Sm(this,t,n)}this._l01_a=this._l12_a,this._l12_a=this._l23_a,this._l01_2a=this._l12_2a,this._l12_2a=this._l23_2a,this._x0=this._x1,this._x1=this._x2,this._x2=t,this._y0=this._y1,this._y1=this._y2,this._y2=n}};var zm=function t(n){function e(t){return n?new Pm(t,n):new Am(t,0)}return e.alpha=function(n){return t(+n)},e}(.5);function Dm(t){this._context=t}function qm(t){return t<0?-1:1}function Rm(t,n,e){var r=t._x1-t._x0,i=n-t._x1,o=(t._y1-t._y0)/(r||i<0&&-0),a=(e-t._y1)/(i||r<0&&-0),u=(o*i+a*r)/(r+i);return(qm(o)+qm(a))*Math.min(Math.abs(o),Math.abs(a),.5*Math.abs(u))||0}function Fm(t,n){var e=t._x1-t._x0;return e?(3*(t._y1-t._y0)/e-n)/2:n}function Om(t,n,e){var r=t._x0,i=t._y0,o=t._x1,a=t._y1,u=(o-r)/3;t._context.bezierCurveTo(r+u,i+u*n,o-u,a-u*e,o,a)}function Im(t){this._context=t}function Um(t){this._context=new Bm(t)}function Bm(t){this._context=t}function Ym(t){this._context=t}function Lm(t){var n,e,r=t.length-1,i=new Array(r),o=new Array(r),a=new Array(r);for(i[0]=0,o[0]=2,a[0]=t[0]+2*t[1],n=1;n=0;--n)i[n]=(a[n]-i[n+1])/o[n];for(o[r-1]=(t[r]+i[r-1])/2,n=0;n1)for(var e,r,i,o=1,a=t[n[0]],u=a.length;o=0;)e[n]=n;return e}function Gm(t,n){return t[n]}function Vm(t){const n=[];return n.key=t,n}function $m(t){var n=t.map(Wm);return Xm(t).sort((function(t,e){return n[t]-n[e]}))}function Wm(t){for(var n,e=-1,r=0,i=t.length,o=-1/0;++eo&&(o=n,r=e);return r}function Zm(t){var n=t.map(Km);return Xm(t).sort((function(t,e){return n[t]-n[e]}))}function Km(t){for(var n,e=0,r=-1,i=t.length;++r=0&&(this._t=1-this._t,this._line=1-this._line)},point:function(t,n){switch(t=+t,n=+n,this._point){case 0:this._point=1,this._line?this._context.lineTo(t,n):this._context.moveTo(t,n);break;case 1:this._point=2;default:if(this._t<=0)this._context.lineTo(this._x,n),this._context.lineTo(t,n);else{var e=this._x*(1-this._t)+t*this._t;this._context.lineTo(e,this._y),this._context.lineTo(e,n)}}this._x=t,this._y=n}};var Qm=t=>()=>t;function Jm(t,{sourceEvent:n,target:e,transform:r,dispatch:i}){Object.defineProperties(this,{type:{value:t,enumerable:!0,configurable:!0},sourceEvent:{value:n,enumerable:!0,configurable:!0},target:{value:e,enumerable:!0,configurable:!0},transform:{value:r,enumerable:!0,configurable:!0},_:{value:i}})}function tx(t,n,e){this.k=t,this.x=n,this.y=e}tx.prototype={constructor:tx,scale:function(t){return 1===t?this:new tx(this.k*t,this.x,this.y)},translate:function(t,n){return 0===t&0===n?this:new tx(this.k,this.x+this.k*t,this.y+this.k*n)},apply:function(t){return[t[0]*this.k+this.x,t[1]*this.k+this.y]},applyX:function(t){return t*this.k+this.x},applyY:function(t){return t*this.k+this.y},invert:function(t){return[(t[0]-this.x)/this.k,(t[1]-this.y)/this.k]},invertX:function(t){return(t-this.x)/this.k},invertY:function(t){return(t-this.y)/this.k},rescaleX:function(t){return t.copy().domain(t.range().map(this.invertX,this).map(t.invert,t))},rescaleY:function(t){return t.copy().domain(t.range().map(this.invertY,this).map(t.invert,t))},toString:function(){return"translate("+this.x+","+this.y+") scale("+this.k+")"}};var nx=new tx(1,0,0);function ex(t){for(;!t.__zoom;)if(!(t=t.parentNode))return nx;return t.__zoom}function rx(t){t.stopImmediatePropagation()}function ix(t){t.preventDefault(),t.stopImmediatePropagation()}function ox(t){return!(t.ctrlKey&&"wheel"!==t.type||t.button)}function ax(){var t=this;return t instanceof SVGElement?(t=t.ownerSVGElement||t).hasAttribute("viewBox")?[[(t=t.viewBox.baseVal).x,t.y],[t.x+t.width,t.y+t.height]]:[[0,0],[t.width.baseVal.value,t.height.baseVal.value]]:[[0,0],[t.clientWidth,t.clientHeight]]}function ux(){return this.__zoom||nx}function cx(t){return-t.deltaY*(1===t.deltaMode?.05:t.deltaMode?1:.002)*(t.ctrlKey?10:1)}function fx(){return navigator.maxTouchPoints||"ontouchstart"in this}function sx(t,n,e){var r=t.invertX(n[0][0])-e[0][0],i=t.invertX(n[1][0])-e[1][0],o=t.invertY(n[0][1])-e[0][1],a=t.invertY(n[1][1])-e[1][1];return t.translate(i>r?(r+i)/2:Math.min(0,r)||Math.max(0,i),a>o?(o+a)/2:Math.min(0,o)||Math.max(0,a))}ex.prototype=tx.prototype,t.Adder=g,t.Delaunay=nu,t.FormatSpecifier=uc,t.InternMap=y,t.InternSet=v,t.Voronoi=Wa,t.active=function(t,n){var e,r,i=t.__transition;if(i)for(r in n=null==n?null:n+"",i)if((e=i[r]).state>1&&e.name===n)return new ji([[t]],_o,n,+r);return null},t.arc=function(){var t=vb,n=_b,e=rb(0),r=null,i=bb,o=mb,a=xb,u=null;function c(){var c,f,s=+t.apply(this,arguments),l=+n.apply(this,arguments),h=i.apply(this,arguments)-db,d=o.apply(this,arguments)-db,p=ib(d-h),g=d>h;if(u||(u=c=fa()),llb)if(p>pb-lb)u.moveTo(l*ab(h),l*fb(h)),u.arc(0,0,l,h,d,!g),s>lb&&(u.moveTo(s*ab(d),s*fb(d)),u.arc(0,0,s,d,h,g));else{var y,v,_=h,b=d,m=h,x=d,w=p,M=p,A=a.apply(this,arguments)/2,T=A>lb&&(r?+r.apply(this,arguments):sb(s*s+l*l)),S=cb(ib(l-s)/2,+e.apply(this,arguments)),E=S,k=S;if(T>lb){var N=yb(T/s*fb(A)),C=yb(T/l*fb(A));(w-=2*N)>lb?(m+=N*=g?1:-1,x-=N):(w=0,m=x=(h+d)/2),(M-=2*C)>lb?(_+=C*=g?1:-1,b-=C):(M=0,_=b=(h+d)/2)}var P=l*ab(_),z=l*fb(_),D=s*ab(x),q=s*fb(x);if(S>lb){var R,F=l*ab(b),O=l*fb(b),I=s*ab(m),U=s*fb(m);if(plb?k>lb?(y=Mb(I,U,P,z,l,k,g),v=Mb(F,O,D,q,l,k,g),u.moveTo(y.cx+y.x01,y.cy+y.y01),klb&&w>lb?E>lb?(y=Mb(D,q,F,O,s,-E,g),v=Mb(P,z,I,U,s,-E,g),u.lineTo(y.cx+y.x01,y.cy+y.y01),E>a,f=i+2*u>>a,s=wa(20);function l(r){var i=new Float32Array(c*f),l=new Float32Array(c*f);r.forEach((function(r,o,s){var l=+t(r,o,s)+u>>a,h=+n(r,o,s)+u>>a,d=+e(r,o,s);l>=0&&l=0&&h>a),Ca({width:c,height:f,data:l},{width:c,height:f,data:i},o>>a),Na({width:c,height:f,data:i},{width:c,height:f,data:l},o>>a),Ca({width:c,height:f,data:l},{width:c,height:f,data:i},o>>a),Na({width:c,height:f,data:i},{width:c,height:f,data:l},o>>a),Ca({width:c,height:f,data:l},{width:c,height:f,data:i},o>>a);var d=s(i);if(!Array.isArray(d)){var p=B(i);d=F(0,p,d),(d=Z(0,Math.floor(p/d)*d,d)).shift()}return ka().thresholds(d).size([c,f])(i).map(h)}function h(t){return t.value*=Math.pow(2,-2*a),t.coordinates.forEach(d),t}function d(t){t.forEach(p)}function p(t){t.forEach(g)}function g(t){t[0]=t[0]*Math.pow(2,a)-u,t[1]=t[1]*Math.pow(2,a)-u}function y(){return c=r+2*(u=3*o)>>a,f=i+2*u>>a,l}return l.x=function(n){return arguments.length?(t="function"==typeof n?n:wa(+n),l):t},l.y=function(t){return arguments.length?(n="function"==typeof t?t:wa(+t),l):n},l.weight=function(t){return arguments.length?(e="function"==typeof t?t:wa(+t),l):e},l.size=function(t){if(!arguments.length)return[r,i];var n=+t[0],e=+t[1];if(!(n>=0&&e>=0))throw new Error("invalid size");return r=n,i=e,y()},l.cellSize=function(t){if(!arguments.length)return 1<=1))throw new Error("invalid cell size");return a=Math.floor(Math.log(t)/Math.LN2),y()},l.thresholds=function(t){return arguments.length?(s="function"==typeof t?t:Array.isArray(t)?wa(ma.call(t)):wa(t),l):s},l.bandwidth=function(t){if(!arguments.length)return Math.sqrt(o*(o+1));if(!((t=+t)>=0))throw new Error("invalid bandwidth");return o=Math.round((Math.sqrt(4*t*t+1)-1)/2),y()},l},t.contours=ka,t.count=c,t.create=function(t){return Dn(At(t).call(document.documentElement))},t.creator=At,t.cross=function(...t){const n="function"==typeof t[t.length-1]&&function(t){return n=>t(...n)}(t.pop()),e=(t=t.map(l)).map(f),r=t.length-1,i=new Array(r+1).fill(0),o=[];if(r<0||e.some(s))return o;for(;;){o.push(i.map(((n,e)=>t[e][n])));let a=r;for(;++i[a]===e[a];){if(0===a)return n?o.map(n):o;i[a--]=0}}},t.csv=Pu,t.csvFormat=hu,t.csvFormatBody=du,t.csvFormatRow=gu,t.csvFormatRows=pu,t.csvFormatValue=yu,t.csvParse=su,t.csvParseRows=lu,t.cubehelix=tr,t.cumsum=function(t,n){var e=0,r=0;return Float64Array.from(t,void 0===n?t=>e+=+t||0:i=>e+=+n(i,r++,t)||0)},t.curveBasis=function(t){return new dm(t)},t.curveBasisClosed=function(t){return new pm(t)},t.curveBasisOpen=function(t){return new gm(t)},t.curveBumpX=function(t){return new ym(t,!0)},t.curveBumpY=function(t){return new ym(t,!1)},t.curveBundle=_m,t.curveCardinal=xm,t.curveCardinalClosed=Mm,t.curveCardinalOpen=Tm,t.curveCatmullRom=km,t.curveCatmullRomClosed=Cm,t.curveCatmullRomOpen=zm,t.curveLinear=Eb,t.curveLinearClosed=function(t){return new Dm(t)},t.curveMonotoneX=function(t){return new Im(t)},t.curveMonotoneY=function(t){return new Um(t)},t.curveNatural=function(t){return new Ym(t)},t.curveStep=function(t){return new jm(t,.5)},t.curveStepAfter=function(t){return new jm(t,1)},t.curveStepBefore=function(t){return new jm(t,0)},t.descending=function(t,n){return nt?1:n>=t?0:NaN},t.deviation=d,t.difference=function(t,...n){t=new Set(t);for(const e of n)for(const n of e)t.delete(n);return t},t.disjoint=function(t,n){const e=n[Symbol.iterator](),r=new Set;for(const n of t){if(r.has(n))return!1;let t,i;for(;({value:t,done:i}=e.next())&&!i;){if(Object.is(n,t))return!1;r.add(t)}}return!0},t.dispatch=pt,t.drag=function(){var t,n,e,r,i=Xn,o=Gn,a=Vn,u=$n,c={},f=pt("start","drag","end"),s=0,l=0;function h(t){t.on("mousedown.drag",d).filter(u).on("touchstart.drag",y).on("touchmove.drag",v).on("touchend.drag touchcancel.drag",_).style("touch-action","none").style("-webkit-tap-highlight-color","rgba(0,0,0,0)")}function d(a,u){if(!r&&i.call(this,a,u)){var c=b(this,o.call(this,a,u),a,u,"mouse");c&&(Dn(a.view).on("mousemove.drag",p,!0).on("mouseup.drag",g,!0),Yn(a.view),Un(a),e=!1,t=a.clientX,n=a.clientY,c("start",a))}}function p(r){if(Bn(r),!e){var i=r.clientX-t,o=r.clientY-n;e=i*i+o*o>l}c.mouse("drag",r)}function g(t){Dn(t.view).on("mousemove.drag mouseup.drag",null),Ln(t.view,e),Bn(t),c.mouse("end",t)}function y(t,n){if(i.call(this,t,n)){var e,r,a=t.changedTouches,u=o.call(this,t,n),c=a.length;for(e=0;e+t,t.easePoly=Ki,t.easePolyIn=Wi,t.easePolyInOut=Ki,t.easePolyOut=Zi,t.easeQuad=Vi,t.easeQuadIn=function(t){return t*t},t.easeQuadInOut=Vi,t.easeQuadOut=function(t){return t*(2-t)},t.easeSin=to,t.easeSinIn=function(t){return 1==+t?1:1-Math.cos(t*Ji)},t.easeSinInOut=to,t.easeSinOut=function(t){return Math.sin(t*Ji)},t.every=function(t,n){if("function"!=typeof n)throw new TypeError("test is not a function");let e=-1;for(const r of t)if(!n(r,++e,t))return!1;return!0},t.extent=p,t.fcumsum=function(t,n){const e=new g;let r=-1;return Float64Array.from(t,void 0===n?t=>e.add(+t||0):i=>e.add(+n(i,++r,t)||0))},t.filter=function(t,n){if("function"!=typeof n)throw new TypeError("test is not a function");const e=[];let r=-1;for(const i of t)n(i,++r,t)&&e.push(i);return e},t.forceCenter=function(t,n){var e,r=1;function i(){var i,o,a=e.length,u=0,c=0;for(i=0;if+p||os+p||ac.index){var g=f-u.x-u.vx,y=s-u.y-u.vy,v=g*g+y*y;vt.r&&(t.r=t[n].r)}function c(){if(n){var r,i,o=n.length;for(e=new Array(o),r=0;r[u(t,n,r),t])));for(a=0,i=new Array(f);a=u)){(t.data!==n||t.next)&&(0===l&&(p+=(l=Vu(e))*l),0===h&&(p+=(h=Vu(e))*h),p(t=(1664525*t+1013904223)%Qu)/Qu}();function l(){h(),f.call("tick",n),e1?(null==e?u.delete(t):u.set(t,p(e)),n):u.get(t)},find:function(n,e,r){var i,o,a,u,c,f=0,s=t.length;for(null==r?r=1/0:r*=r,f=0;f1?(f.on(t,e),n):f.on(t)}}},t.forceX=function(t){var n,e,r,i=Gu(.1);function o(t){for(var i,o=0,a=n.length;o=.12&&i<.234&&r>=-.425&&r<-.214?u:i>=.166&&i<.234&&r>=-.214&&r<-.115?c:a).invert(t)},s.stream=function(e){return t&&n===e?t:(r=[a.stream(n=e),u.stream(e),c.stream(e)],i=r.length,t={point:function(t,n){for(var e=-1;++eKf(r[0],r[1])&&(r[1]=i[1]),Kf(i[0],r[1])>Kf(r[0],r[1])&&(r[0]=i[0])):o.push(r=i);for(a=-1/0,n=0,r=o[e=o.length-1];n<=e;r=i,++n)i=o[n],(u=Kf(r[1],i[0]))>a&&(a=u,nf=i[0],rf=r[1])}return lf=hf=null,nf===1/0||ef===1/0?[[NaN,NaN],[NaN,NaN]]:[[nf,ef],[rf,of]]},t.geoCentroid=function(t){Ef=kf=Nf=Cf=Pf=zf=Df=qf=0,Rf=new g,Ff=new g,Of=new g,Wc(t,ts);var n=+Rf,e=+Ff,r=+Of,i=Dc(n,e,r);return i2?t[2]+90:90]):[(t=e())[0],t[1],t[2]-90]},e([0,0,90]).scale(159.155)},t.geoTransverseMercatorRaw=Bh,t.gray=function(t,n){return new Fe(t,0,0,null==n?1:n)},t.greatest=function(t,e=n){let r,i=!1;if(1===e.length){let o;for(const a of t){const t=e(a);(i?n(t,o)>0:0===n(t,t))&&(r=a,o=t,i=!0)}}else for(const n of t)(i?e(n,r)>0:0===e(n,n))&&(r=n,i=!0);return r},t.greatestIndex=function(t,e=n){if(1===e.length)return G(t,e);let r,i=-1,o=-1;for(const n of t)++o,(i<0?0===e(n,n):e(n,r)>0)&&(r=n,i=o);return i},t.group=M,t.groupSort=function(t,e,r){return(1===e.length?k(A(t,e,r),(([t,e],[r,i])=>n(e,i)||n(t,r))):k(M(t,r),(([t,r],[i,o])=>e(r,o)||n(t,i)))).map((([t])=>t))},t.groups=function(t,...n){return S(t,Array.from,w,n)},t.hcl=Le,t.hierarchy=Xh,t.histogram=U,t.hsl=Ae,t.html=Fu,t.image=function(t,n){return new Promise((function(e,r){var i=new Image;for(var o in n)i[o]=n[o];i.onerror=r,i.onload=function(){e(i)},i.src=t}))},t.index=function(t,...n){return S(t,w,T,n)},t.indexes=function(t,...n){return S(t,Array.from,T,n)},t.interpolate=Mr,t.interpolateArray=function(t,n){return(gr(n)?pr:yr)(t,n)},t.interpolateBasis=rr,t.interpolateBasisClosed=ir,t.interpolateBlues=q_,t.interpolateBrBG=Gv,t.interpolateBuGn=s_,t.interpolateBuPu=h_,t.interpolateCividis=function(t){return t=Math.max(0,Math.min(1,t)),"rgb("+Math.max(0,Math.min(255,Math.round(-4.54-t*(35.34-t*(2381.73-t*(6402.7-t*(7024.72-2710.57*t)))))))+", "+Math.max(0,Math.min(255,Math.round(32.49+t*(170.73+t*(52.82-t*(131.46-t*(176.58-67.37*t)))))))+", "+Math.max(0,Math.min(255,Math.round(81.24+t*(442.36-t*(2482.43-t*(6167.24-t*(6614.94-2475.67*t)))))))+")"},t.interpolateCool=V_,t.interpolateCubehelix=Yr,t.interpolateCubehelixDefault=X_,t.interpolateCubehelixLong=Lr,t.interpolateDate=vr,t.interpolateDiscrete=function(t){var n=t.length;return function(e){return t[Math.max(0,Math.min(n-1,Math.floor(e*n)))]}},t.interpolateGnBu=p_,t.interpolateGreens=F_,t.interpolateGreys=I_,t.interpolateHcl=Ir,t.interpolateHclLong=Ur,t.interpolateHsl=Rr,t.interpolateHslLong=Fr,t.interpolateHue=function(t,n){var e=ur(+t,+n);return function(t){var n=e(t);return n-360*Math.floor(n/360)}},t.interpolateInferno=nb,t.interpolateLab=function(t,n){var e=fr((t=Re(t)).l,(n=Re(n)).l),r=fr(t.a,n.a),i=fr(t.b,n.b),o=fr(t.opacity,n.opacity);return function(n){return t.l=e(n),t.a=r(n),t.b=i(n),t.opacity=o(n),t+""}},t.interpolateMagma=tb,t.interpolateNumber=_r,t.interpolateNumberArray=pr,t.interpolateObject=br,t.interpolateOrRd=y_,t.interpolateOranges=H_,t.interpolatePRGn=$v,t.interpolatePiYG=Zv,t.interpolatePlasma=eb,t.interpolatePuBu=m_,t.interpolatePuBuGn=__,t.interpolatePuOr=Qv,t.interpolatePuRd=w_,t.interpolatePurples=B_,t.interpolateRainbow=function(t){(t<0||t>1)&&(t-=Math.floor(t));var n=Math.abs(t-.5);return $_.h=360*t-100,$_.s=1.5-1.5*n,$_.l=.8-.9*n,$_+""},t.interpolateRdBu=t_,t.interpolateRdGy=e_,t.interpolateRdPu=A_,t.interpolateRdYlBu=i_,t.interpolateRdYlGn=a_,t.interpolateReds=L_,t.interpolateRgb=sr,t.interpolateRgbBasis=hr,t.interpolateRgbBasisClosed=dr,t.interpolateRound=Ar,t.interpolateSinebow=function(t){var n;return t=(.5-t)*Math.PI,W_.r=255*(n=Math.sin(t))*n,W_.g=255*(n=Math.sin(t+Z_))*n,W_.b=255*(n=Math.sin(t+K_))*n,W_+""},t.interpolateSpectral=c_,t.interpolateString=wr,t.interpolateTransformCss=Cr,t.interpolateTransformSvg=Pr,t.interpolateTurbo=function(t){return t=Math.max(0,Math.min(1,t)),"rgb("+Math.max(0,Math.min(255,Math.round(34.61+t*(1172.33-t*(10793.56-t*(33300.12-t*(38394.49-14825.05*t)))))))+", "+Math.max(0,Math.min(255,Math.round(23.31+t*(557.33+t*(1225.33-t*(3574.96-t*(1073.77+707.56*t)))))))+", "+Math.max(0,Math.min(255,Math.round(27.2+t*(3211.1-t*(15327.97-t*(27814-t*(22569.18-6838.66*t)))))))+")"},t.interpolateViridis=J_,t.interpolateWarm=G_,t.interpolateYlGn=k_,t.interpolateYlGnBu=S_,t.interpolateYlOrBr=C_,t.interpolateYlOrRd=z_,t.interpolateZoom=Dr,t.interrupt=gi,t.intersection=function(t,...n){t=new Set(t),n=n.map(et);t:for(const e of t)for(const r of n)if(!r.has(e)){t.delete(e);continue t}return t},t.interval=function(t,n,e){var r=new ei,i=n;return null==n?(r.restart(t,n,e),r):(r._restart=r.restart,r.restart=function(t,n,e){n=+n,e=null==e?ti():+e,r._restart((function o(a){a+=i,r._restart(o,i+=n,e),t(a)}),n,e)},r.restart(t,n,e),r)},t.isoFormat=Mv,t.isoParse=Av,t.json=function(t,n){return fetch(t,n).then(Du)},t.lab=Re,t.lch=function(t,n,e,r){return 1===arguments.length?Ye(t):new je(e,n,t,null==r?1:r)},t.least=function(t,e=n){let r,i=!1;if(1===e.length){let o;for(const a of t){const t=e(a);(i?n(t,o)<0:0===n(t,t))&&(r=a,o=t,i=!0)}}else for(const n of t)(i?e(n,r)<0:0===e(n,n))&&(r=n,i=!0);return r},t.leastIndex=K,t.line=Cb,t.lineRadial=Ib,t.linkHorizontal=function(){return jb(Hb)},t.linkRadial=function(){var t=jb(Gb);return t.angle=t.x,delete t.x,t.radius=t.y,delete t.y,t},t.linkVertical=function(){return jb(Xb)},t.local=Rn,t.map=function(t,n){if("function"!=typeof t[Symbol.iterator])throw new TypeError("values is not iterable");if("function"!=typeof n)throw new TypeError("mapper is not a function");return Array.from(t,((e,r)=>n(e,r,t)))},t.matcher=Ct,t.max=B,t.maxIndex=G,t.mean=function(t,n){let e=0,r=0;if(void 0===n)for(let n of t)null!=n&&(n=+n)>=n&&(++e,r+=n);else{let i=-1;for(let o of t)null!=(o=n(o,++i,t))&&(o=+o)>=o&&(++e,r+=o)}if(e)return r/e},t.median=function(t,n){return H(t,.5,n)},t.merge=V,t.min=Y,t.minIndex=$,t.namespace=xt,t.namespaces=mt,t.nice=O,t.now=ti,t.pack=function(){var t=null,n=1,e=1,r=hd;function i(i){return i.x=n/2,i.y=e/2,t?i.eachBefore(gd(t)).eachAfter(yd(r,.5)).eachBefore(vd(1)):i.eachBefore(gd(pd)).eachAfter(yd(hd,1)).eachAfter(yd(r,i.r/Math.min(n,e))).eachBefore(vd(Math.min(n,e)/(2*i.r))),i}return i.radius=function(n){return arguments.length?(t=sd(n),i):t},i.size=function(t){return arguments.length?(n=+t[0],e=+t[1],i):[n,e]},i.padding=function(t){return arguments.length?(r="function"==typeof t?t:dd(+t),i):r},i},t.packEnclose=Kh,t.packSiblings=function(t){return fd(t),t},t.pairs=function(t,n=W){const e=[];let r,i=!1;for(const o of t)i&&e.push(n(r,o)),r=o,i=!0;return e},t.partition=function(){var t=1,n=1,e=0,r=!1;function i(i){var o=i.height+1;return i.x0=i.y0=e,i.x1=t,i.y1=n/o,i.eachBefore(function(t,n){return function(r){r.children&&bd(r,r.x0,t*(r.depth+1)/n,r.x1,t*(r.depth+2)/n);var i=r.x0,o=r.y0,a=r.x1-e,u=r.y1-e;a0&&(d+=l);for(null!=n?p.sort((function(t,e){return n(g[t],g[e])})):null!=e&&p.sort((function(t,n){return e(a[t],a[n])})),u=0,f=d?(v-h*b)/d:0;u0?l*f:0)+b,g[c]={data:a[c],index:u,value:l,startAngle:y,endAngle:s,padAngle:_};return g}return a.value=function(n){return arguments.length?(t="function"==typeof n?n:rb(+n),a):t},a.sortValues=function(t){return arguments.length?(n=t,e=null,a):n},a.sort=function(t){return arguments.length?(e=t,n=null,a):e},a.startAngle=function(t){return arguments.length?(r="function"==typeof t?t:rb(+t),a):r},a.endAngle=function(t){return arguments.length?(i="function"==typeof t?t:rb(+t),a):i},a.padAngle=function(t){return arguments.length?(o="function"==typeof t?t:rb(+t),a):o},a},t.piecewise=jr,t.pointRadial=Bb,t.pointer=In,t.pointers=function(t,n){return t.target&&(t=On(t),void 0===n&&(n=t.currentTarget),t=t.touches||[t]),Array.from(t,(t=>In(t,n)))},t.polygonArea=function(t){for(var n,e=-1,r=t.length,i=t[r-1],o=0;++eu!=f>u&&a<(c-e)*(u-r)/(f-r)+e&&(s=!s),c=e,f=r;return s},t.polygonHull=function(t){if((e=t.length)<3)return null;var n,e,r=new Array(e),i=new Array(e);for(n=0;n=0;--n)f.push(t[r[o[n]][2]]);for(n=+u;n(n=1664525*n+1013904223|0,ep*(n>>>0))},t.randomLogNormal=Ld,t.randomLogistic=tp,t.randomNormal=Yd,t.randomPareto=Gd,t.randomPoisson=np,t.randomUniform=Ud,t.randomWeibull=Qd,t.range=Z,t.reduce=function(t,n,e){if("function"!=typeof n)throw new TypeError("reducer is not a function");const r=t[Symbol.iterator]();let i,o,a=-1;if(arguments.length<3){if(({done:i,value:e}=r.next()),i)return;++a}for(;({done:i,value:o}=r.next()),!i;)e=n(e,o,++a,t);return e},t.reverse=function(t){if("function"!=typeof t[Symbol.iterator])throw new TypeError("values is not iterable");return Array.from(t).reverse()},t.rgb=ve,t.ribbon=function(){return ba()},t.ribbonArrow=function(){return ba(_a)},t.rollup=A,t.rollups=function(t,n,...e){return S(t,Array.from,n,e)},t.scaleBand=up,t.scaleDiverging=function t(){var n=bp(Pv()(lp));return n.copy=function(){return Nv(n,t())},ip.apply(n,arguments)},t.scaleDivergingLog=function t(){var n=Ep(Pv()).domain([.1,1,10]);return n.copy=function(){return Nv(n,t()).base(n.base())},ip.apply(n,arguments)},t.scaleDivergingPow=zv,t.scaleDivergingSqrt=function(){return zv.apply(null,arguments).exponent(.5)},t.scaleDivergingSymlog=function t(){var n=Cp(Pv());return n.copy=function(){return Nv(n,t()).constant(n.constant())},ip.apply(n,arguments)},t.scaleIdentity=function t(n){var e;function r(t){return null==t||isNaN(t=+t)?e:t}return r.invert=r,r.domain=r.range=function(t){return arguments.length?(n=Array.from(t,fp),r):n.slice()},r.unknown=function(t){return arguments.length?(e=t,r):e},r.copy=function(){return t(n).unknown(e)},n=arguments.length?Array.from(n,fp):[0,1],bp(r)},t.scaleImplicit=op,t.scaleLinear=function t(){var n=vp();return n.copy=function(){return gp(n,t())},rp.apply(n,arguments),bp(n)},t.scaleLog=function t(){var n=Ep(yp()).domain([1,10]);return n.copy=function(){return gp(n,t()).base(n.base())},rp.apply(n,arguments),n},t.scaleOrdinal=ap,t.scalePoint=function(){return cp(up.apply(null,arguments).paddingInner(1))},t.scalePow=Rp,t.scaleQuantile=function t(){var e,r=[],i=[],a=[];function u(){var t=0,n=Math.max(1,i.length);for(a=new Array(n-1);++t0?a[n-1]:r[0],n=i?[a[i-1],r]:[a[n-1],a[n]]},c.unknown=function(t){return arguments.length?(n=t,c):c},c.thresholds=function(){return a.slice()},c.copy=function(){return t().domain([e,r]).range(u).unknown(n)},rp.apply(bp(c),arguments)},t.scaleRadial=function t(){var n,e=vp(),r=[0,1],i=!1;function o(t){var r=Op(e(t));return isNaN(r)?n:i?Math.round(r):r}return o.invert=function(t){return e.invert(Fp(t))},o.domain=function(t){return arguments.length?(e.domain(t),o):e.domain()},o.range=function(t){return arguments.length?(e.range((r=Array.from(t,fp)).map(Fp)),o):r.slice()},o.rangeRound=function(t){return o.range(t).round(!0)},o.round=function(t){return arguments.length?(i=!!t,o):i},o.clamp=function(t){return arguments.length?(e.clamp(t),o):e.clamp()},o.unknown=function(t){return arguments.length?(n=t,o):n},o.copy=function(){return t(e.domain(),r).round(i).clamp(e.clamp()).unknown(n)},rp.apply(o,arguments),bp(o)},t.scaleSequential=function t(){var n=bp(kv()(lp));return n.copy=function(){return Nv(n,t())},ip.apply(n,arguments)},t.scaleSequentialLog=function t(){var n=Ep(kv()).domain([1,10]);return n.copy=function(){return Nv(n,t()).base(n.base())},ip.apply(n,arguments)},t.scaleSequentialPow=Cv,t.scaleSequentialQuantile=function t(){var e=[],r=lp;function i(t){if(null!=t&&!isNaN(t=+t))return r((o(e,t,1)-1)/(e.length-1))}return i.domain=function(t){if(!arguments.length)return e.slice();e=[];for(let n of t)null==n||isNaN(n=+n)||e.push(n);return e.sort(n),i},i.interpolator=function(t){return arguments.length?(r=t,i):r},i.range=function(){return e.map(((t,n)=>r(n/(e.length-1))))},i.quantiles=function(t){return Array.from({length:t+1},((n,r)=>H(e,r/t)))},i.copy=function(){return t(r).domain(e)},ip.apply(i,arguments)},t.scaleSequentialSqrt=function(){return Cv.apply(null,arguments).exponent(.5)},t.scaleSequentialSymlog=function t(){var n=Cp(kv());return n.copy=function(){return Nv(n,t()).constant(n.constant())},ip.apply(n,arguments)},t.scaleSqrt=function(){return Rp.apply(null,arguments).exponent(.5)},t.scaleSymlog=function t(){var n=Cp(yp());return n.copy=function(){return gp(n,t()).constant(n.constant())},rp.apply(n,arguments)},t.scaleThreshold=function t(){var n,e=[.5],r=[0,1],i=1;function a(t){return null!=t&&t<=t?r[o(e,t,0,i)]:n}return a.domain=function(t){return arguments.length?(e=Array.from(t),i=Math.min(e.length,r.length-1),a):e.slice()},a.range=function(t){return arguments.length?(r=Array.from(t),i=Math.min(e.length,r.length-1),a):r.slice()},a.invertExtent=function(t){var n=r.indexOf(t);return[e[n-1],e[n]]},a.unknown=function(t){return arguments.length?(n=t,a):n},a.copy=function(){return t().domain(e).range(r).unknown(n)},rp.apply(a,arguments)},t.scaleTime=function(){return rp.apply(Ev(Kg,Qg,xg,bg,og,eg,tg,Qp,Zp,t.timeFormat).domain([new Date(2e3,0,1),new Date(2e3,0,2)]),arguments)},t.scaleUtc=function(){return rp.apply(Ev(Wg,Zg,Gg,Hg,Cg,Eg,Tg,Mg,Zp,t.utcFormat).domain([Date.UTC(2e3,0,1),Date.UTC(2e3,0,2)]),arguments)},t.scan=function(t,n){const e=K(t,n);return e<0?void 0:e},t.schemeAccent=Rv,t.schemeBlues=D_,t.schemeBrBG=Xv,t.schemeBuGn=f_,t.schemeBuPu=l_,t.schemeCategory10=qv,t.schemeDark2=Fv,t.schemeGnBu=d_,t.schemeGreens=R_,t.schemeGreys=O_,t.schemeOrRd=g_,t.schemeOranges=j_,t.schemePRGn=Vv,t.schemePaired=Ov,t.schemePastel1=Iv,t.schemePastel2=Uv,t.schemePiYG=Wv,t.schemePuBu=b_,t.schemePuBuGn=v_,t.schemePuOr=Kv,t.schemePuRd=x_,t.schemePurples=U_,t.schemeRdBu=Jv,t.schemeRdGy=n_,t.schemeRdPu=M_,t.schemeRdYlBu=r_,t.schemeRdYlGn=o_,t.schemeReds=Y_,t.schemeSet1=Bv,t.schemeSet2=Yv,t.schemeSet3=Lv,t.schemeSpectral=u_,t.schemeTableau10=jv,t.schemeYlGn=E_,t.schemeYlGnBu=T_,t.schemeYlOrBr=N_,t.schemeYlOrRd=P_,t.select=Dn,t.selectAll=function(t){return"string"==typeof t?new Pn([document.querySelectorAll(t)],[document.documentElement]):new Pn([null==t?[]:Et(t)],Cn)},t.selection=zn,t.selector=St,t.selectorAll=Nt,t.shuffle=Q,t.shuffler=J,t.some=function(t,n){if("function"!=typeof n)throw new TypeError("test is not a function");let e=-1;for(const r of t)if(n(r,++e,t))return!0;return!1},t.sort=k,t.stack=function(){var t=rb([]),n=Xm,e=Hm,r=Gm;function i(i){var o,a,u=Array.from(t.apply(this,arguments),Vm),c=u.length,f=-1;for(const t of i)for(o=0,++f;o0)for(var e,r,i,o,a,u,c=0,f=t[n[0]].length;c0?(r[0]=o,r[1]=o+=i):i<0?(r[1]=a,r[0]=a+=i):(r[0]=0,r[1]=i)},t.stackOffsetExpand=function(t,n){if((r=t.length)>0){for(var e,r,i,o=0,a=t[0].length;o0){for(var e,r=0,i=t[n[0]],o=i.length;r0&&(r=(e=t[n[0]]).length)>0){for(var e,r,i,o=0,a=1;a0)throw new Error("cycle");return o}return e.id=function(n){return arguments.length?(t=ld(n),e):t},e.parentId=function(t){return arguments.length?(n=ld(t),e):n},e},t.style=Jt,t.subset=function(t,n){return rt(n,t)},t.sum=function(t,n){let e=0;if(void 0===n)for(let n of t)(n=+n)&&(e+=n);else{let r=-1;for(let i of t)(i=+n(i,++r,t))&&(e+=i)}return e},t.superset=rt,t.svg=Ou,t.symbol=function(t,n){var e=null;function r(){var r;if(e||(e=r=fa()),t.apply(this,arguments).draw(e,+n.apply(this,arguments)),r)return e=null,r+""||null}return t="function"==typeof t?t:rb(t||Vb),n="function"==typeof n?n:rb(void 0===n?64:+n),r.type=function(n){return arguments.length?(t="function"==typeof n?n:rb(n),r):t},r.size=function(t){return arguments.length?(n="function"==typeof t?t:rb(+t),r):n},r.context=function(t){return arguments.length?(e=null==t?null:t,r):e},r},t.symbolCircle=Vb,t.symbolCross=$b,t.symbolDiamond=Kb,t.symbolSquare=em,t.symbolStar=nm,t.symbolTriangle=im,t.symbolWye=fm,t.symbols=sm,t.text=Nu,t.thresholdFreedmanDiaconis=function(t,n,e){return Math.ceil((e-n)/(2*(H(t,.75)-H(t,.25))*Math.pow(c(t),-1/3)))},t.thresholdScott=function(t,n,e){return Math.ceil((e-n)/(3.5*d(t)*Math.pow(c(t),-1/3)))},t.thresholdSturges=I,t.tickFormat=_p,t.tickIncrement=R,t.tickStep=F,t.ticks=q,t.timeDay=eg,t.timeDays=rg,t.timeFormatDefaultLocale=xv,t.timeFormatLocale=ey,t.timeFriday=sg,t.timeFridays=vg,t.timeHour=tg,t.timeHours=ng,t.timeInterval=Bp,t.timeMillisecond=Yp,t.timeMilliseconds=Lp,t.timeMinute=Qp,t.timeMinutes=Jp,t.timeMonday=ag,t.timeMondays=dg,t.timeMonth=bg,t.timeMonths=mg,t.timeSaturday=lg,t.timeSaturdays=_g,t.timeSecond=Zp,t.timeSeconds=Kp,t.timeSunday=og,t.timeSundays=hg,t.timeThursday=fg,t.timeThursdays=yg,t.timeTickInterval=Qg,t.timeTicks=Kg,t.timeTuesday=ug,t.timeTuesdays=pg,t.timeWednesday=cg,t.timeWednesdays=gg,t.timeWeek=og,t.timeWeeks=hg,t.timeYear=xg,t.timeYears=wg,t.timeout=ci,t.timer=ri,t.timerFlush=ii,t.transition=Hi,t.transpose=tt,t.tree=function(){var t=Ad,n=1,e=1,r=null;function i(i){var c=function(t){for(var n,e,r,i,o,a=new Nd(t,0),u=[a];n=u.pop();)if(r=n._.children)for(n.children=new Array(o=r.length),i=o-1;i>=0;--i)u.push(e=n.children[i]=new Nd(r[i],i)),e.parent=n;return(a.parent=new Nd(null,0)).children=[a],a}(i);if(c.eachAfter(o),c.parent.m=-c.z,c.eachBefore(a),r)i.eachBefore(u);else{var f=i,s=i,l=i;i.eachBefore((function(t){t.xs.x&&(s=t),t.depth>l.depth&&(l=t)}));var h=f===s?1:t(f,s)/2,d=h-f.x,p=n/(s.x+h+d),g=e/(l.depth||1);i.eachBefore((function(t){t.x=(t.x+d)*p,t.y=t.depth*g}))}return i}function o(n){var e=n.children,r=n.parent.children,i=n.i?r[n.i-1]:null;if(e){!function(t){for(var n,e=0,r=0,i=t.children,o=i.length;--o>=0;)(n=i[o]).z+=e,n.m+=e,e+=n.s+(r+=n.c)}(n);var o=(e[0].z+e[e.length-1].z)/2;i?(n.z=i.z+t(n._,i._),n.m=n.z-o):n.z=o}else i&&(n.z=i.z+t(n._,i._));n.parent.A=function(n,e,r){if(e){for(var i,o=n,a=n,u=e,c=o.parent.children[0],f=o.m,s=a.m,l=u.m,h=c.m;u=Sd(u),o=Td(o),u&&o;)c=Td(c),(a=Sd(a)).a=n,(i=u.z+l-o.z-f+t(u._,o._))>0&&(Ed(kd(u,n,r),n,i),f+=i,s+=i),l+=u.m,f+=o.m,h+=c.m,s+=a.m;u&&!Sd(a)&&(a.t=u,a.m+=l-s),o&&!Td(c)&&(c.t=o,c.m+=f-h,r=n)}return r}(n,i,n.parent.A||r[0])}function a(t){t._.x=t.z+t.parent.m,t.m+=t.parent.m}function u(t){t.x*=n,t.y=t.depth*e}return i.separation=function(n){return arguments.length?(t=n,i):t},i.size=function(t){return arguments.length?(r=!1,n=+t[0],e=+t[1],i):r?null:[n,e]},i.nodeSize=function(t){return arguments.length?(r=!0,n=+t[0],e=+t[1],i):r?[n,e]:null},i},t.treemap=function(){var t=Dd,n=!1,e=1,r=1,i=[0],o=hd,a=hd,u=hd,c=hd,f=hd;function s(t){return t.x0=t.y0=0,t.x1=e,t.y1=r,t.eachBefore(l),i=[0],n&&t.eachBefore(_d),t}function l(n){var e=i[n.depth],r=n.x0+e,s=n.y0+e,l=n.x1-e,h=n.y1-e;l=e-1){var s=u[n];return s.x0=i,s.y0=o,s.x1=a,void(s.y1=c)}var l=f[n],h=r/2+l,d=n+1,p=e-1;for(;d>>1;f[g]c-o){var _=r?(i*v+a*y)/r:a;t(n,d,y,i,o,_,c),t(d,e,v,_,o,a,c)}else{var b=r?(o*v+c*y)/r:c;t(n,d,y,i,o,a,b),t(d,e,v,i,b,a,c)}}(0,c,t.value,n,e,r,i)},t.treemapDice=bd,t.treemapResquarify=qd,t.treemapSlice=Cd,t.treemapSliceDice=function(t,n,e,r,i){(1&t.depth?Cd:bd)(t,n,e,r,i)},t.treemapSquarify=Dd,t.tsv=zu,t.tsvFormat=mu,t.tsvFormatBody=xu,t.tsvFormatRow=Mu,t.tsvFormatRows=wu,t.tsvFormatValue=Au,t.tsvParse=_u,t.tsvParseRows=bu,t.union=function(...t){const n=new Set;for(const e of t)for(const t of e)n.add(t);return n},t.utcDay=Eg,t.utcDays=kg,t.utcFriday=Rg,t.utcFridays=Lg,t.utcHour=Tg,t.utcHours=Sg,t.utcMillisecond=Yp,t.utcMilliseconds=Lp,t.utcMinute=Mg,t.utcMinutes=Ag,t.utcMonday=Pg,t.utcMondays=Ig,t.utcMonth=Hg,t.utcMonths=Xg,t.utcSaturday=Fg,t.utcSaturdays=jg,t.utcSecond=Zp,t.utcSeconds=Kp,t.utcSunday=Cg,t.utcSundays=Og,t.utcThursday=qg,t.utcThursdays=Yg,t.utcTickInterval=Zg,t.utcTicks=Wg,t.utcTuesday=zg,t.utcTuesdays=Ug,t.utcWednesday=Dg,t.utcWednesdays=Bg,t.utcWeek=Cg,t.utcWeeks=Og,t.utcYear=Gg,t.utcYears=Vg,t.variance=h,t.version="6.7.0",t.window=Wt,t.xml=Ru,t.zip=function(){return tt(arguments)},t.zoom=function(){var t,n,e,r=ox,i=ax,o=sx,a=cx,u=fx,c=[0,1/0],f=[[-1/0,-1/0],[1/0,1/0]],s=250,l=Dr,h=pt("start","zoom","end"),d=500,p=0,g=10;function y(t){t.property("__zoom",ux).on("wheel.zoom",M).on("mousedown.zoom",A).on("dblclick.zoom",T).filter(u).on("touchstart.zoom",S).on("touchmove.zoom",E).on("touchend.zoom touchcancel.zoom",k).style("-webkit-tap-highlight-color","rgba(0,0,0,0)")}function v(t,n){return(n=Math.max(c[0],Math.min(c[1],n)))===t.k?t:new tx(n,t.x,t.y)}function _(t,n,e){var r=n[0]-e[0]*t.k,i=n[1]-e[1]*t.k;return r===t.x&&i===t.y?t:new tx(t.k,r,i)}function b(t){return[(+t[0][0]+ +t[1][0])/2,(+t[0][1]+ +t[1][1])/2]}function m(t,n,e,r){t.on("start.zoom",(function(){x(this,arguments).event(r).start()})).on("interrupt.zoom end.zoom",(function(){x(this,arguments).event(r).end()})).tween("zoom",(function(){var t=this,o=arguments,a=x(t,o).event(r),u=i.apply(t,o),c=null==e?b(u):"function"==typeof e?e.apply(t,o):e,f=Math.max(u[1][0]-u[0][0],u[1][1]-u[0][1]),s=t.__zoom,h="function"==typeof n?n.apply(t,o):n,d=l(s.invert(c).concat(f/s.k),h.invert(c).concat(f/h.k));return function(t){if(1===t)t=h;else{var n=d(t),e=f/n[2];t=new tx(e,c[0]-n[0]*e,c[1]-n[1]*e)}a.zoom(null,t)}}))}function x(t,n,e){return!e&&t.__zooming||new w(t,n)}function w(t,n){this.that=t,this.args=n,this.active=0,this.sourceEvent=null,this.extent=i.apply(t,n),this.taps=0}function M(t,...n){if(r.apply(this,arguments)){var e=x(this,n).event(t),i=this.__zoom,u=Math.max(c[0],Math.min(c[1],i.k*Math.pow(2,a.apply(this,arguments)))),s=In(t);if(e.wheel)e.mouse[0][0]===s[0]&&e.mouse[0][1]===s[1]||(e.mouse[1]=i.invert(e.mouse[0]=s)),clearTimeout(e.wheel);else{if(i.k===u)return;e.mouse=[s,i.invert(s)],gi(this),e.start()}ix(t),e.wheel=setTimeout(l,150),e.zoom("mouse",o(_(v(i,u),e.mouse[0],e.mouse[1]),e.extent,f))}function l(){e.wheel=null,e.end()}}function A(t,...n){if(!e&&r.apply(this,arguments)){var i=x(this,n,!0).event(t),a=Dn(t.view).on("mousemove.zoom",h,!0).on("mouseup.zoom",d,!0),u=In(t,c),c=t.currentTarget,s=t.clientX,l=t.clientY;Yn(t.view),rx(t),i.mouse=[u,this.__zoom.invert(u)],gi(this),i.start()}function h(t){if(ix(t),!i.moved){var n=t.clientX-s,e=t.clientY-l;i.moved=n*n+e*e>p}i.event(t).zoom("mouse",o(_(i.that.__zoom,i.mouse[0]=In(t,c),i.mouse[1]),i.extent,f))}function d(t){a.on("mousemove.zoom mouseup.zoom",null),Ln(t.view,i.moved),ix(t),i.event(t).end()}}function T(t,...n){if(r.apply(this,arguments)){var e=this.__zoom,a=In(t.changedTouches?t.changedTouches[0]:t,this),u=e.invert(a),c=e.k*(t.shiftKey?.5:2),l=o(_(v(e,c),a,u),i.apply(this,n),f);ix(t),s>0?Dn(this).transition().duration(s).call(m,l,a,t):Dn(this).call(y.transform,l,a,t)}}function S(e,...i){if(r.apply(this,arguments)){var o,a,u,c,f=e.touches,s=f.length,l=x(this,i,e.changedTouches.length===s).event(e);for(rx(e),a=0;a 0: + center = self.smooth_center(self.prev_center, curr_center, bbox_smooth_alpha) + else: + center = curr_center + + # Update prev_center for the next frame + self.prev_center = center + + # Create bounding box using max_bbox_width and max_bbox_height + half_box_width = round(self.max_bbox_width / 2) + half_box_height = round(self.max_bbox_height / 2) + min_x = max(0, center[0] - half_box_width) + max_x = min(img.shape[1], center[0] + half_box_width) + min_y = max(0, center[1] - half_box_height) + max_y = min(img.shape[0], center[1] + half_box_height) + + # Append bounding box coordinates + bounding_boxes.append((min_x, min_y, max_x - min_x, max_y - min_y)) + + # Crop the image from the bounding box + cropped_img = img[min_y:max_y, min_x:max_x, :] + + # Calculate the new dimensions while maintaining the aspect ratio + new_height = min(cropped_img.shape[0], self.max_bbox_height) + new_width = round(new_height * bbox_aspect_ratio) + + # Resize the image + resize_transform = Resize((new_height, new_width)) + resized_img = resize_transform(cropped_img.permute(2, 0, 1)) + + # Perform the center crop to the desired size + crop_transform = CenterCrop((self.max_bbox_height, self.max_bbox_width)) # swap the order here if necessary + cropped_resized_img = crop_transform(resized_img) + + cropped_images.append(cropped_resized_img.permute(1, 2, 0)) + + cropped_out = torch.stack(cropped_images, dim=0) + + return (original_images, cropped_out, bounding_boxes, self.max_bbox_width, self.max_bbox_height, ) + + +def bbox_to_region(bbox, target_size=None): + bbox = bbox_check(bbox, target_size) + return (bbox[0], bbox[1], bbox[0] + bbox[2], bbox[1] + bbox[3]) + +def bbox_check(bbox, target_size=None): + if not target_size: + return bbox + + new_bbox = ( + bbox[0], + bbox[1], + min(target_size[0] - bbox[0], bbox[2]), + min(target_size[1] - bbox[1], bbox[3]), + ) + return new_bbox + +class BatchUncrop: + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "original_images": ("IMAGE",), + "cropped_images": ("IMAGE",), + "bboxes": ("BBOX",), + "border_blending": ("FLOAT", {"default": 0.25, "min": 0.0, "max": 1.0, "step": 0.01}, ), + "crop_rescale": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01}), + "border_top": ("BOOLEAN", {"default": True}), + "border_bottom": ("BOOLEAN", {"default": True}), + "border_left": ("BOOLEAN", {"default": True}), + "border_right": ("BOOLEAN", {"default": True}), + } + } + + RETURN_TYPES = ("IMAGE",) + FUNCTION = "uncrop" + + CATEGORY = "KJNodes/masking" + + def uncrop(self, original_images, cropped_images, bboxes, border_blending, crop_rescale, border_top, border_bottom, border_left, border_right): + def inset_border(image, border_width, border_color, border_top, border_bottom, border_left, border_right): + draw = ImageDraw.Draw(image) + width, height = image.size + if border_top: + draw.rectangle((0, 0, width, border_width), fill=border_color) + if border_bottom: + draw.rectangle((0, height - border_width, width, height), fill=border_color) + if border_left: + draw.rectangle((0, 0, border_width, height), fill=border_color) + if border_right: + draw.rectangle((width - border_width, 0, width, height), fill=border_color) + return image + + if len(original_images) != len(cropped_images): + raise ValueError(f"The number of original_images ({len(original_images)}) and cropped_images ({len(cropped_images)}) should be the same") + + # Ensure there are enough bboxes, but drop the excess if there are more bboxes than images + if len(bboxes) > len(original_images): + print(f"Warning: Dropping excess bounding boxes. Expected {len(original_images)}, but got {len(bboxes)}") + bboxes = bboxes[:len(original_images)] + elif len(bboxes) < len(original_images): + raise ValueError("There should be at least as many bboxes as there are original and cropped images") + + input_images = tensor2pil(original_images) + crop_imgs = tensor2pil(cropped_images) + + out_images = [] + for i in range(len(input_images)): + img = input_images[i] + crop = crop_imgs[i] + bbox = bboxes[i] + + # uncrop the image based on the bounding box + bb_x, bb_y, bb_width, bb_height = bbox + + paste_region = bbox_to_region((bb_x, bb_y, bb_width, bb_height), img.size) + + # scale factors + scale_x = crop_rescale + scale_y = crop_rescale + + # scaled paste_region + paste_region = (round(paste_region[0]*scale_x), round(paste_region[1]*scale_y), round(paste_region[2]*scale_x), round(paste_region[3]*scale_y)) + + # rescale the crop image to fit the paste_region + crop = crop.resize((round(paste_region[2]-paste_region[0]), round(paste_region[3]-paste_region[1]))) + crop_img = crop.convert("RGB") + + if border_blending > 1.0: + border_blending = 1.0 + elif border_blending < 0.0: + border_blending = 0.0 + + blend_ratio = (max(crop_img.size) / 2) * float(border_blending) + + blend = img.convert("RGBA") + mask = Image.new("L", img.size, 0) + + mask_block = Image.new("L", (paste_region[2]-paste_region[0], paste_region[3]-paste_region[1]), 255) + mask_block = inset_border(mask_block, round(blend_ratio / 2), (0), border_top, border_bottom, border_left, border_right) + + mask.paste(mask_block, paste_region) + blend.paste(crop_img, paste_region) + + mask = mask.filter(ImageFilter.BoxBlur(radius=blend_ratio / 4)) + mask = mask.filter(ImageFilter.GaussianBlur(radius=blend_ratio / 4)) + + blend.putalpha(mask) + img = Image.alpha_composite(img.convert("RGBA"), blend) + out_images.append(img.convert("RGB")) + + return (pil2tensor(out_images),) + +class BatchCropFromMaskAdvanced: + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "original_images": ("IMAGE",), + "masks": ("MASK",), + "crop_size_mult": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01}), + "bbox_smooth_alpha": ("FLOAT", {"default": 0.5, "min": 0.0, "max": 1.0, "step": 0.01}), + }, + } + + RETURN_TYPES = ( + "IMAGE", + "IMAGE", + "MASK", + "IMAGE", + "MASK", + "BBOX", + "BBOX", + "INT", + "INT", + ) + RETURN_NAMES = ( + "original_images", + "cropped_images", + "cropped_masks", + "combined_crop_image", + "combined_crop_masks", + "bboxes", + "combined_bounding_box", + "bbox_width", + "bbox_height", + ) + FUNCTION = "crop" + CATEGORY = "KJNodes/masking" + + def smooth_bbox_size(self, prev_bbox_size, curr_bbox_size, alpha): + return round(alpha * curr_bbox_size + (1 - alpha) * prev_bbox_size) + + def smooth_center(self, prev_center, curr_center, alpha=0.5): + return (round(alpha * curr_center[0] + (1 - alpha) * prev_center[0]), + round(alpha * curr_center[1] + (1 - alpha) * prev_center[1])) + + def crop(self, masks, original_images, crop_size_mult, bbox_smooth_alpha): + bounding_boxes = [] + combined_bounding_box = [] + cropped_images = [] + cropped_masks = [] + cropped_masks_out = [] + combined_crop_out = [] + combined_cropped_images = [] + combined_cropped_masks = [] + + def calculate_bbox(mask): + non_zero_indices = np.nonzero(np.array(mask)) + + # handle empty masks + min_x, max_x, min_y, max_y = 0, 0, 0, 0 + if len(non_zero_indices[1]) > 0 and len(non_zero_indices[0]) > 0: + min_x, max_x = np.min(non_zero_indices[1]), np.max(non_zero_indices[1]) + min_y, max_y = np.min(non_zero_indices[0]), np.max(non_zero_indices[0]) + + width = max_x - min_x + height = max_y - min_y + bbox_size = max(width, height) + return min_x, max_x, min_y, max_y, bbox_size + + combined_mask = torch.max(masks, dim=0)[0] + _mask = tensor2pil(combined_mask)[0] + new_min_x, new_max_x, new_min_y, new_max_y, combined_bbox_size = calculate_bbox(_mask) + center_x = (new_min_x + new_max_x) / 2 + center_y = (new_min_y + new_max_y) / 2 + half_box_size = round(combined_bbox_size // 2) + new_min_x = max(0, round(center_x - half_box_size)) + new_max_x = min(original_images[0].shape[1], round(center_x + half_box_size)) + new_min_y = max(0, round(center_y - half_box_size)) + new_max_y = min(original_images[0].shape[0], round(center_y + half_box_size)) + + combined_bounding_box.append((new_min_x, new_min_y, new_max_x - new_min_x, new_max_y - new_min_y)) + + self.max_bbox_size = 0 + + # First, calculate the maximum bounding box size across all masks + curr_max_bbox_size = max(calculate_bbox(tensor2pil(mask)[0])[-1] for mask in masks) + # Smooth the changes in the bounding box size + self.max_bbox_size = self.smooth_bbox_size(self.max_bbox_size, curr_max_bbox_size, bbox_smooth_alpha) + # Apply the crop size multiplier + self.max_bbox_size = round(self.max_bbox_size * crop_size_mult) + # Make sure max_bbox_size is divisible by 16, if not, round it upwards so it is + self.max_bbox_size = math.ceil(self.max_bbox_size / 16) * 16 + + if self.max_bbox_size > original_images[0].shape[0] or self.max_bbox_size > original_images[0].shape[1]: + # max_bbox_size can only be as big as our input's width or height, and it has to be even + self.max_bbox_size = math.floor(min(original_images[0].shape[0], original_images[0].shape[1]) / 2) * 2 + + # Then, for each mask and corresponding image... + for i, (mask, img) in enumerate(zip(masks, original_images)): + _mask = tensor2pil(mask)[0] + non_zero_indices = np.nonzero(np.array(_mask)) + + # check for empty masks + if len(non_zero_indices[0]) > 0 and len(non_zero_indices[1]) > 0: + min_x, max_x = np.min(non_zero_indices[1]), np.max(non_zero_indices[1]) + min_y, max_y = np.min(non_zero_indices[0]), np.max(non_zero_indices[0]) + + # Calculate center of bounding box + center_x = np.mean(non_zero_indices[1]) + center_y = np.mean(non_zero_indices[0]) + curr_center = (round(center_x), round(center_y)) + + # If this is the first frame, initialize prev_center with curr_center + if not hasattr(self, 'prev_center'): + self.prev_center = curr_center + + # Smooth the changes in the center coordinates from the second frame onwards + if i > 0: + center = self.smooth_center(self.prev_center, curr_center, bbox_smooth_alpha) + else: + center = curr_center + + # Update prev_center for the next frame + self.prev_center = center + + # Create bounding box using max_bbox_size + half_box_size = self.max_bbox_size // 2 + min_x = max(0, center[0] - half_box_size) + max_x = min(img.shape[1], center[0] + half_box_size) + min_y = max(0, center[1] - half_box_size) + max_y = min(img.shape[0], center[1] + half_box_size) + + # Append bounding box coordinates + bounding_boxes.append((min_x, min_y, max_x - min_x, max_y - min_y)) + + # Crop the image from the bounding box + cropped_img = img[min_y:max_y, min_x:max_x, :] + cropped_mask = mask[min_y:max_y, min_x:max_x] + + # Resize the cropped image to a fixed size + new_size = max(cropped_img.shape[0], cropped_img.shape[1]) + resize_transform = Resize(new_size, interpolation=InterpolationMode.NEAREST, max_size=max(img.shape[0], img.shape[1])) + resized_mask = resize_transform(cropped_mask.unsqueeze(0).unsqueeze(0)).squeeze(0).squeeze(0) + resized_img = resize_transform(cropped_img.permute(2, 0, 1)) + # Perform the center crop to the desired size + # Constrain the crop to the smaller of our bbox or our image so we don't expand past the image dimensions. + crop_transform = CenterCrop((min(self.max_bbox_size, resized_img.shape[1]), min(self.max_bbox_size, resized_img.shape[2]))) + + cropped_resized_img = crop_transform(resized_img) + cropped_images.append(cropped_resized_img.permute(1, 2, 0)) + + cropped_resized_mask = crop_transform(resized_mask) + cropped_masks.append(cropped_resized_mask) + + combined_cropped_img = original_images[i][new_min_y:new_max_y, new_min_x:new_max_x, :] + combined_cropped_images.append(combined_cropped_img) + + combined_cropped_mask = masks[i][new_min_y:new_max_y, new_min_x:new_max_x] + combined_cropped_masks.append(combined_cropped_mask) + else: + bounding_boxes.append((0, 0, img.shape[1], img.shape[0])) + cropped_images.append(img) + cropped_masks.append(mask) + combined_cropped_images.append(img) + combined_cropped_masks.append(mask) + + cropped_out = torch.stack(cropped_images, dim=0) + combined_crop_out = torch.stack(combined_cropped_images, dim=0) + cropped_masks_out = torch.stack(cropped_masks, dim=0) + combined_crop_mask_out = torch.stack(combined_cropped_masks, dim=0) + + return (original_images, cropped_out, cropped_masks_out, combined_crop_out, combined_crop_mask_out, bounding_boxes, combined_bounding_box, self.max_bbox_size, self.max_bbox_size) + +class FilterZeroMasksAndCorrespondingImages: + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "masks": ("MASK",), + }, + "optional": { + "original_images": ("IMAGE",), + }, + } + + RETURN_TYPES = ("MASK", "IMAGE", "IMAGE", "INDEXES",) + RETURN_NAMES = ("non_zero_masks_out", "non_zero_mask_images_out", "zero_mask_images_out", "zero_mask_images_out_indexes",) + FUNCTION = "filter" + CATEGORY = "KJNodes/masking" + DESCRIPTION = """ +Filter out all the empty (i.e. all zero) mask in masks +Also filter out all the corresponding images in original_images by indexes if provide + +original_images (optional): If provided, need have same length as masks. +""" + + def filter(self, masks, original_images=None): + non_zero_masks = [] + non_zero_mask_images = [] + zero_mask_images = [] + zero_mask_images_indexes = [] + + masks_num = len(masks) + also_process_images = False + if original_images is not None: + imgs_num = len(original_images) + if len(original_images) == masks_num: + also_process_images = True + else: + print(f"[WARNING] ignore input: original_images, due to number of original_images ({imgs_num}) is not equal to number of masks ({masks_num})") + + for i in range(masks_num): + non_zero_num = np.count_nonzero(np.array(masks[i])) + if non_zero_num > 0: + non_zero_masks.append(masks[i]) + if also_process_images: + non_zero_mask_images.append(original_images[i]) + else: + zero_mask_images.append(original_images[i]) + zero_mask_images_indexes.append(i) + + non_zero_masks_out = torch.stack(non_zero_masks, dim=0) + non_zero_mask_images_out = zero_mask_images_out = zero_mask_images_out_indexes = None + + if also_process_images: + non_zero_mask_images_out = torch.stack(non_zero_mask_images, dim=0) + if len(zero_mask_images) > 0: + zero_mask_images_out = torch.stack(zero_mask_images, dim=0) + zero_mask_images_out_indexes = zero_mask_images_indexes + + return (non_zero_masks_out, non_zero_mask_images_out, zero_mask_images_out, zero_mask_images_out_indexes) + +class InsertImageBatchByIndexes: + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "images": ("IMAGE",), + "images_to_insert": ("IMAGE",), + "insert_indexes": ("INDEXES",), + }, + } + + RETURN_TYPES = ("IMAGE", ) + RETURN_NAMES = ("images_after_insert", ) + FUNCTION = "insert" + CATEGORY = "KJNodes/image" + DESCRIPTION = """ +This node is designed to be use with node FilterZeroMasksAndCorrespondingImages +It inserts the images_to_insert into images according to insert_indexes + +Returns: + images_after_insert: updated original images with origonal sequence order +""" + + def insert(self, images, images_to_insert, insert_indexes): + images_after_insert = images + + if images_to_insert is not None and insert_indexes is not None: + images_to_insert_num = len(images_to_insert) + insert_indexes_num = len(insert_indexes) + if images_to_insert_num == insert_indexes_num: + images_after_insert = [] + + i_images = 0 + for i in range(len(images) + images_to_insert_num): + if i in insert_indexes: + images_after_insert.append(images_to_insert[insert_indexes.index(i)]) + else: + images_after_insert.append(images[i_images]) + i_images += 1 + + images_after_insert = torch.stack(images_after_insert, dim=0) + + else: + print(f"[WARNING] skip this node, due to number of images_to_insert ({images_to_insert_num}) is not equal to number of insert_indexes ({insert_indexes_num})") + + + return (images_after_insert, ) + +def bbox_to_region(bbox, target_size=None): + bbox = bbox_check(bbox, target_size) + return (bbox[0], bbox[1], bbox[0] + bbox[2], bbox[1] + bbox[3]) + +def bbox_check(bbox, target_size=None): + if not target_size: + return bbox + + new_bbox = ( + bbox[0], + bbox[1], + min(target_size[0] - bbox[0], bbox[2]), + min(target_size[1] - bbox[1], bbox[3]), + ) + return new_bbox + +class BatchUncropAdvanced: + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "original_images": ("IMAGE",), + "cropped_images": ("IMAGE",), + "cropped_masks": ("MASK",), + "combined_crop_mask": ("MASK",), + "bboxes": ("BBOX",), + "border_blending": ("FLOAT", {"default": 0.25, "min": 0.0, "max": 1.0, "step": 0.01}, ), + "crop_rescale": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01}), + "use_combined_mask": ("BOOLEAN", {"default": False}), + "use_square_mask": ("BOOLEAN", {"default": True}), + }, + "optional": { + "combined_bounding_box": ("BBOX", {"default": None}), + }, + } + + RETURN_TYPES = ("IMAGE",) + FUNCTION = "uncrop" + CATEGORY = "KJNodes/masking" + + + def uncrop(self, original_images, cropped_images, cropped_masks, combined_crop_mask, bboxes, border_blending, crop_rescale, use_combined_mask, use_square_mask, combined_bounding_box = None): + + def inset_border(image, border_width=20, border_color=(0)): + width, height = image.size + bordered_image = Image.new(image.mode, (width, height), border_color) + bordered_image.paste(image, (0, 0)) + draw = ImageDraw.Draw(bordered_image) + draw.rectangle((0, 0, width - 1, height - 1), outline=border_color, width=border_width) + return bordered_image + + if len(original_images) != len(cropped_images): + raise ValueError(f"The number of original_images ({len(original_images)}) and cropped_images ({len(cropped_images)}) should be the same") + + # Ensure there are enough bboxes, but drop the excess if there are more bboxes than images + if len(bboxes) > len(original_images): + print(f"Warning: Dropping excess bounding boxes. Expected {len(original_images)}, but got {len(bboxes)}") + bboxes = bboxes[:len(original_images)] + elif len(bboxes) < len(original_images): + raise ValueError("There should be at least as many bboxes as there are original and cropped images") + + crop_imgs = tensor2pil(cropped_images) + input_images = tensor2pil(original_images) + out_images = [] + + for i in range(len(input_images)): + img = input_images[i] + crop = crop_imgs[i] + bbox = bboxes[i] + + if use_combined_mask: + bb_x, bb_y, bb_width, bb_height = combined_bounding_box[0] + paste_region = bbox_to_region((bb_x, bb_y, bb_width, bb_height), img.size) + mask = combined_crop_mask[i] + else: + bb_x, bb_y, bb_width, bb_height = bbox + paste_region = bbox_to_region((bb_x, bb_y, bb_width, bb_height), img.size) + mask = cropped_masks[i] + + # scale paste_region + scale_x = scale_y = crop_rescale + paste_region = (round(paste_region[0]*scale_x), round(paste_region[1]*scale_y), round(paste_region[2]*scale_x), round(paste_region[3]*scale_y)) + + # rescale the crop image to fit the paste_region + crop = crop.resize((round(paste_region[2]-paste_region[0]), round(paste_region[3]-paste_region[1]))) + crop_img = crop.convert("RGB") + + #border blending + if border_blending > 1.0: + border_blending = 1.0 + elif border_blending < 0.0: + border_blending = 0.0 + + blend_ratio = (max(crop_img.size) / 2) * float(border_blending) + blend = img.convert("RGBA") + + if use_square_mask: + mask = Image.new("L", img.size, 0) + mask_block = Image.new("L", (paste_region[2]-paste_region[0], paste_region[3]-paste_region[1]), 255) + mask_block = inset_border(mask_block, round(blend_ratio / 2), (0)) + mask.paste(mask_block, paste_region) + else: + original_mask = tensor2pil(mask)[0] + original_mask = original_mask.resize((paste_region[2]-paste_region[0], paste_region[3]-paste_region[1])) + mask = Image.new("L", img.size, 0) + mask.paste(original_mask, paste_region) + + mask = mask.filter(ImageFilter.BoxBlur(radius=blend_ratio / 4)) + mask = mask.filter(ImageFilter.GaussianBlur(radius=blend_ratio / 4)) + + blend.paste(crop_img, paste_region) + blend.putalpha(mask) + + img = Image.alpha_composite(img.convert("RGBA"), blend) + out_images.append(img.convert("RGB")) + + return (pil2tensor(out_images),) + +class SplitBboxes: + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "bboxes": ("BBOX",), + "index": ("INT", {"default": 0,"min": 0, "max": 99999999, "step": 1}), + }, + } + + RETURN_TYPES = ("BBOX","BBOX",) + RETURN_NAMES = ("bboxes_a","bboxes_b",) + FUNCTION = "splitbbox" + CATEGORY = "KJNodes/masking" + DESCRIPTION = """ +Splits the specified bbox list at the given index into two lists. +""" + + def splitbbox(self, bboxes, index): + bboxes_a = bboxes[:index] # Sub-list from the start of bboxes up to (but not including) the index + bboxes_b = bboxes[index:] # Sub-list from the index to the end of bboxes + + return (bboxes_a, bboxes_b,) \ No newline at end of file diff --git a/curve_nodes.py b/nodes/curve_nodes.py similarity index 99% rename from curve_nodes.py rename to nodes/curve_nodes.py index 94dcb52..fd25d4f 100644 --- a/curve_nodes.py +++ b/nodes/curve_nodes.py @@ -2,7 +2,7 @@ import torch import json from PIL import Image, ImageDraw import numpy as np -from .utility import pil2tensor +from ..utility.utility import pil2tensor class SplineEditor: @@ -307,7 +307,6 @@ Converts different value lists/series to another type. import pandas as pd input_type = self.detect_input_type(input_values) - # Convert input_values to a list of floats if input_type == 'list of lists': float_values = [item for sublist in input_values for item in sublist] elif input_type == 'pandas series': diff --git a/nodes.py b/nodes/nodes.py similarity index 80% rename from nodes.py rename to nodes/nodes.py index 39c687b..14df121 100644 --- a/nodes.py +++ b/nodes/nodes.py @@ -1,6 +1,5 @@ import torch import torch.nn.functional as F -from torchvision.transforms import Resize, CenterCrop, InterpolationMode from torchvision.transforms import functional as TF import scipy.ndimage @@ -17,9 +16,11 @@ import random import math import model_management -from nodes import MAX_RESOLUTION, SaveImage +from nodes import MAX_RESOLUTION, SaveImage, CLIPTextEncode from comfy_extras.nodes_mask import ImageCompositeMasked +import comfy.sample import folder_paths +from ..utility.utility import tensor2pil, pil2tensor script_directory = os.path.dirname(os.path.abspath(__file__)) folder_paths.add_model_folder_path("kjnodes_fonts", os.path.join(script_directory, "fonts")) @@ -141,7 +142,7 @@ class CreateFluidMask: } #using code from https://github.com/GregTJ/stable-fluids def createfluidmask(self, frames, width, height, invert, inflow_count, inflow_velocity, inflow_radius, inflow_padding, inflow_duration): - from .fluid import Fluid + from ..utility.fluid import Fluid from scipy.spatial import erf out = [] masks = [] @@ -1845,655 +1846,6 @@ class ImageBatchTestPattern: return (out_tensor,) -#based on nodes from mtb https://github.com/melMass/comfy_mtb - -from .utility import tensor2pil, pil2tensor - -class BatchCropFromMask: - - @classmethod - def INPUT_TYPES(cls): - return { - "required": { - "original_images": ("IMAGE",), - "masks": ("MASK",), - "crop_size_mult": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.001}), - "bbox_smooth_alpha": ("FLOAT", {"default": 0.5, "min": 0.0, "max": 1.0, "step": 0.01}), - }, - } - - RETURN_TYPES = ( - "IMAGE", - "IMAGE", - "BBOX", - "INT", - "INT", - ) - RETURN_NAMES = ( - "original_images", - "cropped_images", - "bboxes", - "width", - "height", - ) - FUNCTION = "crop" - CATEGORY = "KJNodes/masking" - - def smooth_bbox_size(self, prev_bbox_size, curr_bbox_size, alpha): - if alpha == 0: - return prev_bbox_size - return round(alpha * curr_bbox_size + (1 - alpha) * prev_bbox_size) - - def smooth_center(self, prev_center, curr_center, alpha=0.5): - if alpha == 0: - return prev_center - return ( - round(alpha * curr_center[0] + (1 - alpha) * prev_center[0]), - round(alpha * curr_center[1] + (1 - alpha) * prev_center[1]) - ) - - def crop(self, masks, original_images, crop_size_mult, bbox_smooth_alpha): - - bounding_boxes = [] - cropped_images = [] - - self.max_bbox_width = 0 - self.max_bbox_height = 0 - - # First, calculate the maximum bounding box size across all masks - curr_max_bbox_width = 0 - curr_max_bbox_height = 0 - for mask in masks: - _mask = tensor2pil(mask)[0] - non_zero_indices = np.nonzero(np.array(_mask)) - min_x, max_x = np.min(non_zero_indices[1]), np.max(non_zero_indices[1]) - min_y, max_y = np.min(non_zero_indices[0]), np.max(non_zero_indices[0]) - width = max_x - min_x - height = max_y - min_y - curr_max_bbox_width = max(curr_max_bbox_width, width) - curr_max_bbox_height = max(curr_max_bbox_height, height) - - # Smooth the changes in the bounding box size - self.max_bbox_width = self.smooth_bbox_size(self.max_bbox_width, curr_max_bbox_width, bbox_smooth_alpha) - self.max_bbox_height = self.smooth_bbox_size(self.max_bbox_height, curr_max_bbox_height, bbox_smooth_alpha) - - # Apply the crop size multiplier - self.max_bbox_width = round(self.max_bbox_width * crop_size_mult) - self.max_bbox_height = round(self.max_bbox_height * crop_size_mult) - bbox_aspect_ratio = self.max_bbox_width / self.max_bbox_height - - # Then, for each mask and corresponding image... - for i, (mask, img) in enumerate(zip(masks, original_images)): - _mask = tensor2pil(mask)[0] - non_zero_indices = np.nonzero(np.array(_mask)) - min_x, max_x = np.min(non_zero_indices[1]), np.max(non_zero_indices[1]) - min_y, max_y = np.min(non_zero_indices[0]), np.max(non_zero_indices[0]) - - # Calculate center of bounding box - center_x = np.mean(non_zero_indices[1]) - center_y = np.mean(non_zero_indices[0]) - curr_center = (round(center_x), round(center_y)) - - # If this is the first frame, initialize prev_center with curr_center - if not hasattr(self, 'prev_center'): - self.prev_center = curr_center - - # Smooth the changes in the center coordinates from the second frame onwards - if i > 0: - center = self.smooth_center(self.prev_center, curr_center, bbox_smooth_alpha) - else: - center = curr_center - - # Update prev_center for the next frame - self.prev_center = center - - # Create bounding box using max_bbox_width and max_bbox_height - half_box_width = round(self.max_bbox_width / 2) - half_box_height = round(self.max_bbox_height / 2) - min_x = max(0, center[0] - half_box_width) - max_x = min(img.shape[1], center[0] + half_box_width) - min_y = max(0, center[1] - half_box_height) - max_y = min(img.shape[0], center[1] + half_box_height) - - # Append bounding box coordinates - bounding_boxes.append((min_x, min_y, max_x - min_x, max_y - min_y)) - - # Crop the image from the bounding box - cropped_img = img[min_y:max_y, min_x:max_x, :] - - # Calculate the new dimensions while maintaining the aspect ratio - new_height = min(cropped_img.shape[0], self.max_bbox_height) - new_width = round(new_height * bbox_aspect_ratio) - - # Resize the image - resize_transform = Resize((new_height, new_width)) - resized_img = resize_transform(cropped_img.permute(2, 0, 1)) - - # Perform the center crop to the desired size - crop_transform = CenterCrop((self.max_bbox_height, self.max_bbox_width)) # swap the order here if necessary - cropped_resized_img = crop_transform(resized_img) - - cropped_images.append(cropped_resized_img.permute(1, 2, 0)) - - cropped_out = torch.stack(cropped_images, dim=0) - - return (original_images, cropped_out, bounding_boxes, self.max_bbox_width, self.max_bbox_height, ) - - -def bbox_to_region(bbox, target_size=None): - bbox = bbox_check(bbox, target_size) - return (bbox[0], bbox[1], bbox[0] + bbox[2], bbox[1] + bbox[3]) - -def bbox_check(bbox, target_size=None): - if not target_size: - return bbox - - new_bbox = ( - bbox[0], - bbox[1], - min(target_size[0] - bbox[0], bbox[2]), - min(target_size[1] - bbox[1], bbox[3]), - ) - return new_bbox - -class BatchUncrop: - - @classmethod - def INPUT_TYPES(cls): - return { - "required": { - "original_images": ("IMAGE",), - "cropped_images": ("IMAGE",), - "bboxes": ("BBOX",), - "border_blending": ("FLOAT", {"default": 0.25, "min": 0.0, "max": 1.0, "step": 0.01}, ), - "crop_rescale": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01}), - "border_top": ("BOOLEAN", {"default": True}), - "border_bottom": ("BOOLEAN", {"default": True}), - "border_left": ("BOOLEAN", {"default": True}), - "border_right": ("BOOLEAN", {"default": True}), - } - } - - RETURN_TYPES = ("IMAGE",) - FUNCTION = "uncrop" - - CATEGORY = "KJNodes/masking" - - def uncrop(self, original_images, cropped_images, bboxes, border_blending, crop_rescale, border_top, border_bottom, border_left, border_right): - def inset_border(image, border_width, border_color, border_top, border_bottom, border_left, border_right): - draw = ImageDraw.Draw(image) - width, height = image.size - if border_top: - draw.rectangle((0, 0, width, border_width), fill=border_color) - if border_bottom: - draw.rectangle((0, height - border_width, width, height), fill=border_color) - if border_left: - draw.rectangle((0, 0, border_width, height), fill=border_color) - if border_right: - draw.rectangle((width - border_width, 0, width, height), fill=border_color) - return image - - if len(original_images) != len(cropped_images): - raise ValueError(f"The number of original_images ({len(original_images)}) and cropped_images ({len(cropped_images)}) should be the same") - - # Ensure there are enough bboxes, but drop the excess if there are more bboxes than images - if len(bboxes) > len(original_images): - print(f"Warning: Dropping excess bounding boxes. Expected {len(original_images)}, but got {len(bboxes)}") - bboxes = bboxes[:len(original_images)] - elif len(bboxes) < len(original_images): - raise ValueError("There should be at least as many bboxes as there are original and cropped images") - - input_images = tensor2pil(original_images) - crop_imgs = tensor2pil(cropped_images) - - out_images = [] - for i in range(len(input_images)): - img = input_images[i] - crop = crop_imgs[i] - bbox = bboxes[i] - - # uncrop the image based on the bounding box - bb_x, bb_y, bb_width, bb_height = bbox - - paste_region = bbox_to_region((bb_x, bb_y, bb_width, bb_height), img.size) - - # scale factors - scale_x = crop_rescale - scale_y = crop_rescale - - # scaled paste_region - paste_region = (round(paste_region[0]*scale_x), round(paste_region[1]*scale_y), round(paste_region[2]*scale_x), round(paste_region[3]*scale_y)) - - # rescale the crop image to fit the paste_region - crop = crop.resize((round(paste_region[2]-paste_region[0]), round(paste_region[3]-paste_region[1]))) - crop_img = crop.convert("RGB") - - if border_blending > 1.0: - border_blending = 1.0 - elif border_blending < 0.0: - border_blending = 0.0 - - blend_ratio = (max(crop_img.size) / 2) * float(border_blending) - - blend = img.convert("RGBA") - mask = Image.new("L", img.size, 0) - - mask_block = Image.new("L", (paste_region[2]-paste_region[0], paste_region[3]-paste_region[1]), 255) - mask_block = inset_border(mask_block, round(blend_ratio / 2), (0), border_top, border_bottom, border_left, border_right) - - mask.paste(mask_block, paste_region) - blend.paste(crop_img, paste_region) - - mask = mask.filter(ImageFilter.BoxBlur(radius=blend_ratio / 4)) - mask = mask.filter(ImageFilter.GaussianBlur(radius=blend_ratio / 4)) - - blend.putalpha(mask) - img = Image.alpha_composite(img.convert("RGBA"), blend) - out_images.append(img.convert("RGB")) - - return (pil2tensor(out_images),) - -class BatchCropFromMaskAdvanced: - - @classmethod - def INPUT_TYPES(cls): - return { - "required": { - "original_images": ("IMAGE",), - "masks": ("MASK",), - "crop_size_mult": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01}), - "bbox_smooth_alpha": ("FLOAT", {"default": 0.5, "min": 0.0, "max": 1.0, "step": 0.01}), - }, - } - - RETURN_TYPES = ( - "IMAGE", - "IMAGE", - "MASK", - "IMAGE", - "MASK", - "BBOX", - "BBOX", - "INT", - "INT", - ) - RETURN_NAMES = ( - "original_images", - "cropped_images", - "cropped_masks", - "combined_crop_image", - "combined_crop_masks", - "bboxes", - "combined_bounding_box", - "bbox_width", - "bbox_height", - ) - FUNCTION = "crop" - CATEGORY = "KJNodes/masking" - - def smooth_bbox_size(self, prev_bbox_size, curr_bbox_size, alpha): - return round(alpha * curr_bbox_size + (1 - alpha) * prev_bbox_size) - - def smooth_center(self, prev_center, curr_center, alpha=0.5): - return (round(alpha * curr_center[0] + (1 - alpha) * prev_center[0]), - round(alpha * curr_center[1] + (1 - alpha) * prev_center[1])) - - def crop(self, masks, original_images, crop_size_mult, bbox_smooth_alpha): - bounding_boxes = [] - combined_bounding_box = [] - cropped_images = [] - cropped_masks = [] - cropped_masks_out = [] - combined_crop_out = [] - combined_cropped_images = [] - combined_cropped_masks = [] - - def calculate_bbox(mask): - non_zero_indices = np.nonzero(np.array(mask)) - - # handle empty masks - min_x, max_x, min_y, max_y = 0, 0, 0, 0 - if len(non_zero_indices[1]) > 0 and len(non_zero_indices[0]) > 0: - min_x, max_x = np.min(non_zero_indices[1]), np.max(non_zero_indices[1]) - min_y, max_y = np.min(non_zero_indices[0]), np.max(non_zero_indices[0]) - - width = max_x - min_x - height = max_y - min_y - bbox_size = max(width, height) - return min_x, max_x, min_y, max_y, bbox_size - - combined_mask = torch.max(masks, dim=0)[0] - _mask = tensor2pil(combined_mask)[0] - new_min_x, new_max_x, new_min_y, new_max_y, combined_bbox_size = calculate_bbox(_mask) - center_x = (new_min_x + new_max_x) / 2 - center_y = (new_min_y + new_max_y) / 2 - half_box_size = round(combined_bbox_size // 2) - new_min_x = max(0, round(center_x - half_box_size)) - new_max_x = min(original_images[0].shape[1], round(center_x + half_box_size)) - new_min_y = max(0, round(center_y - half_box_size)) - new_max_y = min(original_images[0].shape[0], round(center_y + half_box_size)) - - combined_bounding_box.append((new_min_x, new_min_y, new_max_x - new_min_x, new_max_y - new_min_y)) - - self.max_bbox_size = 0 - - # First, calculate the maximum bounding box size across all masks - curr_max_bbox_size = max(calculate_bbox(tensor2pil(mask)[0])[-1] for mask in masks) - # Smooth the changes in the bounding box size - self.max_bbox_size = self.smooth_bbox_size(self.max_bbox_size, curr_max_bbox_size, bbox_smooth_alpha) - # Apply the crop size multiplier - self.max_bbox_size = round(self.max_bbox_size * crop_size_mult) - # Make sure max_bbox_size is divisible by 16, if not, round it upwards so it is - self.max_bbox_size = math.ceil(self.max_bbox_size / 16) * 16 - - if self.max_bbox_size > original_images[0].shape[0] or self.max_bbox_size > original_images[0].shape[1]: - # max_bbox_size can only be as big as our input's width or height, and it has to be even - self.max_bbox_size = math.floor(min(original_images[0].shape[0], original_images[0].shape[1]) / 2) * 2 - - # Then, for each mask and corresponding image... - for i, (mask, img) in enumerate(zip(masks, original_images)): - _mask = tensor2pil(mask)[0] - non_zero_indices = np.nonzero(np.array(_mask)) - - # check for empty masks - if len(non_zero_indices[0]) > 0 and len(non_zero_indices[1]) > 0: - min_x, max_x = np.min(non_zero_indices[1]), np.max(non_zero_indices[1]) - min_y, max_y = np.min(non_zero_indices[0]), np.max(non_zero_indices[0]) - - # Calculate center of bounding box - center_x = np.mean(non_zero_indices[1]) - center_y = np.mean(non_zero_indices[0]) - curr_center = (round(center_x), round(center_y)) - - # If this is the first frame, initialize prev_center with curr_center - if not hasattr(self, 'prev_center'): - self.prev_center = curr_center - - # Smooth the changes in the center coordinates from the second frame onwards - if i > 0: - center = self.smooth_center(self.prev_center, curr_center, bbox_smooth_alpha) - else: - center = curr_center - - # Update prev_center for the next frame - self.prev_center = center - - # Create bounding box using max_bbox_size - half_box_size = self.max_bbox_size // 2 - min_x = max(0, center[0] - half_box_size) - max_x = min(img.shape[1], center[0] + half_box_size) - min_y = max(0, center[1] - half_box_size) - max_y = min(img.shape[0], center[1] + half_box_size) - - # Append bounding box coordinates - bounding_boxes.append((min_x, min_y, max_x - min_x, max_y - min_y)) - - # Crop the image from the bounding box - cropped_img = img[min_y:max_y, min_x:max_x, :] - cropped_mask = mask[min_y:max_y, min_x:max_x] - - # Resize the cropped image to a fixed size - new_size = max(cropped_img.shape[0], cropped_img.shape[1]) - resize_transform = Resize(new_size, interpolation=InterpolationMode.NEAREST, max_size=max(img.shape[0], img.shape[1])) - resized_mask = resize_transform(cropped_mask.unsqueeze(0).unsqueeze(0)).squeeze(0).squeeze(0) - resized_img = resize_transform(cropped_img.permute(2, 0, 1)) - # Perform the center crop to the desired size - # Constrain the crop to the smaller of our bbox or our image so we don't expand past the image dimensions. - crop_transform = CenterCrop((min(self.max_bbox_size, resized_img.shape[1]), min(self.max_bbox_size, resized_img.shape[2]))) - - cropped_resized_img = crop_transform(resized_img) - cropped_images.append(cropped_resized_img.permute(1, 2, 0)) - - cropped_resized_mask = crop_transform(resized_mask) - cropped_masks.append(cropped_resized_mask) - - combined_cropped_img = original_images[i][new_min_y:new_max_y, new_min_x:new_max_x, :] - combined_cropped_images.append(combined_cropped_img) - - combined_cropped_mask = masks[i][new_min_y:new_max_y, new_min_x:new_max_x] - combined_cropped_masks.append(combined_cropped_mask) - else: - bounding_boxes.append((0, 0, img.shape[1], img.shape[0])) - cropped_images.append(img) - cropped_masks.append(mask) - combined_cropped_images.append(img) - combined_cropped_masks.append(mask) - - cropped_out = torch.stack(cropped_images, dim=0) - combined_crop_out = torch.stack(combined_cropped_images, dim=0) - cropped_masks_out = torch.stack(cropped_masks, dim=0) - combined_crop_mask_out = torch.stack(combined_cropped_masks, dim=0) - - return (original_images, cropped_out, cropped_masks_out, combined_crop_out, combined_crop_mask_out, bounding_boxes, combined_bounding_box, self.max_bbox_size, self.max_bbox_size) - -class FilterZeroMasksAndCorrespondingImages: - - @classmethod - def INPUT_TYPES(cls): - return { - "required": { - "masks": ("MASK",), - }, - "optional": { - "original_images": ("IMAGE",), - }, - } - - RETURN_TYPES = ("MASK", "IMAGE", "IMAGE", "INDEXES",) - RETURN_NAMES = ("non_zero_masks_out", "non_zero_mask_images_out", "zero_mask_images_out", "zero_mask_images_out_indexes",) - FUNCTION = "filter" - CATEGORY = "KJNodes/masking" - DESCRIPTION = """ -Filter out all the empty (i.e. all zero) mask in masks -Also filter out all the corresponding images in original_images by indexes if provide - -original_images (optional): If provided, need have same length as masks. -""" - - def filter(self, masks, original_images=None): - non_zero_masks = [] - non_zero_mask_images = [] - zero_mask_images = [] - zero_mask_images_indexes = [] - - masks_num = len(masks) - also_process_images = False - if original_images is not None: - imgs_num = len(original_images) - if len(original_images) == masks_num: - also_process_images = True - else: - print(f"[WARNING] ignore input: original_images, due to number of original_images ({imgs_num}) is not equal to number of masks ({masks_num})") - - for i in range(masks_num): - non_zero_num = np.count_nonzero(np.array(masks[i])) - if non_zero_num > 0: - non_zero_masks.append(masks[i]) - if also_process_images: - non_zero_mask_images.append(original_images[i]) - else: - zero_mask_images.append(original_images[i]) - zero_mask_images_indexes.append(i) - - non_zero_masks_out = torch.stack(non_zero_masks, dim=0) - non_zero_mask_images_out = zero_mask_images_out = zero_mask_images_out_indexes = None - - if also_process_images: - non_zero_mask_images_out = torch.stack(non_zero_mask_images, dim=0) - if len(zero_mask_images) > 0: - zero_mask_images_out = torch.stack(zero_mask_images, dim=0) - zero_mask_images_out_indexes = zero_mask_images_indexes - - return (non_zero_masks_out, non_zero_mask_images_out, zero_mask_images_out, zero_mask_images_out_indexes) - -class InsertImageBatchByIndexes: - - @classmethod - def INPUT_TYPES(cls): - return { - "required": { - "images": ("IMAGE",), - "images_to_insert": ("IMAGE",), - "insert_indexes": ("INDEXES",), - }, - } - - RETURN_TYPES = ("IMAGE", ) - RETURN_NAMES = ("images_after_insert", ) - FUNCTION = "insert" - CATEGORY = "KJNodes/image" - DESCRIPTION = """ -This node is designed to be use with node FilterZeroMasksAndCorrespondingImages -It inserts the images_to_insert into images according to insert_indexes - -Returns: - images_after_insert: updated original images with origonal sequence order -""" - - def insert(self, images, images_to_insert, insert_indexes): - images_after_insert = images - - if images_to_insert is not None and insert_indexes is not None: - images_to_insert_num = len(images_to_insert) - insert_indexes_num = len(insert_indexes) - if images_to_insert_num == insert_indexes_num: - images_after_insert = [] - - i_images = 0 - for i in range(len(images) + images_to_insert_num): - if i in insert_indexes: - images_after_insert.append(images_to_insert[insert_indexes.index(i)]) - else: - images_after_insert.append(images[i_images]) - i_images += 1 - - images_after_insert = torch.stack(images_after_insert, dim=0) - - else: - print(f"[WARNING] skip this node, due to number of images_to_insert ({images_to_insert_num}) is not equal to number of insert_indexes ({insert_indexes_num})") - - - return (images_after_insert, ) - -def bbox_to_region(bbox, target_size=None): - bbox = bbox_check(bbox, target_size) - return (bbox[0], bbox[1], bbox[0] + bbox[2], bbox[1] + bbox[3]) - -def bbox_check(bbox, target_size=None): - if not target_size: - return bbox - - new_bbox = ( - bbox[0], - bbox[1], - min(target_size[0] - bbox[0], bbox[2]), - min(target_size[1] - bbox[1], bbox[3]), - ) - return new_bbox - -class BatchUncropAdvanced: - - @classmethod - def INPUT_TYPES(cls): - return { - "required": { - "original_images": ("IMAGE",), - "cropped_images": ("IMAGE",), - "cropped_masks": ("MASK",), - "combined_crop_mask": ("MASK",), - "bboxes": ("BBOX",), - "border_blending": ("FLOAT", {"default": 0.25, "min": 0.0, "max": 1.0, "step": 0.01}, ), - "crop_rescale": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01}), - "use_combined_mask": ("BOOLEAN", {"default": False}), - "use_square_mask": ("BOOLEAN", {"default": True}), - }, - "optional": { - "combined_bounding_box": ("BBOX", {"default": None}), - }, - } - - RETURN_TYPES = ("IMAGE",) - FUNCTION = "uncrop" - CATEGORY = "KJNodes/masking" - - - def uncrop(self, original_images, cropped_images, cropped_masks, combined_crop_mask, bboxes, border_blending, crop_rescale, use_combined_mask, use_square_mask, combined_bounding_box = None): - - def inset_border(image, border_width=20, border_color=(0)): - width, height = image.size - bordered_image = Image.new(image.mode, (width, height), border_color) - bordered_image.paste(image, (0, 0)) - draw = ImageDraw.Draw(bordered_image) - draw.rectangle((0, 0, width - 1, height - 1), outline=border_color, width=border_width) - return bordered_image - - if len(original_images) != len(cropped_images): - raise ValueError(f"The number of original_images ({len(original_images)}) and cropped_images ({len(cropped_images)}) should be the same") - - # Ensure there are enough bboxes, but drop the excess if there are more bboxes than images - if len(bboxes) > len(original_images): - print(f"Warning: Dropping excess bounding boxes. Expected {len(original_images)}, but got {len(bboxes)}") - bboxes = bboxes[:len(original_images)] - elif len(bboxes) < len(original_images): - raise ValueError("There should be at least as many bboxes as there are original and cropped images") - - crop_imgs = tensor2pil(cropped_images) - input_images = tensor2pil(original_images) - out_images = [] - - for i in range(len(input_images)): - img = input_images[i] - crop = crop_imgs[i] - bbox = bboxes[i] - - if use_combined_mask: - bb_x, bb_y, bb_width, bb_height = combined_bounding_box[0] - paste_region = bbox_to_region((bb_x, bb_y, bb_width, bb_height), img.size) - mask = combined_crop_mask[i] - else: - bb_x, bb_y, bb_width, bb_height = bbox - paste_region = bbox_to_region((bb_x, bb_y, bb_width, bb_height), img.size) - mask = cropped_masks[i] - - # scale paste_region - scale_x = scale_y = crop_rescale - paste_region = (round(paste_region[0]*scale_x), round(paste_region[1]*scale_y), round(paste_region[2]*scale_x), round(paste_region[3]*scale_y)) - - # rescale the crop image to fit the paste_region - crop = crop.resize((round(paste_region[2]-paste_region[0]), round(paste_region[3]-paste_region[1]))) - crop_img = crop.convert("RGB") - - #border blending - if border_blending > 1.0: - border_blending = 1.0 - elif border_blending < 0.0: - border_blending = 0.0 - - blend_ratio = (max(crop_img.size) / 2) * float(border_blending) - blend = img.convert("RGBA") - - if use_square_mask: - mask = Image.new("L", img.size, 0) - mask_block = Image.new("L", (paste_region[2]-paste_region[0], paste_region[3]-paste_region[1]), 255) - mask_block = inset_border(mask_block, round(blend_ratio / 2), (0)) - mask.paste(mask_block, paste_region) - else: - original_mask = tensor2pil(mask)[0] - original_mask = original_mask.resize((paste_region[2]-paste_region[0], paste_region[3]-paste_region[1])) - mask = Image.new("L", img.size, 0) - mask.paste(original_mask, paste_region) - - mask = mask.filter(ImageFilter.BoxBlur(radius=blend_ratio / 4)) - mask = mask.filter(ImageFilter.GaussianBlur(radius=blend_ratio / 4)) - - blend.paste(crop_img, paste_region) - blend.putalpha(mask) - - img = Image.alpha_composite(img.convert("RGBA"), blend) - out_images.append(img.convert("RGB")) - - return (pil2tensor(out_images),) - class BatchCLIPSeg: def __init__(self): @@ -2950,7 +2302,7 @@ class CreateMagicMask: } def createmagicmask(self, frames, transitions, depth, distortion, seed, frame_width, frame_height): - from .magictex import coordinate_grid, random_transform, magic + from ..utility.magictex import coordinate_grid, random_transform, magic rng = np.random.default_rng(seed) out = [] coords = coordinate_grid((frame_width, frame_height)) @@ -3079,31 +2431,6 @@ Visualizes the specified bbox on the image. image_list.append(img_with_bbox) return (torch.cat(image_list, dim=0),) - -class SplitBboxes: - - @classmethod - def INPUT_TYPES(cls): - return { - "required": { - "bboxes": ("BBOX",), - "index": ("INT", {"default": 0,"min": 0, "max": 99999999, "step": 1}), - }, - } - - RETURN_TYPES = ("BBOX","BBOX",) - RETURN_NAMES = ("bboxes_a","bboxes_b",) - FUNCTION = "splitbbox" - CATEGORY = "KJNodes/masking" - DESCRIPTION = """ -Splits the specified bbox list at the given index into two lists. -""" - - def splitbbox(self, bboxes, index): - bboxes_a = bboxes[:index] # Sub-list from the start of bboxes up to (but not including) the index - bboxes_b = bboxes[index:] # Sub-list from the index to the end of bboxes - - return (bboxes_a, bboxes_b,) from PIL import ImageGrab import time @@ -3775,229 +3102,6 @@ with repeats 2 becomes batch of 10 images: 0, 0, 1, 1, 2, 2, 3, 3, 4, 4 repeated_images = torch.repeat_interleave(images, repeats=repeats, dim=0) return (repeated_images, ) -class NormalizedAmplitudeToMask: - @classmethod - def INPUT_TYPES(s): - return {"required": { - "normalized_amp": ("NORMALIZED_AMPLITUDE",), - "width": ("INT", {"default": 512,"min": 16, "max": 4096, "step": 1}), - "height": ("INT", {"default": 512,"min": 16, "max": 4096, "step": 1}), - "frame_offset": ("INT", {"default": 0,"min": -255, "max": 255, "step": 1}), - "location_x": ("INT", {"default": 256,"min": 0, "max": 4096, "step": 1}), - "location_y": ("INT", {"default": 256,"min": 0, "max": 4096, "step": 1}), - "size": ("INT", {"default": 128,"min": 8, "max": 4096, "step": 1}), - "shape": ( - [ - 'none', - 'circle', - 'square', - 'triangle', - ], - { - "default": 'none' - }), - "color": ( - [ - 'white', - 'amplitude', - ], - { - "default": 'amplitude' - }), - },} - - CATEGORY = "KJNodes/audio" - RETURN_TYPES = ("MASK",) - FUNCTION = "convert" - DESCRIPTION = """ -Works as a bridge to the AudioScheduler -nodes: -https://github.com/a1lazydog/ComfyUI-AudioScheduler -Creates masks based on the normalized amplitude. -""" - - def convert(self, normalized_amp, width, height, frame_offset, shape, location_x, location_y, size, color): - # Ensure normalized_amp is an array and within the range [0, 1] - normalized_amp = np.clip(normalized_amp, 0.0, 1.0) - - # Offset the amplitude values by rolling the array - normalized_amp = np.roll(normalized_amp, frame_offset) - - # Initialize an empty list to hold the image tensors - out = [] - # Iterate over each amplitude value to create an image - for amp in normalized_amp: - # Scale the amplitude value to cover the full range of grayscale values - if color == 'amplitude': - grayscale_value = int(amp * 255) - elif color == 'white': - grayscale_value = 255 - # Convert the grayscale value to an RGB format - gray_color = (grayscale_value, grayscale_value, grayscale_value) - finalsize = size * amp - - if shape == 'none': - shapeimage = Image.new("RGB", (width, height), gray_color) - else: - shapeimage = Image.new("RGB", (width, height), "black") - - draw = ImageDraw.Draw(shapeimage) - if shape == 'circle' or shape == 'square': - # Define the bounding box for the shape - left_up_point = (location_x - finalsize, location_y - finalsize) - right_down_point = (location_x + finalsize,location_y + finalsize) - two_points = [left_up_point, right_down_point] - - if shape == 'circle': - draw.ellipse(two_points, fill=gray_color) - elif shape == 'square': - draw.rectangle(two_points, fill=gray_color) - - elif shape == 'triangle': - # Define the points for the triangle - left_up_point = (location_x - finalsize, location_y + finalsize) # bottom left - right_down_point = (location_x + finalsize, location_y + finalsize) # bottom right - top_point = (location_x, location_y) # top point - draw.polygon([top_point, left_up_point, right_down_point], fill=gray_color) - - shapeimage = pil2tensor(shapeimage) - mask = shapeimage[:, :, :, 0] - out.append(mask) - - return (torch.cat(out, dim=0),) - -class OffsetMaskByNormalizedAmplitude: - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "normalized_amp": ("NORMALIZED_AMPLITUDE",), - "mask": ("MASK",), - "x": ("INT", { "default": 0, "min": -4096, "max": MAX_RESOLUTION, "step": 1, "display": "number" }), - "y": ("INT", { "default": 0, "min": -4096, "max": MAX_RESOLUTION, "step": 1, "display": "number" }), - "rotate": ("BOOLEAN", { "default": False }), - "angle_multiplier": ("FLOAT", { "default": 0.0, "min": -1.0, "max": 1.0, "step": 0.001, "display": "number" }), - } - } - - RETURN_TYPES = ("MASK",) - RETURN_NAMES = ("mask",) - FUNCTION = "offset" - CATEGORY = "KJNodes/audio" - DESCRIPTION = """ -Works as a bridge to the AudioScheduler -nodes: -https://github.com/a1lazydog/ComfyUI-AudioScheduler -Offsets masks based on the normalized amplitude. -""" - - def offset(self, mask, x, y, angle_multiplier, rotate, normalized_amp): - - # Ensure normalized_amp is an array and within the range [0, 1] - offsetmask = mask.clone() - normalized_amp = np.clip(normalized_amp, 0.0, 1.0) - - batch_size, height, width = mask.shape - - if rotate: - for i in range(batch_size): - rotation_amp = int(normalized_amp[i] * (360 * angle_multiplier)) - rotation_angle = rotation_amp - offsetmask[i] = TF.rotate(offsetmask[i].unsqueeze(0), rotation_angle).squeeze(0) - if x != 0 or y != 0: - for i in range(batch_size): - offset_amp = normalized_amp[i] * 10 - shift_x = min(x*offset_amp, width-1) - shift_y = min(y*offset_amp, height-1) - if shift_x != 0: - offsetmask[i] = torch.roll(offsetmask[i], shifts=int(shift_x), dims=1) - if shift_y != 0: - offsetmask[i] = torch.roll(offsetmask[i], shifts=int(shift_y), dims=0) - - return offsetmask, - -class ImageTransformByNormalizedAmplitude: - @classmethod - def INPUT_TYPES(s): - return {"required": { - "normalized_amp": ("NORMALIZED_AMPLITUDE",), - "zoom_scale": ("FLOAT", { "default": 0.0, "min": -1.0, "max": 1.0, "step": 0.001, "display": "number" }), - "x_offset": ("INT", { "default": 0, "min": (1 -MAX_RESOLUTION), "max": MAX_RESOLUTION, "step": 1, "display": "number" }), - "y_offset": ("INT", { "default": 0, "min": (1 -MAX_RESOLUTION), "max": MAX_RESOLUTION, "step": 1, "display": "number" }), - "cumulative": ("BOOLEAN", { "default": False }), - "image": ("IMAGE",), - }} - - RETURN_TYPES = ("IMAGE",) - FUNCTION = "amptransform" - CATEGORY = "KJNodes/audio" - DESCRIPTION = """ -Works as a bridge to the AudioScheduler -nodes: -https://github.com/a1lazydog/ComfyUI-AudioScheduler -Transforms image based on the normalized amplitude. -""" - - def amptransform(self, image, normalized_amp, zoom_scale, cumulative, x_offset, y_offset): - # Ensure normalized_amp is an array and within the range [0, 1] - normalized_amp = np.clip(normalized_amp, 0.0, 1.0) - transformed_images = [] - - # Initialize the cumulative zoom factor - prev_amp = 0.0 - - for i in range(image.shape[0]): - img = image[i] # Get the i-th image in the batch - amp = normalized_amp[i] # Get the corresponding amplitude value - - # Incrementally increase the cumulative zoom factor - if cumulative: - prev_amp += amp - amp += prev_amp - - # Convert the image tensor from BxHxWxC to CxHxW format expected by torchvision - img = img.permute(2, 0, 1) - - # Convert PyTorch tensor to PIL Image for processing - pil_img = TF.to_pil_image(img) - - # Calculate the crop size based on the amplitude - width, height = pil_img.size - crop_size = int(min(width, height) * (1 - amp * zoom_scale)) - crop_size = max(crop_size, 1) - - # Calculate the crop box coordinates (centered crop) - left = (width - crop_size) // 2 - top = (height - crop_size) // 2 - right = (width + crop_size) // 2 - bottom = (height + crop_size) // 2 - - # Crop and resize back to original size - cropped_img = TF.crop(pil_img, top, left, crop_size, crop_size) - resized_img = TF.resize(cropped_img, (height, width)) - - # Convert back to tensor in CxHxW format - tensor_img = TF.to_tensor(resized_img) - - # Convert the tensor back to BxHxWxC format - tensor_img = tensor_img.permute(1, 2, 0) - - # Offset the image based on the amplitude - offset_amp = amp * 10 # Calculate the offset magnitude based on the amplitude - shift_x = min(x_offset * offset_amp, img.shape[1] - 1) # Calculate the shift in x direction - shift_y = min(y_offset * offset_amp, img.shape[0] - 1) # Calculate the shift in y direction - - # Apply the offset to the image tensor - if shift_x != 0: - tensor_img = torch.roll(tensor_img, shifts=int(shift_x), dims=1) - if shift_y != 0: - tensor_img = torch.roll(tensor_img, shifts=int(shift_y), dims=0) - - # Add to the list - transformed_images.append(tensor_img) - - # Stack all transformed images into a batch - transformed_batch = torch.stack(transformed_images) - - return (transformed_batch,) - def parse_coordinates(coordinates_str): coordinates = {} pattern = r'(\d+):\((\d+),(\d+)\)' @@ -4203,8 +3307,6 @@ Normalize the images to be in the range [-1, 1] images = images * 2.0 - 1.0 return (images,) -import comfy.sample -from nodes import CLIPTextEncode folder_paths.add_model_folder_path("intristic_loras", os.path.join(script_directory, "intristic_loras")) class Intrinsic_lora_sampling: diff --git a/fluid.py b/utility/fluid.py similarity index 100% rename from fluid.py rename to utility/fluid.py diff --git a/magictex.py b/utility/magictex.py similarity index 100% rename from magictex.py rename to utility/magictex.py diff --git a/numerical.py b/utility/numerical.py similarity index 100% rename from numerical.py rename to utility/numerical.py diff --git a/utility.py b/utility/utility.py similarity index 100% rename from utility.py rename to utility/utility.py diff --git a/web/js/help_popup.js b/web/js/help_popup.js index 4982fd1..cf35bab 100644 --- a/web/js/help_popup.js +++ b/web/js/help_popup.js @@ -211,6 +211,10 @@ const create_documentation_stylesheet = () => { this.show_doc = !this.show_doc docElement.parentNode.removeChild(docElement) docElement = null + if (contentWrapper) { + contentWrapper.remove() + contentWrapper = null + } }, { signal: this.docCtrl.signal }, ); @@ -247,7 +251,7 @@ const create_documentation_stylesheet = () => { const transform = new DOMMatrix() .scaleSelf(scaleX, scaleY) .multiplySelf(ctx.getTransform()) - .translateSelf(this.size[0] * scaleX * window.devicePixelRatio, 0) + .translateSelf(this.size[0] * scaleX * Math.max(1.0,window.devicePixelRatio) , 0) .translateSelf(10, -32) const scale = new DOMMatrix() @@ -301,4 +305,20 @@ const create_documentation_stylesheet = () => { } return r; } + const onRem = nodeType.prototype.onRemoved + + nodeType.prototype.onRemoved = function () { + const r = onRem ? onRem.apply(this, []) : undefined + + if (docElement) { + docElement.remove() + docElement = null + } + + if (contentWrapper) { + contentWrapper.remove() + contentWrapper = null + } + return r + } } \ No newline at end of file From 8ccf83fbbf9fb4d45dd4fd24e9862e7979ddc029 Mon Sep 17 00:00:00 2001 From: kijai <40791699+kijai@users.noreply.github.com> Date: Sun, 28 Apr 2024 17:07:06 +0300 Subject: [PATCH 53/95] cleanup --- web/js/appearance.js | 10 +++------- web/js/plotnode.js | 30 ------------------------------ 2 files changed, 3 insertions(+), 37 deletions(-) delete mode 100644 web/js/plotnode.js diff --git a/web/js/appearance.js b/web/js/appearance.js index 843a5b5..d90b4aa 100644 --- a/web/js/appearance.js +++ b/web/js/appearance.js @@ -1,27 +1,23 @@ import { app } from "../../../scripts/app.js"; - app.registerExtension({ name: "KJNodes.appearance", nodeCreated(node) { - const title = node.getTitle(); - switch (title) { - case "INT Constant": + switch (node.comfyClass) { + case "INTConstant": node.setSize([200, 58]); node.color = "#1b4669"; node.bgcolor = "#29699c"; break; - case "Float Constant": + case "FloatConstant": node.setSize([200, 58]); node.color = LGraphCanvas.node_colors.green.color; node.bgcolor = LGraphCanvas.node_colors.green.bgcolor; break; case "ConditioningMultiCombine": - node.color = LGraphCanvas.node_colors.brown.color; node.bgcolor = LGraphCanvas.node_colors.brown.bgcolor; break; - } } }); diff --git a/web/js/plotnode.js b/web/js/plotnode.js deleted file mode 100644 index 67c519a..0000000 --- a/web/js/plotnode.js +++ /dev/null @@ -1,30 +0,0 @@ -import { app } from "../../../scripts/app.js"; -//WIP doesn't do anything -app.registerExtension({ - name: "KJNodes.PlotNode", - async beforeRegisterNodeDef(nodeType, nodeData, app) { - switch (nodeData.name) { - case "PlotNode": - - nodeType.prototype.onNodeCreated = function () { - - this.addWidget("button", "Update", null, () => { - - console.log("start x:" + this.pos[0]) - console.log("start y:" +this.pos[1]) - console.log(this.graph.links); - const toNode = this.graph._nodes.find((otherNode) => otherNode.id == this.graph.links[1].target_id); - console.log("target x:" + toNode.pos[0]) - const a = this.pos[0] - const b = toNode.pos[0] - const distance = Math.abs(a - b); - const maxDistance = 1000 - const finalDistance = (distance - 0) / (maxDistance - 0); - - this.widgets[0].value = finalDistance; - }); - } - break; - } - }, -}); \ No newline at end of file From 4db34a969d8a41b7328b3a90baefc4a689741935 Mon Sep 17 00:00:00 2001 From: kijai <40791699+kijai@users.noreply.github.com> Date: Sun, 28 Apr 2024 17:14:56 +0300 Subject: [PATCH 54/95] Fix spline not updating when controlpoint is on and edge --- web/js/spline_editor.js | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/web/js/spline_editor.js b/web/js/spline_editor.js index d320e85..b8e0bc6 100644 --- a/web/js/spline_editor.js +++ b/web/js/spline_editor.js @@ -371,11 +371,6 @@ function createSplineEditor(context, reset=false) { context.contextMenu.style.top = `${pv.event.clientY}px`; } }) - .event("mouseup", function() { - if (this.pathElements !== null) { - updatePath(); - } - }); vis.add(pv.Rule) .data(pv.range(0, 8, .5)) @@ -416,6 +411,9 @@ function createSplineEditor(context, reset=false) { return this; }) .event("dragend", function() { + if (this.pathElements !== null) { + updatePath(); + } isDragging = false; }) .event("drag", vis) From 0843356c7895689cdf1fd73ccdb029d57698b9c8 Mon Sep 17 00:00:00 2001 From: kijai <40791699+kijai@users.noreply.github.com> Date: Sun, 28 Apr 2024 18:22:33 +0300 Subject: [PATCH 55/95] Take canvas scale into account with drag events --- web/js/help_popup.js | 5 +++-- web/js/spline_editor.js | 36 ++++++++++++++++++++++-------------- 2 files changed, 25 insertions(+), 16 deletions(-) diff --git a/web/js/help_popup.js b/web/js/help_popup.js index cf35bab..b6ccf3a 100644 --- a/web/js/help_popup.js +++ b/web/js/help_popup.js @@ -221,8 +221,9 @@ const create_documentation_stylesheet = () => { document.addEventListener('mousemove', function (e) { if (!isResizing) return; - const newWidth = startWidth + e.clientX - startX; - const newHeight = startHeight + e.clientY - startY; + const scale = app.canvas.ds.scale; + const newWidth = startWidth + (e.clientX - startX) / scale; + const newHeight = startHeight + (e.clientY - startY) / scale;; docElement.style.width = `${newWidth}px`; docElement.style.height = `${newHeight}px`; }, diff --git a/web/js/spline_editor.js b/web/js/spline_editor.js index b8e0bc6..3228c5a 100644 --- a/web/js/spline_editor.js +++ b/web/js/spline_editor.js @@ -103,6 +103,7 @@ app.registerExtension({ async beforeRegisterNodeDef(nodeType, nodeData) { if (nodeData?.name === 'SplineEditor') { chainCallback(nodeType.prototype, "onNodeCreated", function () { + hideWidgetForGood(this, this.widgets.find(w => w.name === "coordinates")) var element = document.createElement("div"); @@ -146,6 +147,7 @@ app.registerExtension({ this.menuItem2.id = "menu-item-2"; this.menuItem2.textContent = "Display sample points"; styleMenuItem(this.menuItem2); + // Add hover effect to menu items this.menuItem1.addEventListener('mouseover', function() { this.style.backgroundColor = "gray"; @@ -165,6 +167,7 @@ app.registerExtension({ this.contextMenu.appendChild(this.menuItem2); document.body.appendChild( this.contextMenu); + this.addWidget("button", "New spline", null, () => { if (!this.properties || !("points" in this.properties)) { @@ -175,7 +178,9 @@ app.registerExtension({ createSplineEditor(this, true) } }); - this.setSize([550, 870]) + + this.setSize([550, 870]); + this.resizable = false; this.splineEditor.parentEl = document.createElement("div"); this.splineEditor.parentEl.className = "spline-editor"; this.splineEditor.parentEl.id = `spline-editor-${this.uuid}` @@ -183,18 +188,10 @@ app.registerExtension({ chainCallback(this, "onGraphConfigured", function() { console.log('onGraphConfigured'); - createSplineEditor(this) - this.setSize([550, 870]) + createSplineEditor(this); + this.setSize([550, 870]); }); - - //disable context menu on right click - // document.addEventListener('contextmenu', function(e) { - // if (e.button === 2) { // Right mouse button - // e.preventDefault(); - // e.stopPropagation(); - // } - // }) - + }); // onAfterGraphConfigured }//node created } //before register @@ -416,7 +413,19 @@ function createSplineEditor(context, reset=false) { } isDragging = false; }) - .event("drag", vis) + .event("drag", function() { + let adjustedX = this.mouse().x / app.canvas.ds.scale; // Adjust the new X position by the inverse of the scale factor + let adjustedY = this.mouse().y / app.canvas.ds.scale; // Adjust the new Y position by the inverse of the scale factor + // Determine the bounds of the vis.Panel + const panelWidth = vis.width(); + const panelHeight = vis.height(); + + // Adjust the new position if it would place the dot outside the bounds of the vis.Panel + adjustedX = Math.max(0, Math.min(panelWidth, adjustedX)); + adjustedY = Math.max(0, Math.min(panelHeight, adjustedY)); + points[this.index] = { x: adjustedX, y: adjustedY }; // Update the point's position + vis.render(); // Re-render the visualization to reflect the new position + }) .event("mouseover", function() { hoverIndex = this.index; // Set the hover index to the index of the hovered dot vis.render(); // Re-render the visualization @@ -443,7 +452,6 @@ function createSplineEditor(context, reset=false) { }) .textStyle("orange") - vis.render(); var svgElement = vis.canvas(); svgElement.style['zIndex'] = "2" From 1bb4b9bd267f53aa90fb778282b252b571a84ae1 Mon Sep 17 00:00:00 2001 From: kijai <40791699+kijai@users.noreply.github.com> Date: Sun, 28 Apr 2024 21:27:17 +0300 Subject: [PATCH 56/95] Proper point sampling for the spline editor --- __init__.py | 2 + nodes/curve_nodes.py | 57 ++++++++++++----------- web/js/spline_editor.js | 100 ++++++++++++++++++++++++++++++++-------- 3 files changed, 113 insertions(+), 46 deletions(-) diff --git a/__init__.py b/__init__.py index f8d46fe..09f0957 100644 --- a/__init__.py +++ b/__init__.py @@ -93,6 +93,7 @@ NODE_CLASS_MAPPINGS = { "MaskOrImageToWeight": MaskOrImageToWeight, "WeightScheduleConvert": WeightScheduleConvert, "FloatToMask": FloatToMask, + "FloatToSigmas": FloatToSigmas, #experimental "StabilityAPI_SD3": StabilityAPI_SD3, "SoundReactive": SoundReactive, @@ -189,6 +190,7 @@ NODE_DISPLAY_NAME_MAPPINGS = { "MaskOrImageToWeight": "Mask Or Image To Weight", "WeightScheduleConvert": "Weight Schedule Convert", "FloatToMask": "Float To Mask", + "FloatToSigmas": "Float To Sigmas", "CustomSigmas": "Custom Sigmas", "ImagePass": "ImagePass", #curve nodes diff --git a/nodes/curve_nodes.py b/nodes/curve_nodes.py index fd25d4f..a723f06 100644 --- a/nodes/curve_nodes.py +++ b/nodes/curve_nodes.py @@ -34,7 +34,6 @@ class SplineEditor: "float_output_type": ( [ 'list', - 'list of lists', 'pandas series', 'tensor', ], @@ -76,8 +75,6 @@ output types: example compatible nodes: anything that takes masks - list of floats example compatible nodes: IPAdapter weights - - list of lists - example compatible nodes: unknown - pandas series example compatible nodes: anything that takes Fizz' nodes Batch Value Schedule @@ -92,14 +89,13 @@ output types: for coord in coordinates: coord['x'] = int(round(coord['x'])) coord['y'] = int(round(coord['y'])) + normalized_y_values = [ (1.0 - (point['y'] / 512) - 0.0) * (max_value - min_value) + min_value for point in coordinates ] if float_output_type == 'list': out_floats = normalized_y_values * repeat_output - elif float_output_type == 'list of lists': - out_floats = ([[value] for value in normalized_y_values] * repeat_output), elif float_output_type == 'pandas series': try: import pandas as pd @@ -207,7 +203,6 @@ class MaskOrImageToWeight: "output_type": ( [ 'list', - 'list of lists', 'pandas series', 'tensor', ], @@ -243,8 +238,6 @@ and returns that as the selected output type. # Convert mean_values to the specified output_type if output_type == 'list': return mean_values, - elif output_type == 'list of lists': - return [[value] for value in mean_values], elif output_type == 'pandas series': try: import pandas as pd @@ -267,7 +260,6 @@ class WeightScheduleConvert: [ 'match_input', 'list', - 'list of lists', 'pandas series', 'tensor', ], @@ -298,8 +290,6 @@ Converts different value lists/series to another type. return 'pandas series' elif isinstance(input_values, torch.Tensor): return 'tensor' - elif isinstance(input_values, list) and all(isinstance(sub, list) for sub in input_values): - return 'list of lists' else: raise ValueError("Unsupported input type") @@ -307,9 +297,7 @@ Converts different value lists/series to another type. import pandas as pd input_type = self.detect_input_type(input_values) - if input_type == 'list of lists': - float_values = [item for sublist in input_values for item in sublist] - elif input_type == 'pandas series': + if input_type == 'pandas series': float_values = input_values.tolist() elif input_type == 'tensor': float_values = input_values @@ -345,13 +333,13 @@ Converts different value lists/series to another type. if output_type == 'list': return float_values, - elif output_type == 'list of lists': - return [[value] for value in float_values], elif output_type == 'pandas series': return pd.Series(float_values), elif output_type == 'tensor': if input_type == 'pandas series': - return torch.tensor(input_values.values, dtype=torch.float32), + return torch.tensor(float_values.values, dtype=torch.float32), + else: + return torch.tensor(float_values, dtype=torch.float32), elif output_type == 'match_input': return float_values, else: @@ -408,7 +396,6 @@ class WeightScheduleExtend: [ 'match_input', 'list', - 'list of lists', 'pandas series', 'tensor', ], @@ -433,8 +420,6 @@ Extends, and converts if needed, different value lists/series return 'pandas series' elif isinstance(input_values, torch.Tensor): return 'tensor' - elif isinstance(input_values, list) and all(isinstance(sub, list) for sub in input_values): - return 'list of lists' else: raise ValueError("Unsupported input type") @@ -445,10 +430,7 @@ Extends, and converts if needed, different value lists/series # Convert input_values_2 to the same format as input_values_1 if they do not match if not input_type_1 == input_type_2: print("Converting input_values_2 to the same format as input_values_1") - if input_type_1 == 'list of lists': - # Assuming input_values_2 is a flat list, convert it to a list of lists - float_values_2 = [[item] for item in input_values_2] - elif input_type_1 == 'pandas series': + if input_type_1 == 'pandas series': # Convert input_values_2 to a pandas Series float_values_2 = pd.Series(input_values_2) elif input_type_1 == 'tensor': @@ -463,14 +445,33 @@ Extends, and converts if needed, different value lists/series if output_type == 'list': return float_values, - elif output_type == 'list of lists': - return [[value] for value in float_values], elif output_type == 'pandas series': return pd.Series(float_values), elif output_type == 'tensor': if input_type_1 == 'pandas series': - return torch.tensor(input_values_1.values, dtype=torch.float32), + return torch.tensor(float_values.values, dtype=torch.float32), + else: + return torch.tensor(float_values, dtype=torch.float32), elif output_type == 'match_input': return float_values, else: - raise ValueError(f"Unsupported output_type: {output_type}") \ No newline at end of file + raise ValueError(f"Unsupported output_type: {output_type}") + +class FloatToSigmas: + @classmethod + def INPUT_TYPES(s): + return {"required": + { + "float_list": ("FLOAT", {"default": 0.0, "forceInput": True}), + } + } + RETURN_TYPES = ("SIGMAS",) + RETURN_NAMES = ("SIGMAS",) + CATEGORY = "KJNodes/noise" + FUNCTION = "customsigmas" + DESCRIPTION = """ +Creates a sigmas tensor from list of float values. + +""" + def customsigmas(self, float_list): + return torch.tensor(float_list, dtype=torch.float32), \ No newline at end of file diff --git a/web/js/spline_editor.js b/web/js/spline_editor.js index 3228c5a..6b0bf63 100644 --- a/web/js/spline_editor.js +++ b/web/js/spline_editor.js @@ -148,23 +148,27 @@ app.registerExtension({ this.menuItem2.textContent = "Display sample points"; styleMenuItem(this.menuItem2); - // Add hover effect to menu items - this.menuItem1.addEventListener('mouseover', function() { - this.style.backgroundColor = "gray"; + this.menuItem3 = document.createElement("a"); + this.menuItem3.href = "#"; + this.menuItem3.id = "menu-item-2"; + this.menuItem3.textContent = "Switch sampling method"; + styleMenuItem(this.menuItem3); + + const menuItems = [this.menuItem1, this.menuItem2, this.menuItem3]; + + menuItems.forEach(menuItem => { + menuItem.addEventListener('mouseover', function() { + this.style.backgroundColor = "gray"; + }); + menuItem.addEventListener('mouseout', function() { + this.style.backgroundColor = "#202020"; }); - this.menuItem1.addEventListener('mouseout', function() { - this.style.backgroundColor = "#202020"; }); - this.menuItem2.addEventListener('mouseover', function() { - this.style.backgroundColor = "gray"; + // Append menu items to the context menu + menuItems.forEach(menuItem => { + this.contextMenu.appendChild(menuItem); }); - this.menuItem2.addEventListener('mouseout', function() { - this.style.backgroundColor = "#202020"; - }); - - this.contextMenu.appendChild(this.menuItem1); - this.contextMenu.appendChild(this.menuItem2); document.body.appendChild( this.contextMenu); @@ -241,15 +245,26 @@ function createSplineEditor(context, reset=false) { context.menuItem2.addEventListener('click', function(e) { e.preventDefault(); drawSamplePoints = !drawSamplePoints; - updatePath(); }); + context.menuItem3.addEventListener('click', function(e) { + e.preventDefault(); + if (pointSamplingMethod == samplePointsTime) { + pointSamplingMethod = samplePointsPath + } + else { + pointSamplingMethod = samplePointsTime + } + updatePath(); +}); + var drawSamplePoints = false; + var pointSamplingMethod = samplePointsTime function updatePath() { points_to_sample = pointsWidget.value - let coords = samplePoints(pathElements[0], points_to_sample); + let coords = pointSamplingMethod(pathElements[0], points_to_sample); if (drawSamplePoints) { if (pointsLayer) { // Update the data of the existing points layer @@ -315,9 +330,11 @@ function createSplineEditor(context, reset=false) { } minValueWidget.callback = () => { + rangeMin = minValueWidget.value updatePath(); } maxValueWidget.callback = () => { + rangeMax = maxValueWidget.value updatePath(); } @@ -444,7 +461,6 @@ function createSplineEditor(context, reset=false) { .font(12 + "px sans-serif") .text(d => { - // Normalize y to range 0.0 to 1.0, considering the inverted y-axis let normalizedY = (1.0 - (d.y / h) - 0.0) * (rangeMax - rangeMin) + rangeMin; let normalizedX = (d.x / w); let frame = Math.round((d.x / w) * points_to_sample); @@ -461,13 +477,14 @@ function createSplineEditor(context, reset=false) { updatePath(); } -function samplePoints(svgPathElement, numSamples) { +function samplePointsPath(svgPathElement, numSamples) { var pathLength = svgPathElement.getTotalLength(); var points = []; for (var i = 0; i < numSamples; i++) { // Calculate the distance along the path for the current sample var distance = (pathLength / (numSamples - 1)) * i; + console.log(distance) // Get the point at the current distance var point = svgPathElement.getPointAtLength(distance); @@ -475,10 +492,57 @@ function samplePoints(svgPathElement, numSamples) { // Add the point to the array of points points.push({ x: point.x, y: point.y }); } - //console.log(points); + console.log(points); return points; } +function samplePointsTime(svgPathElement, numSamples) { + var svgWidth = 512; // Fixed width of the SVG element + var pathLength = svgPathElement.getTotalLength(); + var points = []; + + for (var i = 0; i < numSamples; i++) { + // Calculate the x-coordinate for the current sample based on the SVG's width + var x = (svgWidth / (numSamples - 1)) * i; + + // Find the point on the path that intersects the vertical line at the calculated x-coordinate + var point = findPointAtX(svgPathElement, x, pathLength); + + // Add the point to the array of points + points.push({ x: point.x, y: point.y }); + } + return points; +} + +function findPointAtX(svgPathElement, targetX, pathLength) { + let low = 0; + let high = pathLength; + let bestPoint = svgPathElement.getPointAtLength(0); + + while (low <= high) { + let mid = low + (high - low) / 2; + let point = svgPathElement.getPointAtLength(mid); + + if (Math.abs(point.x - targetX) < 1) { + return point; // The point is close enough to the target + } + + if (point.x < targetX) { + low = mid + 1; + } else { + high = mid - 1; + } + + // Keep track of the closest point found so far + if (Math.abs(point.x - targetX) < Math.abs(bestPoint.x - targetX)) { + bestPoint = point; + } + } + + // Return the closest point found + return bestPoint; +} + //from melmass export function hideWidgetForGood(node, widget, suffix = '') { widget.origType = widget.type From c48cd8b1520d50de90a27698ef4f19c284bdd3be Mon Sep 17 00:00:00 2001 From: kijai <40791699+kijai@users.noreply.github.com> Date: Mon, 29 Apr 2024 02:21:03 +0300 Subject: [PATCH 57/95] Spline editor updates --- nodes/curve_nodes.py | 31 ++++--- web/js/spline_editor.js | 183 ++++++++++++++++++++++++---------------- 2 files changed, 131 insertions(+), 83 deletions(-) diff --git a/nodes/curve_nodes.py b/nodes/curve_nodes.py index a723f06..f673081 100644 --- a/nodes/curve_nodes.py +++ b/nodes/curve_nodes.py @@ -15,6 +15,14 @@ class SplineEditor: "mask_width": ("INT", {"default": 512, "min": 8, "max": 4096, "step": 8}), "mask_height": ("INT", {"default": 512, "min": 8, "max": 4096, "step": 8}), "points_to_sample": ("INT", {"default": 16, "min": 2, "max": 1000, "step": 1}), + "sampling_method": ( + [ + 'path', + 'time', + ], + { + "default": 'time' + }), "interpolation": ( [ 'cardinal', @@ -59,20 +67,26 @@ guaranteed!! ## Graphical editor to create values for various ## schedules and/or mask batches. -Shift + click to add control points. -Right click to delete control points. +**Shift + click** to add control point at end. +**Ctrl + click** to add control point (subdivide) between two points. +**Right click on a point** to delete it. Note that you can't delete from start/end. + +Right click on canvas for context menu: +These are purely visual options, doesn't affect the output: + - Toggle handles visibility + - Display sample points: display the points to be returned. **points_to_sample** value sets the number of samples returned from the **drawn spline itself**, this is independent from the actual control points, so the interpolation type matters. - -Changing interpolation type and tension value takes effect on -interaction with the graph. +sampling_method: + - time: samples along the time axis, used for schedules + - path: samples along the path itself, useful for coordinates output types: - mask batch - example compatible nodes: anything that takes masks + example compatible nodes: anything that takes masks - list of floats example compatible nodes: IPAdapter weights - pandas series @@ -83,7 +97,7 @@ output types: """ def splinedata(self, mask_width, mask_height, coordinates, float_output_type, interpolation, - points_to_sample, points_store, tension, repeat_output, min_value=0.0, max_value=1.0): + points_to_sample, sampling_method, points_store, tension, repeat_output, min_value=0.0, max_value=1.0): coordinates = json.loads(coordinates) for coord in coordinates: @@ -151,11 +165,8 @@ Grow value is the amount to grow the shape on each frame, creating animated mask # Define the number of images in the batch coordinates = coordinates.replace("'", '"') coordinates = json.loads(coordinates) - for coord in coordinates: - print(coord) batch_size = len(coordinates) - print(batch_size) out = [] color = "white" diff --git a/web/js/spline_editor.js b/web/js/spline_editor.js index 6b0bf63..27a5e46 100644 --- a/web/js/spline_editor.js +++ b/web/js/spline_editor.js @@ -116,7 +116,6 @@ app.registerExtension({ }); // context menu - this.contextMenu = document.createElement("div"); this.contextMenu.id = "context-menu"; this.contextMenu.style.display = "none"; @@ -148,13 +147,13 @@ app.registerExtension({ this.menuItem2.textContent = "Display sample points"; styleMenuItem(this.menuItem2); - this.menuItem3 = document.createElement("a"); - this.menuItem3.href = "#"; - this.menuItem3.id = "menu-item-2"; - this.menuItem3.textContent = "Switch sampling method"; - styleMenuItem(this.menuItem3); + // this.menuItem3 = document.createElement("a"); + // this.menuItem3.href = "#"; + // this.menuItem3.id = "menu-item-2"; + // this.menuItem3.textContent = "Switch sampling method"; + // styleMenuItem(this.menuItem3); - const menuItems = [this.menuItem1, this.menuItem2, this.menuItem3]; + const menuItems = [this.menuItem1, this.menuItem2]; menuItems.forEach(menuItem => { menuItem.addEventListener('mouseover', function() { @@ -173,7 +172,6 @@ app.registerExtension({ document.body.appendChild( this.contextMenu); this.addWidget("button", "New spline", null, () => { - if (!this.properties || !("points" in this.properties)) { createSplineEditor(this) this.addProperty("points", this.constructor.type, "string"); @@ -183,7 +181,7 @@ app.registerExtension({ } }); - this.setSize([550, 870]); + this.setSize([550, 900]); this.resizable = false; this.splineEditor.parentEl = document.createElement("div"); this.splineEditor.parentEl.className = "spline-editor"; @@ -191,9 +189,8 @@ app.registerExtension({ element.appendChild(this.splineEditor.parentEl); chainCallback(this, "onGraphConfigured", function() { - console.log('onGraphConfigured'); createSplineEditor(this); - this.setSize([550, 870]); + this.setSize([550, 900]); }); }); // onAfterGraphConfigured @@ -248,51 +245,50 @@ function createSplineEditor(context, reset=false) { updatePath(); }); - context.menuItem3.addEventListener('click', function(e) { - e.preventDefault(); - if (pointSamplingMethod == samplePointsTime) { - pointSamplingMethod = samplePointsPath - } - else { - pointSamplingMethod = samplePointsTime - } - updatePath(); -}); +// context.menuItem3.addEventListener('click', function(e) { +// e.preventDefault(); +// if (pointSamplingMethod == samplePointsTime) { +// pointSamplingMethod = samplePointsPath +// } +// else { +// pointSamplingMethod = samplePointsTime +// } +// updatePath(); +// }); var drawSamplePoints = false; - var pointSamplingMethod = samplePointsTime + //var pointSamplingMethod = samplePointsTime function updatePath() { - points_to_sample = pointsWidget.value - let coords = pointSamplingMethod(pathElements[0], points_to_sample); + let coords = samplePoints(pathElements[0], points_to_sample, samplingMethod); + if (drawSamplePoints) { - if (pointsLayer) { - // Update the data of the existing points layer - pointsLayer.data(coords); - } else { - // Create the points layer if it doesn't exist - pointsLayer = vis.add(pv.Dot) - .data(coords) - .left(function(d) { return d.x; }) - .top(function(d) { return d.y; }) - .radius(5) // Adjust the radius as needed - .fillStyle("red") // Change the color as needed - .strokeStyle("black") // Change the stroke color as needed - .lineWidth(1); // Adjust the line width as needed + if (pointsLayer) { + // Update the data of the existing points layer + pointsLayer.data(coords); + } else { + // Create the points layer if it doesn't exist + pointsLayer = vis.add(pv.Dot) + .data(coords) + .left(function(d) { return d.x; }) + .top(function(d) { return d.y; }) + .radius(5) // Adjust the radius as needed + .fillStyle("red") // Change the color as needed + .strokeStyle("black") // Change the stroke color as needed + .lineWidth(1); // Adjust the line width as needed } - } - else { + } else { if (pointsLayer) { - // Remove the points layer - pointsLayer.data([]); - vis.render(); + // Remove the points layer + pointsLayer.data([]); + vis.render(); } } let coordsString = JSON.stringify(coords); pointsStoreWidget.value = JSON.stringify(points); if (coordWidget) { coordWidget.value = coordsString; - } + } vis.render(); } @@ -306,6 +302,7 @@ function createSplineEditor(context, reset=false) { const tensionWidget = context.widgets.find(w => w.name === "tension"); const minValueWidget = context.widgets.find(w => w.name === "min_value"); const maxValueWidget = context.widgets.find(w => w.name === "max_value"); + const samplingMethodWidget = context.widgets.find(w => w.name === "sampling_method"); //const segmentedWidget = context.widgets.find(w => w.name === "segmented"); var interpolation = interpolationWidget.value @@ -314,12 +311,16 @@ function createSplineEditor(context, reset=false) { var rangeMin = minValueWidget.value var rangeMax = maxValueWidget.value var pointsLayer = null; + var samplingMethod = samplingMethodWidget.value interpolationWidget.callback = () => { interpolation = interpolationWidget.value updatePath(); } - + samplingMethodWidget.callback = () => { + samplingMethod = samplingMethodWidget.value + updatePath(); + } tensionWidget.callback = () => { tension = tensionWidget.value updatePath(); @@ -328,7 +329,6 @@ function createSplineEditor(context, reset=false) { points_to_sample = pointsWidget.value updatePath(); } - minValueWidget.callback = () => { rangeMin = minValueWidget.value updatePath(); @@ -376,9 +376,34 @@ function createSplineEditor(context, reset=false) { .margin(10) .event("mousedown", function() { if (pv.event.shiftKey) { // Use pv.event to access the event object - i = points.push(this.mouse()) - 1; + let scaledMouse = { + x: this.mouse().x / app.canvas.ds.scale, + y: this.mouse().y / app.canvas.ds.scale + }; + i = points.push(scaledMouse) - 1; return this; } + else if (pv.event.ctrlKey) { + // Capture the clicked location + let clickedPoint = { + x: this.mouse().x / app.canvas.ds.scale, + y: this.mouse().y / app.canvas.ds.scale + }; + + // Find the two closest points to the clicked location + let { point1Index, point2Index } = findClosestPoints(points, clickedPoint); + + // Calculate the midpoint between the two closest points + let midpoint = { + x: (points[point1Index].x + points[point2Index].x) / 2, + y: (points[point1Index].y + points[point2Index].y) / 2 + }; + + // Insert the midpoint into the array + points.splice(point2Index, 0, midpoint); + i = point2Index; + updatePath(); + } else if (pv.event.button === 2) { context.contextMenu.style.display = 'block'; context.contextMenu.style.left = `${pv.event.clientX}px`; @@ -388,9 +413,15 @@ function createSplineEditor(context, reset=false) { vis.add(pv.Rule) .data(pv.range(0, 8, .5)) - .bottom(d => d * 64 + 0) + .bottom(d => d * 64) .strokeStyle("gray") - .lineWidth(2) + .lineWidth(3) + + // vis.add(pv.Rule) + // .data(pv.range(0, points_to_sample, 1)) + // .left(d => d * 512 / (points_to_sample - 1)) + // .strokeStyle("gray") + // .lineWidth(2) vis.add(pv.Line) .data(() => points) @@ -475,38 +506,26 @@ function createSplineEditor(context, reset=false) { context.splineEditor.element.appendChild(svgElement); var pathElements = svgElement.getElementsByTagName('path'); // Get all path elements updatePath(); - -} -function samplePointsPath(svgPathElement, numSamples) { - var pathLength = svgPathElement.getTotalLength(); - var points = []; - - for (var i = 0; i < numSamples; i++) { - // Calculate the distance along the path for the current sample - var distance = (pathLength / (numSamples - 1)) * i; - console.log(distance) - - // Get the point at the current distance - var point = svgPathElement.getPointAtLength(distance); - - // Add the point to the array of points - points.push({ x: point.x, y: point.y }); - } - console.log(points); - return points; } -function samplePointsTime(svgPathElement, numSamples) { +function samplePoints(svgPathElement, numSamples, samplingMethod) { var svgWidth = 512; // Fixed width of the SVG element var pathLength = svgPathElement.getTotalLength(); var points = []; for (var i = 0; i < numSamples; i++) { - // Calculate the x-coordinate for the current sample based on the SVG's width - var x = (svgWidth / (numSamples - 1)) * i; - - // Find the point on the path that intersects the vertical line at the calculated x-coordinate - var point = findPointAtX(svgPathElement, x, pathLength); + if (samplingMethod === "time") { + // Calculate the x-coordinate for the current sample based on the SVG's width + var x = (svgWidth / (numSamples - 1)) * i; + // Find the point on the path that intersects the vertical line at the calculated x-coordinate + var point = findPointAtX(svgPathElement, x, pathLength); + } + else if (samplingMethod === "path") { + // Calculate the distance along the path for the current sample + var distance = (pathLength / (numSamples - 1)) * i; + // Get the point at the current distance + var point = svgPathElement.getPointAtLength(distance); + } // Add the point to the array of points points.push({ x: point.x, y: point.y }); @@ -514,6 +533,24 @@ function samplePointsTime(svgPathElement, numSamples) { return points; } +function findClosestPoints(points, clickedPoint) { + // Calculate distances from clickedPoint to each point in the array + let distances = points.map(point => { + let dx = clickedPoint.x - point.x; + let dy = clickedPoint.y - point.y; + return { index: points.indexOf(point), distance: Math.sqrt(dx * dx + dy * dy) }; + }); + // Sort distances and get the indices of the two closest points + let sortedDistances = distances.sort((a, b) => a.distance - b.distance); + let closestPoint1Index = sortedDistances[0].index; + let closestPoint2Index = sortedDistances[1].index; + // Ensure point1Index is always the smaller index + if (closestPoint1Index > closestPoint2Index) { + [closestPoint1Index, closestPoint2Index] = [closestPoint2Index, closestPoint1Index]; + } + return { point1Index: closestPoint1Index, point2Index: closestPoint2Index }; +} + function findPointAtX(svgPathElement, targetX, pathLength) { let low = 0; let high = pathLength; From 6669c93efcb2a373b3f897a348ad67f55bce381a Mon Sep 17 00:00:00 2001 From: Kijai <40791699+kijai@users.noreply.github.com> Date: Mon, 29 Apr 2024 17:45:58 +0300 Subject: [PATCH 58/95] Add experimental GLIGEN node --- __init__.py | 1 + nodes/curve_nodes.py | 118 +++++++++++++++++++++++++++++++++++++++- web/js/spline_editor.js | 4 +- 3 files changed, 118 insertions(+), 5 deletions(-) diff --git a/__init__.py b/__init__.py index 09f0957..dfbf668 100644 --- a/__init__.py +++ b/__init__.py @@ -102,6 +102,7 @@ NODE_CLASS_MAPPINGS = { "LoadResAdapterNormalization": LoadResAdapterNormalization, "Superprompt": Superprompt, "GLIGENTextBoxApplyBatch": GLIGENTextBoxApplyBatch, + "GLIGENTextBoxApplyBatchCoords": GLIGENTextBoxApplyBatchCoords, "Intrinsic_lora_sampling": Intrinsic_lora_sampling, } diff --git a/nodes/curve_nodes.py b/nodes/curve_nodes.py index f673081..8cac723 100644 --- a/nodes/curve_nodes.py +++ b/nodes/curve_nodes.py @@ -55,7 +55,8 @@ class SplineEditor: } } - RETURN_TYPES = ("MASK", "STRING", "FLOAT") + RETURN_TYPES = ("MASK", "STRING", "FLOAT", "INT") + RETURN_NAMES = ("mask", "string", "float", "count") FUNCTION = "splinedata" CATEGORY = "KJNodes/experimental" DESCRIPTION = """ @@ -126,7 +127,7 @@ output types: masks_out = torch.stack(mask_tensors) masks_out = masks_out.repeat(repeat_output, 1, 1, 1) masks_out = masks_out.mean(dim=-1) - return (masks_out, str(coordinates), out_floats,) + return (masks_out, str(coordinates), out_floats, len(out_floats)) class CreateShapeMaskOnPath: @@ -485,4 +486,115 @@ Creates a sigmas tensor from list of float values. """ def customsigmas(self, float_list): - return torch.tensor(float_list, dtype=torch.float32), \ No newline at end of file + return torch.tensor(float_list, dtype=torch.float32), + +class GLIGENTextBoxApplyBatchCoords: + @classmethod + def INPUT_TYPES(s): + return {"required": {"conditioning_to": ("CONDITIONING", ), + "latents": ("LATENT", ), + "clip": ("CLIP", ), + "gligen_textbox_model": ("GLIGEN", ), + "coordinates": ("STRING", {"forceInput": True}), + "text": ("STRING", {"multiline": True}), + "width": ("INT", {"default": 64, "min": 8, "max": 4096, "step": 8}), + "height": ("INT", {"default": 64, "min": 8, "max": 4096, "step": 8}), + }, + } + RETURN_TYPES = ("CONDITIONING", "IMAGE", ) + FUNCTION = "append" + CATEGORY = "KJNodes/experimental" + DESCRIPTION = """ +Experimental, does not function yet as ComfyUI base changes are needed +""" + + def append(self, latents, coordinates, conditioning_to, clip, gligen_textbox_model, text, width, height): + coordinates = json.loads(coordinates.replace("'", '"')) + coordinates = [(coord['x'], coord['y']) for coord in coordinates] + + batch_size = sum(tensor.size(0) for tensor in latents.values()) + assert len(coordinates) == batch_size, "The number of coordinates does not match the number of latents" + c = [] + cond, cond_pooled = clip.encode_from_tokens(clip.tokenize(text), return_pooled=True) + + image_height = latents['samples'].shape[-1] * 8 + image_width = latents['samples'].shape[-2] * 8 + plot_image_tensor = self.plot_coordinates_to_tensor(coordinates, image_height, image_width, height) + + for t in conditioning_to: + n = [t[0], t[1].copy()] + + position_params_batch = [[] for _ in range(batch_size)] # Initialize a list of empty lists for each batch item + + for i in range(batch_size): + x_position, y_position = coordinates[i] + position_param = (cond_pooled, height // 8, width // 8, y_position // 8, x_position // 8) + position_params_batch[i].append(position_param) # Append position_param to the correct sublist + + prev = [] + if "gligen" in n[1]: + prev = n[1]['gligen'][2] + else: + prev = [[] for _ in range(batch_size)] + # Concatenate prev and position_params_batch, ensuring both are lists of lists + # and each sublist corresponds to a batch item + combined_position_params = [prev_item + batch_item for prev_item, batch_item in zip(prev, position_params_batch)] + n[1]['gligen'] = ("position", gligen_textbox_model, combined_position_params) + c.append(n) + + return (c, plot_image_tensor,) + + def plot_coordinates_to_tensor(self, coordinates, height, width, box_size): + import matplotlib + matplotlib.use('Agg') + from matplotlib.backends.backend_agg import FigureCanvasAgg as FigureCanvas + + # Convert coordinates to separate x and y lists + #x_coords, y_coords = zip(*coordinates) + + fig, ax = matplotlib.pyplot.subplots(figsize=(width/100, height/100), dpi=100) + #ax.scatter(x_coords, y_coords, color='yellow', label='_nolegend_') + + # Draw a box at each coordinate + for x, y in coordinates: + rect = matplotlib.patches.Rectangle((x - box_size/2, y - box_size/2), box_size, box_size, + linewidth=1, edgecolor='green', facecolor='none', alpha=0.5) + ax.add_patch(rect) + + # Draw arrows from one point to another to indicate direction + for i in range(len(coordinates) - 1): + x1, y1 = coordinates[i] + x2, y2 = coordinates[i + 1] + ax.annotate("", xy=(x2, y2), xytext=(x1, y1), + arrowprops=dict(arrowstyle="->", + linestyle="-", + lw=1, + color='orange', + mutation_scale=10)) + matplotlib.pyplot.rcParams['text.color'] = '#999999' + fig.patch.set_facecolor('#353535') + ax.set_facecolor('#353535') + ax.grid(color='#999999', linestyle='-', linewidth=0.5) + ax.set_xlabel('x', color='#999999') + ax.set_ylabel('y', color='#999999') + for text in ax.get_xticklabels() + ax.get_yticklabels(): + text.set_color('#999999') + ax.set_title('Gligen positions') + ax.set_xlabel('X Coordinate') + ax.set_ylabel('Y Coordinate') + ax.legend().remove() + ax.set_xlim(0, width) # Set the x-axis to match the input latent width + ax.set_ylim(height, 0) # Set the y-axis to match the input latent height, with (0,0) at top-left + # Adjust the margins of the subplot + matplotlib.pyplot.subplots_adjust(left=0.08, right=0.95, bottom=0.05, top=0.95, wspace=0.2, hspace=0.2) + canvas = FigureCanvas(fig) + canvas.draw() + matplotlib.pyplot.close(fig) + + width, height = fig.get_size_inches() * fig.get_dpi() + + image_np = np.frombuffer(canvas.tostring_rgb(), dtype='uint8').reshape(int(height), int(width), 3) + image_tensor = torch.from_numpy(image_np).float() / 255.0 + image_tensor = image_tensor.unsqueeze(0) + + return image_tensor \ No newline at end of file diff --git a/web/js/spline_editor.js b/web/js/spline_editor.js index 27a5e46..5aa9a0e 100644 --- a/web/js/spline_editor.js +++ b/web/js/spline_editor.js @@ -181,7 +181,7 @@ app.registerExtension({ } }); - this.setSize([550, 900]); + this.setSize([550, 920]); this.resizable = false; this.splineEditor.parentEl = document.createElement("div"); this.splineEditor.parentEl.className = "spline-editor"; @@ -190,7 +190,7 @@ app.registerExtension({ chainCallback(this, "onGraphConfigured", function() { createSplineEditor(this); - this.setSize([550, 900]); + this.setSize([550, 920]); }); }); // onAfterGraphConfigured From 9112777eb157ca163285b772926b7487f6fa4c94 Mon Sep 17 00:00:00 2001 From: Kijai <40791699+kijai@users.noreply.github.com> Date: Mon, 29 Apr 2024 18:33:10 +0300 Subject: [PATCH 59/95] Update curve_nodes.py --- nodes/curve_nodes.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/nodes/curve_nodes.py b/nodes/curve_nodes.py index 8cac723..0d81fa0 100644 --- a/nodes/curve_nodes.py +++ b/nodes/curve_nodes.py @@ -517,9 +517,9 @@ Experimental, does not function yet as ComfyUI base changes are needed c = [] cond, cond_pooled = clip.encode_from_tokens(clip.tokenize(text), return_pooled=True) - image_height = latents['samples'].shape[-1] * 8 - image_width = latents['samples'].shape[-2] * 8 - plot_image_tensor = self.plot_coordinates_to_tensor(coordinates, image_height, image_width, height) + image_height = latents['samples'].shape[-2] * 8 + image_width = latents['samples'].shape[-1] * 8 + plot_image_tensor = self.plot_coordinates_to_tensor(coordinates, image_height, image_width, height, text) for t in conditioning_to: n = [t[0], t[1].copy()] @@ -544,7 +544,7 @@ Experimental, does not function yet as ComfyUI base changes are needed return (c, plot_image_tensor,) - def plot_coordinates_to_tensor(self, coordinates, height, width, box_size): + def plot_coordinates_to_tensor(self, coordinates, height, width, box_size, prompt): import matplotlib matplotlib.use('Agg') from matplotlib.backends.backend_agg import FigureCanvasAgg as FigureCanvas @@ -579,7 +579,7 @@ Experimental, does not function yet as ComfyUI base changes are needed ax.set_ylabel('y', color='#999999') for text in ax.get_xticklabels() + ax.get_yticklabels(): text.set_color('#999999') - ax.set_title('Gligen positions') + ax.set_title('Gligen pos for: ' + prompt) ax.set_xlabel('X Coordinate') ax.set_ylabel('Y Coordinate') ax.legend().remove() From 4685bfe3f3b5c5fc5dddb07dc01e0b0582156ba5 Mon Sep 17 00:00:00 2001 From: kijai <40791699+kijai@users.noreply.github.com> Date: Tue, 30 Apr 2024 22:55:46 +0300 Subject: [PATCH 60/95] Fix font path --- nodes/curve_nodes.py | 2 +- nodes/nodes.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/nodes/curve_nodes.py b/nodes/curve_nodes.py index 0d81fa0..ccb9199 100644 --- a/nodes/curve_nodes.py +++ b/nodes/curve_nodes.py @@ -56,7 +56,7 @@ class SplineEditor: } RETURN_TYPES = ("MASK", "STRING", "FLOAT", "INT") - RETURN_NAMES = ("mask", "string", "float", "count") + RETURN_NAMES = ("mask", "coord_str", "float", "count") FUNCTION = "splinedata" CATEGORY = "KJNodes/experimental" DESCRIPTION = """ diff --git a/nodes/nodes.py b/nodes/nodes.py index 14df121..a06f6d9 100644 --- a/nodes/nodes.py +++ b/nodes/nodes.py @@ -22,7 +22,7 @@ import comfy.sample import folder_paths from ..utility.utility import tensor2pil, pil2tensor -script_directory = os.path.dirname(os.path.abspath(__file__)) +script_directory = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) folder_paths.add_model_folder_path("kjnodes_fonts", os.path.join(script_directory, "fonts")) class AnyType(str): From 2405a6192a1c568027966bd5f2f2c51e246898f1 Mon Sep 17 00:00:00 2001 From: kijai <40791699+kijai@users.noreply.github.com> Date: Wed, 1 May 2024 09:24:19 +0300 Subject: [PATCH 61/95] Update nodes.py --- nodes/nodes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nodes/nodes.py b/nodes/nodes.py index a06f6d9..1d1e7e7 100644 --- a/nodes/nodes.py +++ b/nodes/nodes.py @@ -1095,7 +1095,7 @@ with the **inputcount** and clicking update. image = kwargs["image_1"] for c in range(1, inputcount): new_image = kwargs[f"image_{c + 1}"] - image, = image_batch_node.batch(new_image, image) + image, = image_batch_node.batch(image, new_image) return (image,) class MaskBatchMulti: From edae7ef9d23876b4414420eb925903046fc16eb2 Mon Sep 17 00:00:00 2001 From: kijai <40791699+kijai@users.noreply.github.com> Date: Wed, 1 May 2024 10:51:49 +0300 Subject: [PATCH 62/95] Add Split/Merge image channels -nodes --- __init__.py | 4 ++++ nodes/nodes.py | 63 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+) diff --git a/__init__.py b/__init__.py index dfbf668..0c435a1 100644 --- a/__init__.py +++ b/__init__.py @@ -56,6 +56,8 @@ NODE_CLASS_MAPPINGS = { "ImagePass": ImagePass, "ImagePadForOutpaintMasked": ImagePadForOutpaintMasked, "ImageAndMaskPreview": ImageAndMaskPreview, + "SplitImageChannels": SplitImageChannels, + "MergeImageChannels": MergeImageChannels, #batch cropping "BatchCropFromMask": BatchCropFromMask, "BatchCropFromMaskAdvanced": BatchCropFromMaskAdvanced, @@ -194,6 +196,8 @@ NODE_DISPLAY_NAME_MAPPINGS = { "FloatToSigmas": "Float To Sigmas", "CustomSigmas": "Custom Sigmas", "ImagePass": "ImagePass", + "SplitImageChannels": "Split Image Channels", + "MergeImageChannels": "Merge Image Channels", #curve nodes "SplineEditor": "Spline Editor", "CreateShapeMaskOnPath": "Create Shape Mask On Path", diff --git a/nodes/nodes.py b/nodes/nodes.py index 1d1e7e7..9c2bab9 100644 --- a/nodes/nodes.py +++ b/nodes/nodes.py @@ -3558,7 +3558,70 @@ Remaps the image values to the specified range. if clamp: image = torch.clamp(image, min=0.0, max=1.0) return (image, ) + +class SplitImageChannels: + @classmethod + def INPUT_TYPES(s): + return {"required": { + "image": ("IMAGE",), + }, + } + RETURN_TYPES = ("IMAGE", "IMAGE", "IMAGE", "MASK") + RETURN_NAMES = ("red", "green", "blue", "mask") + FUNCTION = "split" + CATEGORY = "KJNodes/image" + DESCRIPTION = """ +Splits image channels into images where the selected channel +is repeated for all channels, and the alpha as a mask. +""" + + def split(self, image): + red = image[:, :, :, 0:1] # Red channel + green = image[:, :, :, 1:2] # Green channel + blue = image[:, :, :, 2:3] # Blue channel + alpha = image[:, :, :, 3:4] # Alpha channel + alpha = alpha.squeeze(-1) + + # Repeat the selected channel for all channels + red = torch.cat([red, red, red], dim=3) + green = torch.cat([green, green, green], dim=3) + blue = torch.cat([blue, blue, blue], dim=3) + return (red, green, blue, alpha) + +class MergeImageChannels: + @classmethod + def INPUT_TYPES(s): + return {"required": { + "red": ("IMAGE",), + "green": ("IMAGE",), + "blue": ("IMAGE",), + + }, + "optional": { + "mask": ("MASK", {"default": None}), + }, + } + + RETURN_TYPES = ("IMAGE",) + RETURN_NAMES = ("image",) + FUNCTION = "merge" + CATEGORY = "KJNodes/image" + DESCRIPTION = """ +Merges channel data into an image. +""" + + def merge(self, red, green, blue, alpha=None): + image = torch.stack([ + red[..., 0, None], # Red channel + green[..., 1, None], # Green channel + blue[..., 2, None] # Blue channel + ], dim=-1) + image = image.squeeze(-2) + if alpha is not None: + image = torch.cat([image, alpha], dim=-1) + return (image,) + class CameraPoseVisualizer: @classmethod From f259e062c7fef42fd868dafa5705bf9bd8c66f22 Mon Sep 17 00:00:00 2001 From: kijai <40791699+kijai@users.noreply.github.com> Date: Wed, 1 May 2024 18:44:30 +0300 Subject: [PATCH 63/95] New nodes and continue restructuring --- __init__.py | 2 + nodes/curve_nodes.py | 30 +- nodes/image_nodes.py | 1032 +++++++++++++++++++++++++++++++++++++++ nodes/nodes.py | 1100 +++--------------------------------------- web/js/jsnodes.js | 22 + 5 files changed, 1139 insertions(+), 1047 deletions(-) create mode 100644 nodes/image_nodes.py diff --git a/__init__.py b/__init__.py index 0c435a1..fd2bba9 100644 --- a/__init__.py +++ b/__init__.py @@ -2,6 +2,7 @@ from .nodes.nodes import * from .nodes.curve_nodes import * from .nodes.batchcrop_nodes import * from .nodes.audioscheduler_nodes import * +from .nodes.image_nodes import * NODE_CLASS_MAPPINGS = { #constants "INTConstant": INTConstant, @@ -80,6 +81,7 @@ NODE_CLASS_MAPPINGS = { "ScaleBatchPromptSchedule": ScaleBatchPromptSchedule, "CameraPoseVisualizer": CameraPoseVisualizer, "JoinStrings": JoinStrings, + "JoinStringMulti": JoinStringMulti, "Sleep": Sleep, "VRAM_Debug" : VRAM_Debug, "SomethingToString" : SomethingToString, diff --git a/nodes/curve_nodes.py b/nodes/curve_nodes.py index ccb9199..668ed23 100644 --- a/nodes/curve_nodes.py +++ b/nodes/curve_nodes.py @@ -217,6 +217,7 @@ class MaskOrImageToWeight: 'list', 'pandas series', 'tensor', + 'string' ], { "default": 'list' @@ -228,7 +229,7 @@ class MaskOrImageToWeight: }, } - RETURN_TYPES = ("FLOAT",) + RETURN_TYPES = ("FLOAT", "STRING",) FUNCTION = "execute" CATEGORY = "KJNodes" DESCRIPTION = """ @@ -249,18 +250,17 @@ and returns that as the selected output type. # Convert mean_values to the specified output_type if output_type == 'list': - return mean_values, + out = mean_values, elif output_type == 'pandas series': try: import pandas as pd except: raise Exception("MaskOrImageToWeight: pandas is not installed. Please install pandas to use this output_type") - return pd.Series(mean_values), + out = pd.Series(mean_values), elif output_type == 'tensor': - return torch.tensor(mean_values, dtype=torch.float32), - else: - raise ValueError(f"Unsupported output_type: {output_type}") - + out = torch.tensor(mean_values, dtype=torch.float32), + return (out, [str(value) for value in mean_values],) + class WeightScheduleConvert: @classmethod @@ -287,7 +287,7 @@ class WeightScheduleConvert: }, } - RETURN_TYPES = ("FLOAT",) + RETURN_TYPES = ("FLOAT", "STRING",) FUNCTION = "execute" CATEGORY = "KJNodes" DESCRIPTION = """ @@ -344,18 +344,18 @@ Converts different value lists/series to another type. float_values = float_values * repeat if output_type == 'list': - return float_values, + out = float_values, elif output_type == 'pandas series': - return pd.Series(float_values), + out = pd.Series(float_values), elif output_type == 'tensor': if input_type == 'pandas series': - return torch.tensor(float_values.values, dtype=torch.float32), + out = torch.tensor(float_values.values, dtype=torch.float32), else: - return torch.tensor(float_values, dtype=torch.float32), + out = torch.tensor(float_values, dtype=torch.float32), elif output_type == 'match_input': - return float_values, - else: - raise ValueError(f"Unsupported output_type: {output_type}") + out = float_values, + return (out, [str(value) for value in float_values],) + class FloatToMask: diff --git a/nodes/image_nodes.py b/nodes/image_nodes.py new file mode 100644 index 0000000..042a612 --- /dev/null +++ b/nodes/image_nodes.py @@ -0,0 +1,1032 @@ +import numpy as np +import time +import torch +import torch.nn.functional as F +import random +import math +import os +import re +import json +from PIL import ImageGrab, ImageDraw, ImageFont, Image + +from nodes import MAX_RESOLUTION, SaveImage +from comfy_extras.nodes_mask import ImageCompositeMasked +from comfy.cli_args import args +from comfy.utils import ProgressBar +import folder_paths +import model_management + +script_directory = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + +class ImagePass: + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "image": ("IMAGE",), + }, + } + RETURN_TYPES = ("IMAGE",) + FUNCTION = "passthrough" + CATEGORY = "KJNodes/misc" + DESCRIPTION = """ +Passes the image through without modifying it. +""" + + def passthrough(self, image): + return image, + +class ColorMatch: + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "image_ref": ("IMAGE",), + "image_target": ("IMAGE",), + "method": ( + [ + 'mkl', + 'hm', + 'reinhard', + 'mvgd', + 'hm-mvgd-hm', + 'hm-mkl-hm', + ], { + "default": 'mkl' + }), + + }, + } + + CATEGORY = "KJNodes/image" + + RETURN_TYPES = ("IMAGE",) + RETURN_NAMES = ("image",) + FUNCTION = "colormatch" + DESCRIPTION = """ +color-matcher enables color transfer across images which comes in handy for automatic +color-grading of photographs, paintings and film sequences as well as light-field +and stopmotion corrections. + +The methods behind the mappings are based on the approach from Reinhard et al., +the Monge-Kantorovich Linearization (MKL) as proposed by Pitie et al. and our analytical solution +to a Multi-Variate Gaussian Distribution (MVGD) transfer in conjunction with classical histogram +matching. As shown below our HM-MVGD-HM compound outperforms existing methods. +https://github.com/hahnec/color-matcher/ + +""" + + def colormatch(self, image_ref, image_target, method): + try: + from color_matcher import ColorMatcher + except: + raise Exception("Can't import color-matcher, did you install requirements.txt? Manual install: pip install color-matcher") + cm = ColorMatcher() + image_ref = image_ref.cpu() + image_target = image_target.cpu() + batch_size = image_target.size(0) + out = [] + images_target = image_target.squeeze() + images_ref = image_ref.squeeze() + + image_ref_np = images_ref.numpy() + images_target_np = images_target.numpy() + + if image_ref.size(0) > 1 and image_ref.size(0) != batch_size: + raise ValueError("ColorMatch: Use either single reference image or a matching batch of reference images.") + + for i in range(batch_size): + image_target_np = images_target_np if batch_size == 1 else images_target[i].numpy() + image_ref_np_i = image_ref_np if image_ref.size(0) == 1 else images_ref[i].numpy() + try: + image_result = cm.transfer(src=image_target_np, ref=image_ref_np_i, method=method) + except BaseException as e: + print(f"Error occurred during transfer: {e}") + break + out.append(torch.from_numpy(image_result)) + return (torch.stack(out, dim=0).to(torch.float32), ) + +class SaveImageWithAlpha: + def __init__(self): + self.output_dir = folder_paths.get_output_directory() + self.type = "output" + self.prefix_append = "" + + @classmethod + def INPUT_TYPES(s): + return {"required": + {"images": ("IMAGE", ), + "mask": ("MASK", ), + "filename_prefix": ("STRING", {"default": "ComfyUI"})}, + "hidden": {"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO"}, + } + + RETURN_TYPES = () + FUNCTION = "save_images_alpha" + OUTPUT_NODE = True + CATEGORY = "KJNodes/image" + DESCRIPTION = """ +Saves an image and mask as .PNG with the mask as the alpha channel. +""" + + def save_images_alpha(self, images, mask, filename_prefix="ComfyUI_image_with_alpha", prompt=None, extra_pnginfo=None): + from PIL.PngImagePlugin import PngInfo + filename_prefix += self.prefix_append + full_output_folder, filename, counter, subfolder, filename_prefix = folder_paths.get_save_image_path(filename_prefix, self.output_dir, images[0].shape[1], images[0].shape[0]) + results = list() + if mask.dtype == torch.float16: + mask = mask.to(torch.float32) + def file_counter(): + max_counter = 0 + # Loop through the existing files + for existing_file in os.listdir(full_output_folder): + # Check if the file matches the expected format + match = re.fullmatch(fr"{filename}_(\d+)_?\.[a-zA-Z0-9]+", existing_file) + if match: + # Extract the numeric portion of the filename + file_counter = int(match.group(1)) + # Update the maximum counter value if necessary + if file_counter > max_counter: + max_counter = file_counter + return max_counter + + for image, alpha in zip(images, mask): + i = 255. * image.cpu().numpy() + a = 255. * alpha.cpu().numpy() + img = Image.fromarray(np.clip(i, 0, 255).astype(np.uint8)) + + # Resize the mask to match the image size + a_resized = Image.fromarray(a).resize(img.size, Image.LANCZOS) + a_resized = np.clip(a_resized, 0, 255).astype(np.uint8) + img.putalpha(Image.fromarray(a_resized, mode='L')) + metadata = None + if not args.disable_metadata: + metadata = PngInfo() + if prompt is not None: + metadata.add_text("prompt", json.dumps(prompt)) + if extra_pnginfo is not None: + for x in extra_pnginfo: + metadata.add_text(x, json.dumps(extra_pnginfo[x])) + + # Increment the counter by 1 to get the next available value + counter = file_counter() + 1 + file = f"{filename}_{counter:05}.png" + img.save(os.path.join(full_output_folder, file), pnginfo=metadata, compress_level=4) + results.append({ + "filename": file, + "subfolder": subfolder, + "type": self.type + }) + + return { "ui": { "images": results } } + +class ImageConcanate: + @classmethod + def INPUT_TYPES(s): + return {"required": { + "image1": ("IMAGE",), + "image2": ("IMAGE",), + "direction": ( + [ 'right', + 'down', + 'left', + 'up', + ], + { + "default": 'right' + }), + "match_image_size": ("BOOLEAN", {"default": False}), + }} + + RETURN_TYPES = ("IMAGE",) + FUNCTION = "concanate" + CATEGORY = "KJNodes/image" + DESCRIPTION = """ +Concatenates the image2 to image1 in the specified direction. +""" + + def concanate(self, image1, image2, direction, match_image_size): + if match_image_size: + image2 = torch.nn.functional.interpolate(image2, size=(image1.shape[2], image1.shape[3]), mode="bilinear") + if direction == 'right': + row = torch.cat((image1, image2), dim=2) + elif direction == 'down': + row = torch.cat((image1, image2), dim=1) + elif direction == 'left': + row = torch.cat((image2, image1), dim=2) + elif direction == 'up': + row = torch.cat((image2, image1), dim=1) + return (row,) + +class ImageGridComposite2x2: + @classmethod + def INPUT_TYPES(s): + return {"required": { + "image1": ("IMAGE",), + "image2": ("IMAGE",), + "image3": ("IMAGE",), + "image4": ("IMAGE",), + }} + + RETURN_TYPES = ("IMAGE",) + FUNCTION = "compositegrid" + CATEGORY = "KJNodes/image" + DESCRIPTION = """ +Concatenates the 4 input images into a 2x2 grid. +""" + + def compositegrid(self, image1, image2, image3, image4): + top_row = torch.cat((image1, image2), dim=2) + bottom_row = torch.cat((image3, image4), dim=2) + grid = torch.cat((top_row, bottom_row), dim=1) + return (grid,) + +class ImageGridComposite3x3: + @classmethod + def INPUT_TYPES(s): + return {"required": { + "image1": ("IMAGE",), + "image2": ("IMAGE",), + "image3": ("IMAGE",), + "image4": ("IMAGE",), + "image5": ("IMAGE",), + "image6": ("IMAGE",), + "image7": ("IMAGE",), + "image8": ("IMAGE",), + "image9": ("IMAGE",), + }} + + RETURN_TYPES = ("IMAGE",) + FUNCTION = "compositegrid" + CATEGORY = "KJNodes/image" + DESCRIPTION = """ +Concatenates the 9 input images into a 3x3 grid. +""" + + def compositegrid(self, image1, image2, image3, image4, image5, image6, image7, image8, image9): + top_row = torch.cat((image1, image2, image3), dim=2) + mid_row = torch.cat((image4, image5, image6), dim=2) + bottom_row = torch.cat((image7, image8, image9), dim=2) + grid = torch.cat((top_row, mid_row, bottom_row), dim=1) + return (grid,) + +class ImageBatchTestPattern: + @classmethod + def INPUT_TYPES(s): + return {"required": { + "batch_size": ("INT", {"default": 1,"min": 1, "max": 255, "step": 1}), + "start_from": ("INT", {"default": 0,"min": 0, "max": 255, "step": 1}), + "text_x": ("INT", {"default": 256,"min": 0, "max": 4096, "step": 1}), + "text_y": ("INT", {"default": 256,"min": 0, "max": 4096, "step": 1}), + "width": ("INT", {"default": 512,"min": 16, "max": 4096, "step": 1}), + "height": ("INT", {"default": 512,"min": 16, "max": 4096, "step": 1}), + "font": (folder_paths.get_filename_list("kjnodes_fonts"), ), + "font_size": ("INT", {"default": 255,"min": 8, "max": 4096, "step": 1}), + }} + + RETURN_TYPES = ("IMAGE",) + FUNCTION = "generatetestpattern" + CATEGORY = "KJNodes/text" + + def generatetestpattern(self, batch_size, font, font_size, start_from, width, height, text_x, text_y): + out = [] + # Generate the sequential numbers for each image + numbers = np.arange(start_from, start_from + batch_size) + font_path = folder_paths.get_full_path("kjnodes_fonts", font) + + for number in numbers: + # Create a black image with the number as a random color text + image = Image.new("RGB", (width, height), color='black') + draw = ImageDraw.Draw(image) + + # Generate a random color for the text + font_color = (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255)) + + font = ImageFont.truetype(font_path, font_size) + + # Get the size of the text and position it in the center + text = str(number) + + try: + draw.text((text_x, text_y), text, font=font, fill=font_color, features=['-liga']) + except: + draw.text((text_x, text_y), text, font=font, fill=font_color,) + + # Convert the image to a numpy array and normalize the pixel values + image_np = np.array(image).astype(np.float32) / 255.0 + image_tensor = torch.from_numpy(image_np).unsqueeze(0) + out.append(image_tensor) + out_tensor = torch.cat(out, dim=0) + + return (out_tensor,) + +class ImageGrabPIL: + + @classmethod + def IS_CHANGED(cls): + + return + + RETURN_TYPES = ("IMAGE",) + RETURN_NAMES = ("image",) + FUNCTION = "screencap" + CATEGORY = "KJNodes/experimental" + DESCRIPTION = """ +Captures an area specified by screen coordinates. +Can be used for realtime diffusion with autoqueue. +""" + + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "x": ("INT", {"default": 0,"min": 0, "max": 4096, "step": 1}), + "y": ("INT", {"default": 0,"min": 0, "max": 4096, "step": 1}), + "width": ("INT", {"default": 512,"min": 0, "max": 4096, "step": 1}), + "height": ("INT", {"default": 512,"min": 0, "max": 4096, "step": 1}), + "num_frames": ("INT", {"default": 1,"min": 1, "max": 255, "step": 1}), + "delay": ("FLOAT", {"default": 0.1,"min": 0.0, "max": 10.0, "step": 0.01}), + }, + } + + def screencap(self, x, y, width, height, num_frames, delay): + captures = [] + bbox = (x, y, x + width, y + height) + + for _ in range(num_frames): + # Capture screen + screen_capture = ImageGrab.grab(bbox=bbox) + screen_capture_torch = torch.tensor(np.array(screen_capture), dtype=torch.float32) / 255.0 + screen_capture_torch = screen_capture_torch.unsqueeze(0) + captures.append(screen_capture_torch) + + # Wait for a short delay if more than one frame is to be captured + if num_frames > 1: + time.sleep(delay) + + return (torch.cat(captures, dim=0),) + +class AddLabel: + @classmethod + def INPUT_TYPES(s): + return {"required": { + "image":("IMAGE",), + "text_x": ("INT", {"default": 10, "min": 0, "max": 4096, "step": 1}), + "text_y": ("INT", {"default": 2, "min": 0, "max": 4096, "step": 1}), + "height": ("INT", {"default": 48, "min": 0, "max": 4096, "step": 1}), + "font_size": ("INT", {"default": 32, "min": 0, "max": 4096, "step": 1}), + "font_color": ("STRING", {"default": "white"}), + "label_color": ("STRING", {"default": "black"}), + "font": (folder_paths.get_filename_list("kjnodes_fonts"), ), + "text": ("STRING", {"default": "Text"}), + "direction": ( + [ 'up', + 'down', + 'left', + 'right', + 'overlay' + ], + { + "default": 'up' + }), + }, + "optional":{ + "caption": ("STRING", {"default": "", "forceInput": True}), + } + } + + RETURN_TYPES = ("IMAGE",) + FUNCTION = "addlabel" + CATEGORY = "KJNodes/text" + DESCRIPTION = """ +Creates a new with the given text, and concatenates it to +either above or below the input image. +Note that this changes the input image's height! +Fonts are loaded from this folder: +ComfyUI/custom_nodes/ComfyUI-KJNodes/fonts +""" + + def addlabel(self, image, text_x, text_y, text, height, font_size, font_color, label_color, font, direction, caption=""): + batch_size = image.shape[0] + width = image.shape[2] + + font_path = os.path.join(script_directory, "fonts", "TTNorms-Black.otf") if font == "TTNorms-Black.otf" else folder_paths.get_full_path("kjnodes_fonts", font) + + def process_image(input_image, caption_text): + if direction == 'overlay': + pil_image = Image.fromarray((input_image.cpu().numpy() * 255).astype(np.uint8)) + draw = ImageDraw.Draw(pil_image) + font = ImageFont.truetype(font_path, font_size) + try: + draw.text((text_x, text_y), caption_text, font=font, fill=font_color, features=['-liga']) + except: + draw.text((text_x, text_y), caption_text, font=font, fill=font_color) + processed_image = torch.from_numpy(np.array(pil_image).astype(np.float32) / 255.0).unsqueeze(0) + else: + label_image = Image.new("RGB", (width, height), label_color) + draw = ImageDraw.Draw(label_image) + font = ImageFont.truetype(font_path, font_size) + try: + draw.text((text_x, text_y), caption_text, font=font, fill=font_color, features=['-liga']) + except: + draw.text((text_x, text_y), caption_text, font=font, fill=font_color) + processed_image = torch.from_numpy(np.array(label_image).astype(np.float32) / 255.0)[None, :, :, :] + return processed_image + + if caption == "": + processed_images = [process_image(img, text) for img in image] + else: + assert len(caption) == batch_size, "Number of captions does not match number of images" + processed_images = [process_image(img, cap) for img, cap in zip(image, caption)] + processed_batch = torch.cat(processed_images, dim=0) + + # Combine images based on direction + if direction == 'down': + combined_images = torch.cat((image, processed_batch), dim=1) + elif direction == 'up': + combined_images = torch.cat((processed_batch, image), dim=1) + elif direction == 'left': + processed_batch = torch.rot90(processed_batch, 3, (2, 3)).permute(0, 3, 1, 2) + combined_images = torch.cat((processed_batch, image), dim=2) + elif direction == 'right': + processed_batch = torch.rot90(processed_batch, 3, (2, 3)).permute(0, 3, 1, 2) + combined_images = torch.cat((image, processed_batch), dim=2) + else: + combined_images = processed_batch + + return (combined_images,) + +class ImageBatchRepeatInterleaving: + + RETURN_TYPES = ("IMAGE",) + FUNCTION = "repeat" + CATEGORY = "KJNodes/image" + DESCRIPTION = """ +Repeats each image in a batch by the specified number of times. +Example batch of 5 images: 0, 1 ,2, 3, 4 +with repeats 2 becomes batch of 10 images: 0, 0, 1, 1, 2, 2, 3, 3, 4, 4 +""" + + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "images": ("IMAGE",), + "repeats": ("INT", {"default": 1, "min": 1, "max": 4096}), + }, + } + + def repeat(self, images, repeats): + + repeated_images = torch.repeat_interleave(images, repeats=repeats, dim=0) + return (repeated_images, ) + +class ImageUpscaleWithModelBatched: + @classmethod + def INPUT_TYPES(s): + return {"required": { "upscale_model": ("UPSCALE_MODEL",), + "images": ("IMAGE",), + "per_batch": ("INT", {"default": 16, "min": 1, "max": 4096, "step": 1}), + }} + RETURN_TYPES = ("IMAGE",) + FUNCTION = "upscale" + CATEGORY = "KJNodes/image" + DESCRIPTION = """ +Same as ComfyUI native model upscaling node, +but allows setting sub-batches for reduced VRAM usage. +""" + def upscale(self, upscale_model, images, per_batch): + + device = model_management.get_torch_device() + upscale_model.to(device) + in_img = images.movedim(-1,-3).to(device) + + steps = in_img.shape[0] + pbar = ProgressBar(steps) + t = [] + + for start_idx in range(0, in_img.shape[0], per_batch): + sub_images = upscale_model(in_img[start_idx:start_idx+per_batch]) + t.append(sub_images.cpu()) + # Calculate the number of images processed in this batch + batch_count = sub_images.shape[0] + # Update the progress bar by the number of images processed in this batch + pbar.update(batch_count) + upscale_model.cpu() + + t = torch.cat(t, dim=0).permute(0, 2, 3, 1).cpu() + + return (t,) + +class ImageNormalize_Neg1_To_1: + @classmethod + def INPUT_TYPES(s): + return {"required": { + "images": ("IMAGE",), + + }} + RETURN_TYPES = ("IMAGE",) + FUNCTION = "normalize" + CATEGORY = "KJNodes/misc" + DESCRIPTION = """ +Normalize the images to be in the range [-1, 1] +""" + + def normalize(self,images): + images = images * 2.0 - 1.0 + return (images,) + +class RemapImageRange: + @classmethod + def INPUT_TYPES(s): + return {"required": { + "image": ("IMAGE",), + "min": ("FLOAT", {"default": 0.0,"min": -10.0, "max": 1.0, "step": 0.01}), + "max": ("FLOAT", {"default": 1.0,"min": 0.0, "max": 10.0, "step": 0.01}), + "clamp": ("BOOLEAN", {"default": True}), + }, + } + + RETURN_TYPES = ("IMAGE",) + FUNCTION = "remap" + CATEGORY = "KJNodes/image" + DESCRIPTION = """ +Remaps the image values to the specified range. +""" + + def remap(self, image, min, max, clamp): + if image.dtype == torch.float16: + image = image.to(torch.float32) + image = min + image * (max - min) + if clamp: + image = torch.clamp(image, min=0.0, max=1.0) + return (image, ) + +class SplitImageChannels: + @classmethod + def INPUT_TYPES(s): + return {"required": { + "image": ("IMAGE",), + }, + } + + RETURN_TYPES = ("IMAGE", "IMAGE", "IMAGE", "MASK") + RETURN_NAMES = ("red", "green", "blue", "mask") + FUNCTION = "split" + CATEGORY = "KJNodes/image" + DESCRIPTION = """ +Splits image channels into images where the selected channel +is repeated for all channels, and the alpha as a mask. +""" + + def split(self, image): + red = image[:, :, :, 0:1] # Red channel + green = image[:, :, :, 1:2] # Green channel + blue = image[:, :, :, 2:3] # Blue channel + alpha = image[:, :, :, 3:4] # Alpha channel + alpha = alpha.squeeze(-1) + + # Repeat the selected channel for all channels + red = torch.cat([red, red, red], dim=3) + green = torch.cat([green, green, green], dim=3) + blue = torch.cat([blue, blue, blue], dim=3) + return (red, green, blue, alpha) + +class MergeImageChannels: + @classmethod + def INPUT_TYPES(s): + return {"required": { + "red": ("IMAGE",), + "green": ("IMAGE",), + "blue": ("IMAGE",), + + }, + "optional": { + "mask": ("MASK", {"default": None}), + }, + } + + RETURN_TYPES = ("IMAGE",) + RETURN_NAMES = ("image",) + FUNCTION = "merge" + CATEGORY = "KJNodes/image" + DESCRIPTION = """ +Merges channel data into an image. +""" + + def merge(self, red, green, blue, alpha=None): + image = torch.stack([ + red[..., 0, None], # Red channel + green[..., 1, None], # Green channel + blue[..., 2, None] # Blue channel + ], dim=-1) + image = image.squeeze(-2) + if alpha is not None: + image = torch.cat([image, alpha], dim=-1) + return (image,) + +class ImagePadForOutpaintMasked: + + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "image": ("IMAGE",), + "left": ("INT", {"default": 0, "min": 0, "max": MAX_RESOLUTION, "step": 8}), + "top": ("INT", {"default": 0, "min": 0, "max": MAX_RESOLUTION, "step": 8}), + "right": ("INT", {"default": 0, "min": 0, "max": MAX_RESOLUTION, "step": 8}), + "bottom": ("INT", {"default": 0, "min": 0, "max": MAX_RESOLUTION, "step": 8}), + "feathering": ("INT", {"default": 40, "min": 0, "max": MAX_RESOLUTION, "step": 1}), + }, + "optional": { + "mask": ("MASK",), + } + } + + RETURN_TYPES = ("IMAGE", "MASK") + FUNCTION = "expand_image" + + CATEGORY = "image" + + def expand_image(self, image, left, top, right, bottom, feathering, mask=None): + B, H, W, C = image.size() + + new_image = torch.ones( + (B, H + top + bottom, W + left + right, C), + dtype=torch.float32, + ) * 0.5 + + new_image[:, top:top + H, left:left + W, :] = image + + if mask is None: + new_mask = torch.ones( + (H + top + bottom, W + left + right), + dtype=torch.float32, + ) + + t = torch.zeros( + (H, W), + dtype=torch.float32 + ) + else: + # If a mask is provided, pad it to fit the new image size + mask = F.pad(mask, (left, right, top, bottom), mode='constant', value=0) + mask = 1 - mask + t = torch.zeros_like(mask) + + + + if feathering > 0 and feathering * 2 < H and feathering * 2 < W: + + for i in range(H): + for j in range(W): + dt = i if top != 0 else H + db = H - i if bottom != 0 else H + + dl = j if left != 0 else W + dr = W - j if right != 0 else W + + d = min(dt, db, dl, dr) + + if d >= feathering: + continue + + v = (feathering - d) / feathering + + if mask is None: + t[i, j] = v * v + else: + t[:, top + i, left + j] = v * v + + if mask is None: + mask = new_mask.squeeze(0) + mask[top:top + H, left:left + W] = t + mask = mask.unsqueeze(0) + + return (new_image, mask,) + +class ImageAndMaskPreview(SaveImage): + def __init__(self): + self.output_dir = folder_paths.get_temp_directory() + self.type = "temp" + self.prefix_append = "_temp_" + ''.join(random.choice("abcdefghijklmnopqrstupvxyz") for x in range(5)) + self.compress_level = 4 + + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "mask_opacity": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}), + "mask_color": ("STRING", {"default": "255, 255, 255"}), + "pass_through": ("BOOLEAN", {"default": False}), + }, + "optional": { + "image": ("IMAGE",), + "mask": ("MASK",), + }, + "hidden": {"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO"}, + } + RETURN_TYPES = ("IMAGE",) + RETURN_NAMES = ("composite",) + FUNCTION = "execute" + CATEGORY = "KJNodes" + DESCRIPTION = """ +Preview an image or a mask, when both inputs are used +composites the mask on top of the image. +with pass_through on the preview is disabled and the +composite is returned from the composite slot instead, +this allows for the preview to be passed for video combine +nodes for example. +""" + + def execute(self, mask_opacity, mask_color, pass_through, filename_prefix="ComfyUI", image=None, mask=None, prompt=None, extra_pnginfo=None): + if mask is not None and image is None: + preview = mask.reshape((-1, 1, mask.shape[-2], mask.shape[-1])).movedim(1, -1).expand(-1, -1, -1, 3) + elif mask is None and image is not None: + preview = image + elif mask is not None and image is not None: + mask_adjusted = mask * mask_opacity + mask_image = mask.reshape((-1, 1, mask.shape[-2], mask.shape[-1])).movedim(1, -1).expand(-1, -1, -1, 3).clone() + + color_list = list(map(int, mask_color.split(', '))) + print(color_list[0]) + mask_image[:, :, :, 0] = color_list[0] // 255 # Red channel + mask_image[:, :, :, 1] = color_list[1] // 255 # Green channel + mask_image[:, :, :, 2] = color_list[2] // 255 # Blue channel + + preview, = ImageCompositeMasked.composite(self, image, mask_image, 0, 0, True, mask_adjusted) + if pass_through: + return (preview, ) + return(self.save_images(preview, filename_prefix, prompt, extra_pnginfo)) + +class CrossFadeImages: + + RETURN_TYPES = ("IMAGE",) + FUNCTION = "crossfadeimages" + CATEGORY = "KJNodes/image" + + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "images_1": ("IMAGE",), + "images_2": ("IMAGE",), + "interpolation": (["linear", "ease_in", "ease_out", "ease_in_out", "bounce", "elastic", "glitchy", "exponential_ease_out"],), + "transition_start_index": ("INT", {"default": 1,"min": 0, "max": 4096, "step": 1}), + "transitioning_frames": ("INT", {"default": 1,"min": 0, "max": 4096, "step": 1}), + "start_level": ("FLOAT", {"default": 0.0,"min": 0.0, "max": 1.0, "step": 0.01}), + "end_level": ("FLOAT", {"default": 1.0,"min": 0.0, "max": 1.0, "step": 0.01}), + }, + } + + def crossfadeimages(self, images_1, images_2, transition_start_index, transitioning_frames, interpolation, start_level, end_level): + + def crossfade(images_1, images_2, alpha): + crossfade = (1 - alpha) * images_1 + alpha * images_2 + return crossfade + def ease_in(t): + return t * t + def ease_out(t): + return 1 - (1 - t) * (1 - t) + def ease_in_out(t): + return 3 * t * t - 2 * t * t * t + def bounce(t): + if t < 0.5: + return self.ease_out(t * 2) * 0.5 + else: + return self.ease_in((t - 0.5) * 2) * 0.5 + 0.5 + def elastic(t): + return math.sin(13 * math.pi / 2 * t) * math.pow(2, 10 * (t - 1)) + def glitchy(t): + return t + 0.1 * math.sin(40 * t) + def exponential_ease_out(t): + return 1 - (1 - t) ** 4 + + easing_functions = { + "linear": lambda t: t, + "ease_in": ease_in, + "ease_out": ease_out, + "ease_in_out": ease_in_out, + "bounce": bounce, + "elastic": elastic, + "glitchy": glitchy, + "exponential_ease_out": exponential_ease_out, + } + + crossfade_images = [] + + alphas = torch.linspace(start_level, end_level, transitioning_frames) + for i in range(transitioning_frames): + alpha = alphas[i] + image1 = images_1[i + transition_start_index] + image2 = images_2[i + transition_start_index] + easing_function = easing_functions.get(interpolation) + alpha = easing_function(alpha) # Apply the easing function to the alpha value + + crossfade_image = crossfade(image1, image2, alpha) + crossfade_images.append(crossfade_image) + + # Convert crossfade_images to tensor + crossfade_images = torch.stack(crossfade_images, dim=0) + # Get the last frame result of the interpolation + last_frame = crossfade_images[-1] + # Calculate the number of remaining frames from images_2 + remaining_frames = len(images_2) - (transition_start_index + transitioning_frames) + # Crossfade the remaining frames with the last used alpha value + for i in range(remaining_frames): + alpha = alphas[-1] + image1 = images_1[i + transition_start_index + transitioning_frames] + image2 = images_2[i + transition_start_index + transitioning_frames] + easing_function = easing_functions.get(interpolation) + alpha = easing_function(alpha) # Apply the easing function to the alpha value + + crossfade_image = crossfade(image1, image2, alpha) + crossfade_images = torch.cat([crossfade_images, crossfade_image.unsqueeze(0)], dim=0) + # Append the beginning of images_1 + beginning_images_1 = images_1[:transition_start_index] + crossfade_images = torch.cat([beginning_images_1, crossfade_images], dim=0) + return (crossfade_images, ) + +class GetImageRangeFromBatch: + + RETURN_TYPES = ("IMAGE",) + FUNCTION = "imagesfrombatch" + CATEGORY = "KJNodes/image" + DESCRIPTION = """ +Creates a new batch using images from the input, +batch, starting from start_index. +""" + + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "images": ("IMAGE",), + "start_index": ("INT", {"default": 0,"min": -1, "max": 4096, "step": 1}), + "num_frames": ("INT", {"default": 1,"min": 1, "max": 4096, "step": 1}), + }, + } + + def imagesfrombatch(self, images, start_index, num_frames): + if start_index == -1: + start_index = len(images) - num_frames + if start_index < 0 or start_index >= len(images): + raise ValueError("GetImageRangeFromBatch: Start index is out of range") + end_index = start_index + num_frames + if end_index > len(images): + raise ValueError("GetImageRangeFromBatch: End index is out of range") + chosen_images = images[start_index:end_index] + return (chosen_images, ) + +class GetImagesFromBatchIndexed: + + RETURN_TYPES = ("IMAGE",) + FUNCTION = "indexedimagesfrombatch" + CATEGORY = "KJNodes/image" + DESCRIPTION = """ +Selects and returns the images at the specified indices as an image batch. +""" + + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "images": ("IMAGE",), + "indexes": ("STRING", {"default": "0, 1, 2", "multiline": True}), + }, + } + + def indexedimagesfrombatch(self, images, indexes): + + # Parse the indexes string into a list of integers + index_list = [int(index.strip()) for index in indexes.split(',')] + + # Convert list of indices to a PyTorch tensor + indices_tensor = torch.tensor(index_list, dtype=torch.long) + + # Select the images at the specified indices + chosen_images = images[indices_tensor] + + return (chosen_images,) + +class InsertImagesToBatchIndexed: + + RETURN_TYPES = ("IMAGE",) + FUNCTION = "insertimagesfrombatch" + CATEGORY = "KJNodes/image" + DESCRIPTION = """ +Inserts images at the specified indices into the original image batch. +""" + + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "original_images": ("IMAGE",), + "images_to_insert": ("IMAGE",), + "indexes": ("STRING", {"default": "0, 1, 2", "multiline": True}), + }, + } + + def insertimagesfrombatch(self, original_images, images_to_insert, indexes): + + # Parse the indexes string into a list of integers + index_list = [int(index.strip()) for index in indexes.split(',')] + + # Convert list of indices to a PyTorch tensor + indices_tensor = torch.tensor(index_list, dtype=torch.long) + + # Ensure the images_to_insert is a tensor + if not isinstance(images_to_insert, torch.Tensor): + images_to_insert = torch.tensor(images_to_insert) + + # Insert the images at the specified indices + for index, image in zip(indices_tensor, images_to_insert): + original_images[index] = image + + return (original_images,) + +class ReplaceImagesInBatch: + + RETURN_TYPES = ("IMAGE",) + FUNCTION = "replace" + CATEGORY = "KJNodes/image" + DESCRIPTION = """ +Replaces the images in a batch, starting from the specified start index, +with the replacement images. +""" + + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "original_images": ("IMAGE",), + "replacement_images": ("IMAGE",), + "start_index": ("INT", {"default": 1,"min": 0, "max": 4096, "step": 1}), + }, + } + + def replace(self, original_images, replacement_images, start_index): + images = None + if start_index >= len(original_images): + raise ValueError("GetImageRangeFromBatch: Start index is out of range") + end_index = start_index + len(replacement_images) + if end_index > len(original_images): + raise ValueError("GetImageRangeFromBatch: End index is out of range") + # Create a copy of the original_images tensor + original_images_copy = original_images.clone() + original_images_copy[start_index:end_index] = replacement_images + images = original_images_copy + return (images, ) + + +class ReverseImageBatch: + + RETURN_TYPES = ("IMAGE",) + FUNCTION = "reverseimagebatch" + CATEGORY = "KJNodes/image" + DESCRIPTION = """ +Reverses the order of the images in a batch. +""" + + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "images": ("IMAGE",), + }, + } + + def reverseimagebatch(self, images): + reversed_images = torch.flip(images, [0]) + return (reversed_images, ) + +class ImageBatchMulti: + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "inputcount": ("INT", {"default": 2, "min": 2, "max": 1000, "step": 1}), + "image_1": ("IMAGE", ), + "image_2": ("IMAGE", ), + }, + } + + RETURN_TYPES = ("IMAGE",) + RETURN_NAMES = ("images",) + FUNCTION = "combine" + CATEGORY = "KJNodes/image" + DESCRIPTION = """ +Creates an image batch from multiple images. +You can set how many inputs the node has, +with the **inputcount** and clicking update. +""" + + def combine(self, inputcount, **kwargs): + from nodes import ImageBatch + image_batch_node = ImageBatch() + image = kwargs["image_1"] + for c in range(1, inputcount): + new_image = kwargs[f"image_{c + 1}"] + image, = image_batch_node.batch(image, new_image) + return (image,) diff --git a/nodes/nodes.py b/nodes/nodes.py index 9c2bab9..725d5e8 100644 --- a/nodes/nodes.py +++ b/nodes/nodes.py @@ -12,12 +12,10 @@ import json import re import os import io -import random -import math import model_management -from nodes import MAX_RESOLUTION, SaveImage, CLIPTextEncode -from comfy_extras.nodes_mask import ImageCompositeMasked +from nodes import MAX_RESOLUTION, CLIPTextEncode + import comfy.sample import folder_paths from ..utility.utility import tensor2pil, pil2tensor @@ -100,24 +98,6 @@ class StringConstantMultiline: new_string = "\n".join(new_string) return (new_string, ) - -class JoinStrings: - @classmethod - def INPUT_TYPES(cls): - return { - "required": { - "string1": ("STRING", {"default": '', "forceInput": True}), - "string2": ("STRING", {"default": '', "forceInput": True}), - "delimiter": ("STRING", {"default": ' ', "multiline": False}), - } - } - RETURN_TYPES = ("STRING",) - FUNCTION = "joinstring" - CATEGORY = "KJNodes/constants" - - def joinstring(self, string1, string2, delimiter): - joined_string = string1 + delimiter + string2 - return (joined_string, ) class CreateFluidMask: @@ -501,193 +481,7 @@ to a different frame count. print(output_str) return (output_str,) -class CrossFadeImages: - - RETURN_TYPES = ("IMAGE",) - FUNCTION = "crossfadeimages" - CATEGORY = "KJNodes/image" - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "images_1": ("IMAGE",), - "images_2": ("IMAGE",), - "interpolation": (["linear", "ease_in", "ease_out", "ease_in_out", "bounce", "elastic", "glitchy", "exponential_ease_out"],), - "transition_start_index": ("INT", {"default": 1,"min": 0, "max": 4096, "step": 1}), - "transitioning_frames": ("INT", {"default": 1,"min": 0, "max": 4096, "step": 1}), - "start_level": ("FLOAT", {"default": 0.0,"min": 0.0, "max": 1.0, "step": 0.01}), - "end_level": ("FLOAT", {"default": 1.0,"min": 0.0, "max": 1.0, "step": 0.01}), - }, - } - - def crossfadeimages(self, images_1, images_2, transition_start_index, transitioning_frames, interpolation, start_level, end_level): - - def crossfade(images_1, images_2, alpha): - crossfade = (1 - alpha) * images_1 + alpha * images_2 - return crossfade - def ease_in(t): - return t * t - def ease_out(t): - return 1 - (1 - t) * (1 - t) - def ease_in_out(t): - return 3 * t * t - 2 * t * t * t - def bounce(t): - if t < 0.5: - return self.ease_out(t * 2) * 0.5 - else: - return self.ease_in((t - 0.5) * 2) * 0.5 + 0.5 - def elastic(t): - return math.sin(13 * math.pi / 2 * t) * math.pow(2, 10 * (t - 1)) - def glitchy(t): - return t + 0.1 * math.sin(40 * t) - def exponential_ease_out(t): - return 1 - (1 - t) ** 4 - - easing_functions = { - "linear": lambda t: t, - "ease_in": ease_in, - "ease_out": ease_out, - "ease_in_out": ease_in_out, - "bounce": bounce, - "elastic": elastic, - "glitchy": glitchy, - "exponential_ease_out": exponential_ease_out, - } - - crossfade_images = [] - - alphas = torch.linspace(start_level, end_level, transitioning_frames) - for i in range(transitioning_frames): - alpha = alphas[i] - image1 = images_1[i + transition_start_index] - image2 = images_2[i + transition_start_index] - easing_function = easing_functions.get(interpolation) - alpha = easing_function(alpha) # Apply the easing function to the alpha value - - crossfade_image = crossfade(image1, image2, alpha) - crossfade_images.append(crossfade_image) - - # Convert crossfade_images to tensor - crossfade_images = torch.stack(crossfade_images, dim=0) - # Get the last frame result of the interpolation - last_frame = crossfade_images[-1] - # Calculate the number of remaining frames from images_2 - remaining_frames = len(images_2) - (transition_start_index + transitioning_frames) - # Crossfade the remaining frames with the last used alpha value - for i in range(remaining_frames): - alpha = alphas[-1] - image1 = images_1[i + transition_start_index + transitioning_frames] - image2 = images_2[i + transition_start_index + transitioning_frames] - easing_function = easing_functions.get(interpolation) - alpha = easing_function(alpha) # Apply the easing function to the alpha value - - crossfade_image = crossfade(image1, image2, alpha) - crossfade_images = torch.cat([crossfade_images, crossfade_image.unsqueeze(0)], dim=0) - # Append the beginning of images_1 - beginning_images_1 = images_1[:transition_start_index] - crossfade_images = torch.cat([beginning_images_1, crossfade_images], dim=0) - return (crossfade_images, ) - -class GetImageRangeFromBatch: - - RETURN_TYPES = ("IMAGE",) - FUNCTION = "imagesfrombatch" - CATEGORY = "KJNodes/image" - DESCRIPTION = """ -Creates a new batch using images from the input, -batch, starting from start_index. -""" - - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "images": ("IMAGE",), - "start_index": ("INT", {"default": 0,"min": -1, "max": 4096, "step": 1}), - "num_frames": ("INT", {"default": 1,"min": 1, "max": 4096, "step": 1}), - }, - } - - def imagesfrombatch(self, images, start_index, num_frames): - if start_index == -1: - start_index = len(images) - num_frames - if start_index < 0 or start_index >= len(images): - raise ValueError("GetImageRangeFromBatch: Start index is out of range") - end_index = start_index + num_frames - if end_index > len(images): - raise ValueError("GetImageRangeFromBatch: End index is out of range") - chosen_images = images[start_index:end_index] - return (chosen_images, ) - -class GetImagesFromBatchIndexed: - - RETURN_TYPES = ("IMAGE",) - FUNCTION = "indexedimagesfrombatch" - CATEGORY = "KJNodes/image" - DESCRIPTION = """ -Selects and returns the images at the specified indices as an image batch. -""" - - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "images": ("IMAGE",), - "indexes": ("STRING", {"default": "0, 1, 2", "multiline": True}), - }, - } - - def indexedimagesfrombatch(self, images, indexes): - - # Parse the indexes string into a list of integers - index_list = [int(index.strip()) for index in indexes.split(',')] - - # Convert list of indices to a PyTorch tensor - indices_tensor = torch.tensor(index_list, dtype=torch.long) - - # Select the images at the specified indices - chosen_images = images[indices_tensor] - - return (chosen_images,) - -class InsertImagesToBatchIndexed: - - RETURN_TYPES = ("IMAGE",) - FUNCTION = "insertimagesfrombatch" - CATEGORY = "KJNodes/image" - DESCRIPTION = """ -Inserts images at the specified indices into the original image batch. -""" - - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "original_images": ("IMAGE",), - "images_to_insert": ("IMAGE",), - "indexes": ("STRING", {"default": "0, 1, 2", "multiline": True}), - }, - } - - def insertimagesfrombatch(self, original_images, images_to_insert, indexes): - - # Parse the indexes string into a list of integers - index_list = [int(index.strip()) for index in indexes.split(',')] - - # Convert list of indices to a PyTorch tensor - indices_tensor = torch.tensor(index_list, dtype=torch.long) - - # Ensure the images_to_insert is a tensor - if not isinstance(images_to_insert, torch.Tensor): - images_to_insert = torch.tensor(images_to_insert) - - # Insert the images at the specified indices - for index, image in zip(indices_tensor, images_to_insert): - original_images[index] = image - - return (original_images,) - class GetLatentsFromBatchIndexed: RETURN_TYPES = ("LATENT",) @@ -723,63 +517,6 @@ Selects and returns the latents at the specified indices as an latent batch. samples["samples"] = chosen_latents return (samples,) -class ReplaceImagesInBatch: - - RETURN_TYPES = ("IMAGE",) - FUNCTION = "replace" - CATEGORY = "KJNodes/image" - DESCRIPTION = """ -Replaces the images in a batch, starting from the specified start index, -with the replacement images. -""" - - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "original_images": ("IMAGE",), - "replacement_images": ("IMAGE",), - "start_index": ("INT", {"default": 1,"min": 0, "max": 4096, "step": 1}), - }, - } - - def replace(self, original_images, replacement_images, start_index): - images = None - if start_index >= len(original_images): - raise ValueError("GetImageRangeFromBatch: Start index is out of range") - end_index = start_index + len(replacement_images) - if end_index > len(original_images): - raise ValueError("GetImageRangeFromBatch: End index is out of range") - # Create a copy of the original_images tensor - original_images_copy = original_images.clone() - original_images_copy[start_index:end_index] = replacement_images - images = original_images_copy - return (images, ) - - -class ReverseImageBatch: - - RETURN_TYPES = ("IMAGE",) - FUNCTION = "reverseimagebatch" - CATEGORY = "KJNodes/image" - DESCRIPTION = """ -Reverses the order of the images in a batch. -""" - - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "images": ("IMAGE",), - }, - } - - def reverseimagebatch(self, images): - reversed_images = torch.flip(images, [0]) - return (reversed_images, ) - - - class CreateTextMask: RETURN_TYPES = ("IMAGE", "MASK",) @@ -1068,35 +805,7 @@ Combines multiple conditioning nodes into one cond = cond_combine_node.combine(new_cond, cond)[0] return (cond, inputcount,) -class ImageBatchMulti: - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "inputcount": ("INT", {"default": 2, "min": 2, "max": 1000, "step": 1}), - "image_1": ("IMAGE", ), - "image_2": ("IMAGE", ), - }, - } - RETURN_TYPES = ("IMAGE",) - RETURN_NAMES = ("images",) - FUNCTION = "combine" - CATEGORY = "KJNodes/image" - DESCRIPTION = """ -Creates an image batch from multiple images. -You can set how many inputs the node has, -with the **inputcount** and clicking update. -""" - - def combine(self, inputcount, **kwargs): - from nodes import ImageBatch - image_batch_node = ImageBatch() - image = kwargs["image_1"] - for c in range(1, inputcount): - new_image = kwargs[f"image_{c + 1}"] - image, = image_batch_node.batch(image, new_image) - return (image,) class MaskBatchMulti: @classmethod @@ -1128,6 +837,63 @@ with the **inputcount** and clicking update. mask = torch.cat((mask, new_mask), dim=0) return (mask,) +class JoinStrings: + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "string1": ("STRING", {"default": '', "forceInput": True}), + "string2": ("STRING", {"default": '', "forceInput": True}), + "delimiter": ("STRING", {"default": ' ', "multiline": False}), + } + } + RETURN_TYPES = ("STRING",) + FUNCTION = "joinstring" + CATEGORY = "KJNodes/constants" + + def joinstring(self, string1, string2, delimiter): + joined_string = string1 + delimiter + string2 + return (joined_string, ) + +class JoinStringMulti: + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "inputcount": ("INT", {"default": 2, "min": 2, "max": 1000, "step": 1}), + "string_1": ("STRING", {"default": '', "forceInput": True}), + "string_2": ("STRING", {"default": '', "forceInput": True}), + "delimiter": ("STRING", {"default": ' ', "multiline": False}), + "return_list": ("BOOLEAN", {"default": False}), + }, + } + + RETURN_TYPES = ("STRING",) + RETURN_NAMES = ("string",) + FUNCTION = "combine" + CATEGORY = "KJNodes" + DESCRIPTION = """ +Creates single string, or a list of strings, from +multiple input strings. +You can set how many inputs the node has, +with the **inputcount** and clicking update. +""" + + def combine(self, inputcount, delimiter, **kwargs): + string = kwargs["string_1"] + return_list = kwargs["return_list"] + strings = [string] # Initialize a list with the first string + for c in range(1, inputcount): + new_string = kwargs[f"string_{c + 1}"] + if return_list: + strings.append(new_string) # Add new string to the list + else: + string = string + delimiter + new_string + if return_list: + return (strings,) # Return the list of strings + else: + return (string,) # Return the combined string + class CondPassThrough: @classmethod def INPUT_TYPES(s): @@ -1463,33 +1229,19 @@ Converts any type to a string. """ def stringify(self, input, prefix="", suffix=""): - if isinstance(input, (int, float, bool)): + if isinstance(input, (int, float, bool)): stringified = str(input) - if prefix: # Check if prefix is not empty - stringified = prefix + stringified # Add the prefix - if suffix: # Check if suffix is not empty - stringified = stringified + suffix # Add the suffix + elif isinstance(input, list): + print("input is a list") + stringified = ', '.join(str(item) for item in input) else: return - return (stringified,) - -class ImagePass: - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "image": ("IMAGE",), - }, - } - RETURN_TYPES = ("IMAGE",) - FUNCTION = "passthrough" - CATEGORY = "KJNodes/misc" - DESCRIPTION = """ -Passes the image through without modifying it. -""" + if prefix: # Check if prefix is not empty + stringified = prefix + stringified # Add the prefix + if suffix: # Check if suffix is not empty + stringified = stringified + suffix # Add the suffix - def passthrough(self, image): - return image, + return (stringified,) class Sleep: @classmethod @@ -1561,291 +1313,6 @@ class EmptyLatentImagePresets: return (latent, int(width), int(height),) -class ColorMatch: - @classmethod - def INPUT_TYPES(cls): - return { - "required": { - "image_ref": ("IMAGE",), - "image_target": ("IMAGE",), - "method": ( - [ - 'mkl', - 'hm', - 'reinhard', - 'mvgd', - 'hm-mvgd-hm', - 'hm-mkl-hm', - ], { - "default": 'mkl' - }), - - }, - } - - CATEGORY = "KJNodes/image" - - RETURN_TYPES = ("IMAGE",) - RETURN_NAMES = ("image",) - FUNCTION = "colormatch" - DESCRIPTION = """ -color-matcher enables color transfer across images which comes in handy for automatic -color-grading of photographs, paintings and film sequences as well as light-field -and stopmotion corrections. - -The methods behind the mappings are based on the approach from Reinhard et al., -the Monge-Kantorovich Linearization (MKL) as proposed by Pitie et al. and our analytical solution -to a Multi-Variate Gaussian Distribution (MVGD) transfer in conjunction with classical histogram -matching. As shown below our HM-MVGD-HM compound outperforms existing methods. -https://github.com/hahnec/color-matcher/ - -""" - - def colormatch(self, image_ref, image_target, method): - try: - from color_matcher import ColorMatcher - except: - raise Exception("Can't import color-matcher, did you install requirements.txt? Manual install: pip install color-matcher") - cm = ColorMatcher() - image_ref = image_ref.cpu() - image_target = image_target.cpu() - batch_size = image_target.size(0) - out = [] - images_target = image_target.squeeze() - images_ref = image_ref.squeeze() - - image_ref_np = images_ref.numpy() - images_target_np = images_target.numpy() - - if image_ref.size(0) > 1 and image_ref.size(0) != batch_size: - raise ValueError("ColorMatch: Use either single reference image or a matching batch of reference images.") - - for i in range(batch_size): - image_target_np = images_target_np if batch_size == 1 else images_target[i].numpy() - image_ref_np_i = image_ref_np if image_ref.size(0) == 1 else images_ref[i].numpy() - try: - image_result = cm.transfer(src=image_target_np, ref=image_ref_np_i, method=method) - except BaseException as e: - print(f"Error occurred during transfer: {e}") - break - out.append(torch.from_numpy(image_result)) - return (torch.stack(out, dim=0).to(torch.float32), ) - -class SaveImageWithAlpha: - def __init__(self): - self.output_dir = folder_paths.get_output_directory() - self.type = "output" - self.prefix_append = "" - - @classmethod - def INPUT_TYPES(s): - return {"required": - {"images": ("IMAGE", ), - "mask": ("MASK", ), - "filename_prefix": ("STRING", {"default": "ComfyUI"})}, - "hidden": {"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO"}, - } - - RETURN_TYPES = () - FUNCTION = "save_images_alpha" - OUTPUT_NODE = True - CATEGORY = "KJNodes/image" - DESCRIPTION = """ -Saves an image and mask as .PNG with the mask as the alpha channel. -""" - - def save_images_alpha(self, images, mask, filename_prefix="ComfyUI_image_with_alpha", prompt=None, extra_pnginfo=None): - from comfy.cli_args import args - from PIL.PngImagePlugin import PngInfo - filename_prefix += self.prefix_append - full_output_folder, filename, counter, subfolder, filename_prefix = folder_paths.get_save_image_path(filename_prefix, self.output_dir, images[0].shape[1], images[0].shape[0]) - results = list() - if mask.dtype == torch.float16: - mask = mask.to(torch.float32) - def file_counter(): - max_counter = 0 - # Loop through the existing files - for existing_file in os.listdir(full_output_folder): - # Check if the file matches the expected format - match = re.fullmatch(fr"{filename}_(\d+)_?\.[a-zA-Z0-9]+", existing_file) - if match: - # Extract the numeric portion of the filename - file_counter = int(match.group(1)) - # Update the maximum counter value if necessary - if file_counter > max_counter: - max_counter = file_counter - return max_counter - - for image, alpha in zip(images, mask): - i = 255. * image.cpu().numpy() - a = 255. * alpha.cpu().numpy() - img = Image.fromarray(np.clip(i, 0, 255).astype(np.uint8)) - - # Resize the mask to match the image size - a_resized = Image.fromarray(a).resize(img.size, Image.LANCZOS) - a_resized = np.clip(a_resized, 0, 255).astype(np.uint8) - img.putalpha(Image.fromarray(a_resized, mode='L')) - metadata = None - if not args.disable_metadata: - metadata = PngInfo() - if prompt is not None: - metadata.add_text("prompt", json.dumps(prompt)) - if extra_pnginfo is not None: - for x in extra_pnginfo: - metadata.add_text(x, json.dumps(extra_pnginfo[x])) - - # Increment the counter by 1 to get the next available value - counter = file_counter() + 1 - file = f"{filename}_{counter:05}.png" - img.save(os.path.join(full_output_folder, file), pnginfo=metadata, compress_level=4) - results.append({ - "filename": file, - "subfolder": subfolder, - "type": self.type - }) - - return { "ui": { "images": results } } - -class ImageConcanate: - @classmethod - def INPUT_TYPES(s): - return {"required": { - "image1": ("IMAGE",), - "image2": ("IMAGE",), - "direction": ( - [ 'right', - 'down', - 'left', - 'up', - ], - { - "default": 'right' - }), - "match_image_size": ("BOOLEAN", {"default": False}), - }} - - RETURN_TYPES = ("IMAGE",) - FUNCTION = "concanate" - CATEGORY = "KJNodes/image" - DESCRIPTION = """ -Concatenates the image2 to image1 in the specified direction. -""" - - def concanate(self, image1, image2, direction, match_image_size): - if match_image_size: - image2 = torch.nn.functional.interpolate(image2, size=(image1.shape[2], image1.shape[3]), mode="bilinear") - if direction == 'right': - row = torch.cat((image1, image2), dim=2) - elif direction == 'down': - row = torch.cat((image1, image2), dim=1) - elif direction == 'left': - row = torch.cat((image2, image1), dim=2) - elif direction == 'up': - row = torch.cat((image2, image1), dim=1) - return (row,) - -class ImageGridComposite2x2: - @classmethod - def INPUT_TYPES(s): - return {"required": { - "image1": ("IMAGE",), - "image2": ("IMAGE",), - "image3": ("IMAGE",), - "image4": ("IMAGE",), - }} - - RETURN_TYPES = ("IMAGE",) - FUNCTION = "compositegrid" - CATEGORY = "KJNodes/image" - DESCRIPTION = """ -Concatenates the 4 input images into a 2x2 grid. -""" - - def compositegrid(self, image1, image2, image3, image4): - top_row = torch.cat((image1, image2), dim=2) - bottom_row = torch.cat((image3, image4), dim=2) - grid = torch.cat((top_row, bottom_row), dim=1) - return (grid,) - -class ImageGridComposite3x3: - @classmethod - def INPUT_TYPES(s): - return {"required": { - "image1": ("IMAGE",), - "image2": ("IMAGE",), - "image3": ("IMAGE",), - "image4": ("IMAGE",), - "image5": ("IMAGE",), - "image6": ("IMAGE",), - "image7": ("IMAGE",), - "image8": ("IMAGE",), - "image9": ("IMAGE",), - }} - - RETURN_TYPES = ("IMAGE",) - FUNCTION = "compositegrid" - CATEGORY = "KJNodes/image" - DESCRIPTION = """ -Concatenates the 9 input images into a 3x3 grid. -""" - - def compositegrid(self, image1, image2, image3, image4, image5, image6, image7, image8, image9): - top_row = torch.cat((image1, image2, image3), dim=2) - mid_row = torch.cat((image4, image5, image6), dim=2) - bottom_row = torch.cat((image7, image8, image9), dim=2) - grid = torch.cat((top_row, mid_row, bottom_row), dim=1) - return (grid,) - -class ImageBatchTestPattern: - @classmethod - def INPUT_TYPES(s): - return {"required": { - "batch_size": ("INT", {"default": 1,"min": 1, "max": 255, "step": 1}), - "start_from": ("INT", {"default": 0,"min": 0, "max": 255, "step": 1}), - "text_x": ("INT", {"default": 256,"min": 0, "max": 4096, "step": 1}), - "text_y": ("INT", {"default": 256,"min": 0, "max": 4096, "step": 1}), - "width": ("INT", {"default": 512,"min": 16, "max": 4096, "step": 1}), - "height": ("INT", {"default": 512,"min": 16, "max": 4096, "step": 1}), - "font": (folder_paths.get_filename_list("kjnodes_fonts"), ), - "font_size": ("INT", {"default": 255,"min": 8, "max": 4096, "step": 1}), - }} - - RETURN_TYPES = ("IMAGE",) - FUNCTION = "generatetestpattern" - CATEGORY = "KJNodes/text" - - def generatetestpattern(self, batch_size, font, font_size, start_from, width, height, text_x, text_y): - out = [] - # Generate the sequential numbers for each image - numbers = np.arange(start_from, start_from + batch_size) - font_path = folder_paths.get_full_path("kjnodes_fonts", font) - - for number in numbers: - # Create a black image with the number as a random color text - image = Image.new("RGB", (width, height), color='black') - draw = ImageDraw.Draw(image) - - # Generate a random color for the text - font_color = (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255)) - - font = ImageFont.truetype(font_path, font_size) - - # Get the size of the text and position it in the center - text = str(number) - - try: - draw.text((text_x, text_y), text, font=font, fill=font_color, features=['-liga']) - except: - draw.text((text_x, text_y), text, font=font, fill=font_color,) - - # Convert the image to a numpy array and normalize the pixel values - image_np = np.array(image).astype(np.float32) / 255.0 - image_tensor = torch.from_numpy(image_np).unsqueeze(0) - out.append(image_tensor) - out_tensor = torch.cat(out, dim=0) - - return (out_tensor,) - class BatchCLIPSeg: def __init__(self): @@ -2432,54 +1899,6 @@ Visualizes the specified bbox on the image. return (torch.cat(image_list, dim=0),) -from PIL import ImageGrab -import time -class ImageGrabPIL: - - @classmethod - def IS_CHANGED(cls): - - return - - RETURN_TYPES = ("IMAGE",) - RETURN_NAMES = ("image",) - FUNCTION = "screencap" - CATEGORY = "KJNodes/experimental" - DESCRIPTION = """ -Captures an area specified by screen coordinates. -Can be used for realtime diffusion with autoqueue. -""" - - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "x": ("INT", {"default": 0,"min": 0, "max": 4096, "step": 1}), - "y": ("INT", {"default": 0,"min": 0, "max": 4096, "step": 1}), - "width": ("INT", {"default": 512,"min": 0, "max": 4096, "step": 1}), - "height": ("INT", {"default": 512,"min": 0, "max": 4096, "step": 1}), - "num_frames": ("INT", {"default": 1,"min": 1, "max": 255, "step": 1}), - "delay": ("FLOAT", {"default": 0.1,"min": 0.0, "max": 10.0, "step": 0.01}), - }, - } - - def screencap(self, x, y, width, height, num_frames, delay): - captures = [] - bbox = (x, y, x + width, y + height) - - for _ in range(num_frames): - # Capture screen - screen_capture = ImageGrab.grab(bbox=bbox) - screen_capture_torch = torch.tensor(np.array(screen_capture), dtype=torch.float32) / 255.0 - screen_capture_torch = screen_capture_torch.unsqueeze(0) - captures.append(screen_capture_torch) - - # Wait for a short delay if more than one frame is to be captured - if num_frames > 1: - time.sleep(delay) - - return (torch.cat(captures, dim=0),) - class DummyLatentOut: @classmethod @@ -2634,91 +2053,6 @@ class InjectNoiseToLatent: rand_noise) / ((mix_randn_amount**2 + (1-mix_randn_amount)**2) ** 0.5) samples["samples"] = noised return (samples,) - -class AddLabel: - @classmethod - def INPUT_TYPES(s): - return {"required": { - "image":("IMAGE",), - "text_x": ("INT", {"default": 10, "min": 0, "max": 4096, "step": 1}), - "text_y": ("INT", {"default": 2, "min": 0, "max": 4096, "step": 1}), - "height": ("INT", {"default": 48, "min": 0, "max": 4096, "step": 1}), - "font_size": ("INT", {"default": 32, "min": 0, "max": 4096, "step": 1}), - "font_color": ("STRING", {"default": "white"}), - "label_color": ("STRING", {"default": "black"}), - "font": (folder_paths.get_filename_list("kjnodes_fonts"), ), - "text": ("STRING", {"default": "Text"}), - "direction": ( - [ 'up', - 'down', - ], - { - "default": 'up' - }), - }, - "optional":{ - "caption": ("STRING", {"default": "", "forceInput": True}), - } - } - - RETURN_TYPES = ("IMAGE",) - FUNCTION = "addlabel" - CATEGORY = "KJNodes/text" - DESCRIPTION = """ -Creates a new with the given text, and concatenates it to -either above or below the input image. -Note that this changes the input image's height! -Fonts are loaded from this folder: -ComfyUI/custom_nodes/ComfyUI-KJNodes/fonts -""" - - def addlabel(self, image, text_x, text_y, text, height, font_size, font_color, label_color, font, direction, caption=""): - batch_size = image.shape[0] - width = image.shape[2] - - if font == "TTNorms-Black.otf": - font_path = os.path.join(script_directory, "fonts", "TTNorms-Black.otf") - else: - font_path = folder_paths.get_full_path("kjnodes_fonts", font) - - if caption == "": - label_image = Image.new("RGB", (width, height), label_color) - draw = ImageDraw.Draw(label_image) - font = ImageFont.truetype(font_path, font_size) - try: - draw.text((text_x, text_y), text, font=font, fill=font_color, features=['-liga']) - except: - draw.text((text_x, text_y), text, font=font, fill=font_color) - - label_image = np.array(label_image).astype(np.float32) / 255.0 - label_image = torch.from_numpy(label_image)[None, :, :, :] - # Duplicate the label image for the entire batch - label_batch = label_image.repeat(batch_size, 1, 1, 1) - else: - label_list = [] - assert len(caption) == batch_size, "Number of captions does not match number of images" - for cap in caption: - label_image = Image.new("RGB", (width, height), label_color) - draw = ImageDraw.Draw(label_image) - font = ImageFont.truetype(font_path, font_size) - try: - draw.text((text_x, text_y), cap, font=font, fill=font_color, features=['-liga']) - except: - draw.text((text_x, text_y), cap, font=font, fill=font_color) - - label_image = np.array(label_image).astype(np.float32) / 255.0 - label_image = torch.from_numpy(label_image) - label_list.append(label_image) - label_batch = torch.stack(label_list) - print(label_batch.shape) - - if direction == 'down': - combined_images = torch.cat((image, label_batch), dim=1) - elif direction == 'up': - combined_images = torch.cat((label_batch, image), dim=1) - - return (combined_images,) - class SoundReactive: @classmethod @@ -3077,30 +2411,7 @@ https://huggingface.co/stabilityai/sv3d latent = torch.zeros([batch_size, 4, height // 8, width // 8]) return (final_positive, final_negative, {"samples": latent}) -class ImageBatchRepeatInterleaving: - - RETURN_TYPES = ("IMAGE",) - FUNCTION = "repeat" - CATEGORY = "KJNodes/image" - DESCRIPTION = """ -Repeats each image in a batch by the specified number of times. -Example batch of 5 images: 0, 1 ,2, 3, 4 -with repeats 2 becomes batch of 10 images: 0, 0, 1, 1, 2, 2, 3, 3, 4, 4 -""" - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "images": ("IMAGE",), - "repeats": ("INT", {"default": 1, "min": 1, "max": 4096}), - }, - } - - def repeat(self, images, repeats): - - repeated_images = torch.repeat_interleave(images, repeats=repeats, dim=0) - return (repeated_images, ) def parse_coordinates(coordinates_str): coordinates = {} @@ -3252,60 +2563,7 @@ Experimental, does not function yet as ComfyUI base changes are needed return (c, plot_image_tensor,) -class ImageUpscaleWithModelBatched: - @classmethod - def INPUT_TYPES(s): - return {"required": { "upscale_model": ("UPSCALE_MODEL",), - "images": ("IMAGE",), - "per_batch": ("INT", {"default": 16, "min": 1, "max": 4096, "step": 1}), - }} - RETURN_TYPES = ("IMAGE",) - FUNCTION = "upscale" - CATEGORY = "KJNodes/image" - DESCRIPTION = """ -Same as ComfyUI native model upscaling node, -but allows setting sub-batches for reduced VRAM usage. -""" - def upscale(self, upscale_model, images, per_batch): - - device = model_management.get_torch_device() - upscale_model.to(device) - in_img = images.movedim(-1,-3).to(device) - - steps = in_img.shape[0] - pbar = comfy.utils.ProgressBar(steps) - t = [] - - for start_idx in range(0, in_img.shape[0], per_batch): - sub_images = upscale_model(in_img[start_idx:start_idx+per_batch]) - t.append(sub_images.cpu()) - # Calculate the number of images processed in this batch - batch_count = sub_images.shape[0] - # Update the progress bar by the number of images processed in this batch - pbar.update(batch_count) - upscale_model.cpu() - - t = torch.cat(t, dim=0).permute(0, 2, 3, 1).cpu() - return (t,) - -class ImageNormalize_Neg1_To_1: - @classmethod - def INPUT_TYPES(s): - return {"required": { - "images": ("IMAGE",), - - }} - RETURN_TYPES = ("IMAGE",) - FUNCTION = "normalize" - CATEGORY = "KJNodes/misc" - DESCRIPTION = """ -Normalize the images to be in the range [-1, 1] -""" - - def normalize(self,images): - images = images * 2.0 - 1.0 - return (images,) folder_paths.add_model_folder_path("intristic_loras", os.path.join(script_directory, "intristic_loras")) @@ -3533,94 +2791,7 @@ https://huggingface.co/roborovski/superprompt-v1 return (out, ) -class RemapImageRange: - @classmethod - def INPUT_TYPES(s): - return {"required": { - "image": ("IMAGE",), - "min": ("FLOAT", {"default": 0.0,"min": -10.0, "max": 1.0, "step": 0.01}), - "max": ("FLOAT", {"default": 1.0,"min": 0.0, "max": 10.0, "step": 0.01}), - "clamp": ("BOOLEAN", {"default": True}), - }, - } - - RETURN_TYPES = ("IMAGE",) - FUNCTION = "remap" - CATEGORY = "KJNodes/image" - DESCRIPTION = """ -Remaps the image values to the specified range. -""" - - def remap(self, image, min, max, clamp): - if image.dtype == torch.float16: - image = image.to(torch.float32) - image = min + image * (max - min) - if clamp: - image = torch.clamp(image, min=0.0, max=1.0) - return (image, ) -class SplitImageChannels: - @classmethod - def INPUT_TYPES(s): - return {"required": { - "image": ("IMAGE",), - }, - } - - RETURN_TYPES = ("IMAGE", "IMAGE", "IMAGE", "MASK") - RETURN_NAMES = ("red", "green", "blue", "mask") - FUNCTION = "split" - CATEGORY = "KJNodes/image" - DESCRIPTION = """ -Splits image channels into images where the selected channel -is repeated for all channels, and the alpha as a mask. -""" - - def split(self, image): - red = image[:, :, :, 0:1] # Red channel - green = image[:, :, :, 1:2] # Green channel - blue = image[:, :, :, 2:3] # Blue channel - alpha = image[:, :, :, 3:4] # Alpha channel - alpha = alpha.squeeze(-1) - - # Repeat the selected channel for all channels - red = torch.cat([red, red, red], dim=3) - green = torch.cat([green, green, green], dim=3) - blue = torch.cat([blue, blue, blue], dim=3) - return (red, green, blue, alpha) - -class MergeImageChannels: - @classmethod - def INPUT_TYPES(s): - return {"required": { - "red": ("IMAGE",), - "green": ("IMAGE",), - "blue": ("IMAGE",), - - }, - "optional": { - "mask": ("MASK", {"default": None}), - }, - } - - RETURN_TYPES = ("IMAGE",) - RETURN_NAMES = ("image",) - FUNCTION = "merge" - CATEGORY = "KJNodes/image" - DESCRIPTION = """ -Merges channel data into an image. -""" - - def merge(self, red, green, blue, alpha=None): - image = torch.stack([ - red[..., 0, None], # Red channel - green[..., 1, None], # Green channel - blue[..., 2, None] # Blue channel - ], dim=-1) - image = image.squeeze(-2) - if alpha is not None: - image = torch.cat([image, alpha], dim=-1) - return (image,) class CameraPoseVisualizer: @@ -3776,141 +2947,6 @@ or a .txt file with RealEstate camera intrinsics and coordinates, in a 3D plot. ret_poses = [transform_matrix @ x for x in ret_poses] return np.array(ret_poses, dtype=np.float32) -class ImagePadForOutpaintMasked: - - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "image": ("IMAGE",), - "left": ("INT", {"default": 0, "min": 0, "max": MAX_RESOLUTION, "step": 8}), - "top": ("INT", {"default": 0, "min": 0, "max": MAX_RESOLUTION, "step": 8}), - "right": ("INT", {"default": 0, "min": 0, "max": MAX_RESOLUTION, "step": 8}), - "bottom": ("INT", {"default": 0, "min": 0, "max": MAX_RESOLUTION, "step": 8}), - "feathering": ("INT", {"default": 40, "min": 0, "max": MAX_RESOLUTION, "step": 1}), - }, - "optional": { - "mask": ("MASK",), - } - } - - RETURN_TYPES = ("IMAGE", "MASK") - FUNCTION = "expand_image" - - CATEGORY = "image" - - def expand_image(self, image, left, top, right, bottom, feathering, mask=None): - B, H, W, C = image.size() - - new_image = torch.ones( - (B, H + top + bottom, W + left + right, C), - dtype=torch.float32, - ) * 0.5 - - new_image[:, top:top + H, left:left + W, :] = image - - if mask is None: - new_mask = torch.ones( - (H + top + bottom, W + left + right), - dtype=torch.float32, - ) - - t = torch.zeros( - (H, W), - dtype=torch.float32 - ) - else: - # If a mask is provided, pad it to fit the new image size - mask = F.pad(mask, (left, right, top, bottom), mode='constant', value=0) - mask = 1 - mask - t = torch.zeros_like(mask) - - - - if feathering > 0 and feathering * 2 < H and feathering * 2 < W: - - for i in range(H): - for j in range(W): - dt = i if top != 0 else H - db = H - i if bottom != 0 else H - - dl = j if left != 0 else W - dr = W - j if right != 0 else W - - d = min(dt, db, dl, dr) - - if d >= feathering: - continue - - v = (feathering - d) / feathering - - if mask is None: - t[i, j] = v * v - else: - t[:, top + i, left + j] = v * v - - if mask is None: - mask = new_mask.squeeze(0) - mask[top:top + H, left:left + W] = t - mask = mask.unsqueeze(0) - - return (new_image, mask,) - -class ImageAndMaskPreview(SaveImage): - def __init__(self): - self.output_dir = folder_paths.get_temp_directory() - self.type = "temp" - self.prefix_append = "_temp_" + ''.join(random.choice("abcdefghijklmnopqrstupvxyz") for x in range(5)) - self.compress_level = 4 - - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "mask_opacity": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}), - "mask_color": ("STRING", {"default": "255, 255, 255"}), - "pass_through": ("BOOLEAN", {"default": False}), - }, - "optional": { - "image": ("IMAGE",), - "mask": ("MASK",), - }, - "hidden": {"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO"}, - } - RETURN_TYPES = ("IMAGE",) - RETURN_NAMES = ("composite",) - FUNCTION = "execute" - CATEGORY = "KJNodes" - DESCRIPTION = """ -Preview an image or a mask, when both inputs are used -composites the mask on top of the image. -with pass_through on the preview is disabled and the -composite is returned from the composite slot instead, -this allows for the preview to be passed for video combine -nodes for example. -""" - - def execute(self, mask_opacity, mask_color, pass_through, filename_prefix="ComfyUI", image=None, mask=None, prompt=None, extra_pnginfo=None): - if mask is not None and image is None: - preview = mask.reshape((-1, 1, mask.shape[-2], mask.shape[-1])).movedim(1, -1).expand(-1, -1, -1, 3) - elif mask is None and image is not None: - preview = image - elif mask is not None and image is not None: - mask_adjusted = mask * mask_opacity - mask_image = mask.reshape((-1, 1, mask.shape[-2], mask.shape[-1])).movedim(1, -1).expand(-1, -1, -1, 3).clone() - - color_list = list(map(int, mask_color.split(', '))) - print(color_list[0]) - mask_image[:, :, :, 0] = color_list[0] // 255 # Red channel - mask_image[:, :, :, 1] = color_list[1] // 255 # Green channel - mask_image[:, :, :, 2] = color_list[2] // 255 # Blue channel - - preview, = ImageCompositeMasked.composite(self, image, mask_image, 0, 0, True, mask_adjusted) - if pass_through: - return (preview, ) - return(self.save_images(preview, filename_prefix, prompt, extra_pnginfo)) - - class StabilityAPI_SD3: diff --git a/web/js/jsnodes.js b/web/js/jsnodes.js index a20a7a7..9a9483e 100644 --- a/web/js/jsnodes.js +++ b/web/js/jsnodes.js @@ -73,6 +73,28 @@ app.registerExtension({ }); } break; + case "JoinStringMulti": + nodeType.prototype.onNodeCreated = function () { + this._type = "STRING" + this.inputs_offset = nodeData.name.includes("selective")?1:0 + this.addWidget("button", "Update inputs", null, () => { + if (!this.inputs) { + this.inputs = []; + } + const target_number_of_inputs = this.widgets.find(w => w.name === "inputcount")["value"]; + if(target_number_of_inputs===this.inputs.length)return; // already set, do nothing + + if(target_number_of_inputs < this.inputs.length){ + for(let i = this.inputs.length; i>=this.inputs_offset+target_number_of_inputs; i--) + this.removeInput(i) + } + else{ + for(let i = this.inputs.length+1-this.inputs_offset; i <= target_number_of_inputs; ++i) + this.addInput(`string_${i}`, this._type) + } + }); + } + break; case "SoundReactive": nodeType.prototype.onNodeCreated = function () { let audioContext; From 4e2ff11e3fab4457fa2dc8e0050336178cf82d02 Mon Sep 17 00:00:00 2001 From: kijai <40791699+kijai@users.noreply.github.com> Date: Wed, 1 May 2024 20:59:39 +0300 Subject: [PATCH 64/95] Spline editor custom size option --- __init__.py | 1 + nodes/curve_nodes.py | 4 +- nodes/nodes.py | 22 ++++++++ web/js/spline_editor.js | 112 +++++++++++++++++++++++++++------------- 4 files changed, 102 insertions(+), 37 deletions(-) diff --git a/__init__.py b/__init__.py index fd2bba9..bc0f998 100644 --- a/__init__.py +++ b/__init__.py @@ -34,6 +34,7 @@ NODE_CLASS_MAPPINGS = { "CreateVoronoiMask": CreateVoronoiMask, "CreateMagicMask": CreateMagicMask, "RemapMaskRange": RemapMaskRange, + "GetMaskSize": GetMaskSize, #images "ImageBatchMulti": ImageBatchMulti, "ColorMatch": ColorMatch, diff --git a/nodes/curve_nodes.py b/nodes/curve_nodes.py index 668ed23..ae1ca6c 100644 --- a/nodes/curve_nodes.py +++ b/nodes/curve_nodes.py @@ -106,7 +106,7 @@ output types: coord['y'] = int(round(coord['y'])) normalized_y_values = [ - (1.0 - (point['y'] / 512) - 0.0) * (max_value - min_value) + min_value + (1.0 - (point['y'] / mask_height) - 0.0) * (max_value - min_value) + min_value for point in coordinates ] if float_output_type == 'list': @@ -539,7 +539,7 @@ Experimental, does not function yet as ComfyUI base changes are needed # Concatenate prev and position_params_batch, ensuring both are lists of lists # and each sublist corresponds to a batch item combined_position_params = [prev_item + batch_item for prev_item, batch_item in zip(prev, position_params_batch)] - n[1]['gligen'] = ("position", gligen_textbox_model, combined_position_params) + n[1]['gligen'] = ("position_batched", gligen_textbox_model, combined_position_params) c.append(n) return (c, plot_image_tensor,) diff --git a/nodes/nodes.py b/nodes/nodes.py index 725d5e8..1e3868f 100644 --- a/nodes/nodes.py +++ b/nodes/nodes.py @@ -1393,6 +1393,28 @@ Segments an image or batch of images using CLIPSeg. return results, +class GetMaskSize: + @classmethod + def INPUT_TYPES(s): + return {"required": { + "mask": ("MASK",), + }} + + RETURN_TYPES = ("MASK","INT", "INT", ) + RETURN_NAMES = ("mask", "width", "height",) + FUNCTION = "getsize" + CATEGORY = "KJNodes/masking" + DESCRIPTION = """ +Returns the width and height of the mask, +and passes through the mask unchanged. + +""" + + def getsize(self, mask): + width = mask.shape[2] + height = mask.shape[1] + return (mask, width, height,) + class RoundMask: @classmethod def INPUT_TYPES(s): diff --git a/web/js/spline_editor.js b/web/js/spline_editor.js index 5aa9a0e..d39a43c 100644 --- a/web/js/spline_editor.js +++ b/web/js/spline_editor.js @@ -147,13 +147,13 @@ app.registerExtension({ this.menuItem2.textContent = "Display sample points"; styleMenuItem(this.menuItem2); - // this.menuItem3 = document.createElement("a"); - // this.menuItem3.href = "#"; - // this.menuItem3.id = "menu-item-2"; - // this.menuItem3.textContent = "Switch sampling method"; - // styleMenuItem(this.menuItem3); + this.menuItem3 = document.createElement("a"); + this.menuItem3.href = "#"; + this.menuItem3.id = "menu-item-2"; + this.menuItem3.textContent = "Switch point shape"; + styleMenuItem(this.menuItem3); - const menuItems = [this.menuItem1, this.menuItem2]; + const menuItems = [this.menuItem1, this.menuItem2, this.menuItem3]; menuItems.forEach(menuItem => { menuItem.addEventListener('mouseover', function() { @@ -190,7 +190,6 @@ app.registerExtension({ chainCallback(this, "onGraphConfigured", function() { createSplineEditor(this); - this.setSize([550, 920]); }); }); // onAfterGraphConfigured @@ -245,22 +244,22 @@ function createSplineEditor(context, reset=false) { updatePath(); }); -// context.menuItem3.addEventListener('click', function(e) { -// e.preventDefault(); -// if (pointSamplingMethod == samplePointsTime) { -// pointSamplingMethod = samplePointsPath -// } -// else { -// pointSamplingMethod = samplePointsTime -// } -// updatePath(); -// }); - + context.menuItem3.addEventListener('click', function(e) { + e.preventDefault(); + if (dotShape == "circle"){ + dotShape = "triangle" + } + else { + dotShape = "circle" + } + console.log(dotShape) + updatePath(); +}); + var dotShape = "circle"; var drawSamplePoints = false; - //var pointSamplingMethod = samplePointsTime function updatePath() { - let coords = samplePoints(pathElements[0], points_to_sample, samplingMethod); + let coords = samplePoints(pathElements[0], points_to_sample, samplingMethod, w); if (drawSamplePoints) { if (pointsLayer) { @@ -303,6 +302,8 @@ function createSplineEditor(context, reset=false) { const minValueWidget = context.widgets.find(w => w.name === "min_value"); const maxValueWidget = context.widgets.find(w => w.name === "max_value"); const samplingMethodWidget = context.widgets.find(w => w.name === "sampling_method"); + const widthWidget = context.widgets.find(w => w.name === "mask_width"); + const heightWidget = context.widgets.find(w => w.name === "mask_height"); //const segmentedWidget = context.widgets.find(w => w.name === "segmented"); var interpolation = interpolationWidget.value @@ -337,12 +338,26 @@ function createSplineEditor(context, reset=false) { rangeMax = maxValueWidget.value updatePath(); } + widthWidget.callback = () => { + w = widthWidget.value + vis.width(w) + context.setSize([w + 45, context.size[1]]); + updatePath(); + } + heightWidget.callback = () => { + h = heightWidget.value + vis.height(h) + context.setSize([context.size[0], h + 410]); + updatePath(); + } // Initialize or reset points array - var drawHandles = false - var w = 512 - var h = 512 - var i = 3 + var drawHandles = false; + var hoverIndex = -1; + var isDragging = false; + var w = widthWidget.value; + var h = heightWidget.value; + var i = 3; let points = []; if (!reset && pointsStoreWidget.value != "") { @@ -408,12 +423,12 @@ function createSplineEditor(context, reset=false) { context.contextMenu.style.display = 'block'; context.contextMenu.style.left = `${pv.event.clientX}px`; context.contextMenu.style.top = `${pv.event.clientY}px`; - } + } }) - + vis.add(pv.Rule) - .data(pv.range(0, 8, .5)) - .bottom(d => d * 64) + .data(pv.range(0, h, 64)) + .bottom(d => d) .strokeStyle("gray") .lineWidth(3) @@ -432,15 +447,42 @@ function createSplineEditor(context, reset=false) { .segmented(() => false) .strokeStyle(pv.Colors.category10().by(pv.index)) .lineWidth(3) - - var hoverIndex = -1; - var isDragging - + vis.add(pv.Dot) .data(() => points) .left(d => d.x) .top(d => d.y) .radius(10) + .shape(function() { + return dotShape; + }) + .angle(function() { + const index = this.index; + let angle = 0; + + if (dotShape === "triangle") { + let dxNext = 0, dyNext = 0; + if (index < points.length - 1) { + dxNext = points[index + 1].x - points[index].x; + dyNext = points[index + 1].y - points[index].y; + } + + let dxPrev = 0, dyPrev = 0; + if (index > 0) { + dxPrev = points[index].x - points[index - 1].x; + dyPrev = points[index].y - points[index - 1].y; + } + + const dx = (dxNext + dxPrev) / 2; + const dy = (dyNext + dyPrev) / 2; + + angle = Math.atan2(dy, dx); + angle -= Math.PI / 2; + angle = (angle + 2 * Math.PI) % (2 * Math.PI); + } + + return angle; + }) .cursor("move") .strokeStyle(function() { return i == this.index ? "#ff7f0e" : "#1f77b4"; }) .fillStyle(function() { return "rgba(100, 100, 100, 0.2)"; }) @@ -498,7 +540,7 @@ function createSplineEditor(context, reset=false) { return `F: ${frame}, X: ${normalizedX.toFixed(2)}, Y: ${normalizedY.toFixed(2)}`; }) .textStyle("orange") - + vis.render(); var svgElement = vis.canvas(); svgElement.style['zIndex'] = "2" @@ -508,8 +550,8 @@ function createSplineEditor(context, reset=false) { updatePath(); } -function samplePoints(svgPathElement, numSamples, samplingMethod) { - var svgWidth = 512; // Fixed width of the SVG element +function samplePoints(svgPathElement, numSamples, samplingMethod, width) { + var svgWidth = width; // Fixed width of the SVG element var pathLength = svgPathElement.getTotalLength(); var points = []; From d61894be21ba2f035c0192db5d31549a0bcfd479 Mon Sep 17 00:00:00 2001 From: kijai <40791699+kijai@users.noreply.github.com> Date: Thu, 2 May 2024 02:34:57 +0300 Subject: [PATCH 65/95] Gligen node updates --- __init__.py | 2 +- nodes/curve_nodes.py | 73 +++++++++++++++++++++++++++++++------------- nodes/nodes.py | 6 ++-- 3 files changed, 54 insertions(+), 27 deletions(-) diff --git a/__init__.py b/__init__.py index bc0f998..0cac1c9 100644 --- a/__init__.py +++ b/__init__.py @@ -175,7 +175,7 @@ NODE_DISPLAY_NAME_MAPPINGS = { "ImageTransformByNormalizedAmplitude": "ImageTransformByNormalizedAmplitude", "GetLatentsFromBatchIndexed": "GetLatentsFromBatchIndexed", "StringConstant": "StringConstant", - "GLIGENTextBoxApplyBatch": "GLIGENTextBoxApplyBatch", + "GLIGENTextBoxApplyBatch": "GLIGENTextBoxApplyBatch (deprecated)", "CondPassThrough": "CondPassThrough", "ImageUpscaleWithModelBatched": "ImageUpscaleWithModelBatched", "ScaleBatchPromptSchedule": "ScaleBatchPromptSchedule", diff --git a/nodes/curve_nodes.py b/nodes/curve_nodes.py index ae1ca6c..d44ca32 100644 --- a/nodes/curve_nodes.py +++ b/nodes/curve_nodes.py @@ -497,38 +497,61 @@ class GLIGENTextBoxApplyBatchCoords: "gligen_textbox_model": ("GLIGEN", ), "coordinates": ("STRING", {"forceInput": True}), "text": ("STRING", {"multiline": True}), - "width": ("INT", {"default": 64, "min": 8, "max": 4096, "step": 8}), - "height": ("INT", {"default": 64, "min": 8, "max": 4096, "step": 8}), + "width": ("INT", {"default": 128, "min": 8, "max": 4096, "step": 8}), + "height": ("INT", {"default": 128, "min": 8, "max": 4096, "step": 8}), }, + "optional": {"size_multiplier": ("FLOAT", {"default": [1.0], "forceInput": True})}, } RETURN_TYPES = ("CONDITIONING", "IMAGE", ) + RETURN_NAMES = ("conditioning", "coord_preview", ) FUNCTION = "append" CATEGORY = "KJNodes/experimental" DESCRIPTION = """ -Experimental, does not function yet as ComfyUI base changes are needed +This node allows scheduling GLIGEN text box positions in a batch, +to be used with AnimateDiff-Evolved. Intended to pair with the +Spline Editor -node. + +GLIGEN model can be downloaded through the Manage's "Install Models" menu. +Or directly from here: +https://huggingface.co/comfyanonymous/GLIGEN_pruned_safetensors/tree/main + +Inputs: +- **latents** input is used to calculate batch size +- **clip** is your standard text encoder, use same as for the main prompt +- **gligen_textbox_model** connects to GLIGEN Loader +- **coordinates** takes a json string of points, directly compatible +with the spline editor node. +- **text** is the part of the prompt to set position for +- **width** and **height** are the size of the GLIGEN bounding box + +Outputs: +- **conditioning** goes between to clip text encode and the sampler +- **coord_preview** is an optional preview of the coordinates and +bounding boxes. + """ - def append(self, latents, coordinates, conditioning_to, clip, gligen_textbox_model, text, width, height): + def append(self, latents, coordinates, conditioning_to, clip, gligen_textbox_model, text, width, height, size_multiplier=[1.0]): coordinates = json.loads(coordinates.replace("'", '"')) coordinates = [(coord['x'], coord['y']) for coord in coordinates] batch_size = sum(tensor.size(0) for tensor in latents.values()) - assert len(coordinates) == batch_size, "The number of coordinates does not match the number of latents" - c = [] - cond, cond_pooled = clip.encode_from_tokens(clip.tokenize(text), return_pooled=True) + if len(coordinates) != batch_size: + print("GLIGENTextBoxApplyBatchCoords WARNING: The number of coordinates does not match the number of latents") - image_height = latents['samples'].shape[-2] * 8 - image_width = latents['samples'].shape[-1] * 8 - plot_image_tensor = self.plot_coordinates_to_tensor(coordinates, image_height, image_width, height, text) + c = [] + _, cond_pooled = clip.encode_from_tokens(clip.tokenize(text), return_pooled=True) for t in conditioning_to: n = [t[0], t[1].copy()] position_params_batch = [[] for _ in range(batch_size)] # Initialize a list of empty lists for each batch item - + if len(size_multiplier) != batch_size: + size_multiplier = size_multiplier * (batch_size // len(size_multiplier)) + size_multiplier[:batch_size % len(size_multiplier)] + for i in range(batch_size): - x_position, y_position = coordinates[i] - position_param = (cond_pooled, height // 8, width // 8, y_position // 8, x_position // 8) + x_position, y_position = coordinates[i] + position_param = (cond_pooled, int((height // 8) * size_multiplier[i]), int((width // 8) * size_multiplier[i]), y_position // 8, x_position // 8) position_params_batch[i].append(position_param) # Append position_param to the correct sublist prev = [] @@ -541,35 +564,41 @@ Experimental, does not function yet as ComfyUI base changes are needed combined_position_params = [prev_item + batch_item for prev_item, batch_item in zip(prev, position_params_batch)] n[1]['gligen'] = ("position_batched", gligen_textbox_model, combined_position_params) c.append(n) + + image_height = latents['samples'].shape[-2] * 8 + image_width = latents['samples'].shape[-1] * 8 + plot_image_tensor = self.plot_coordinates_to_tensor(coordinates, image_height, image_width, height, size_multiplier, text) return (c, plot_image_tensor,) - def plot_coordinates_to_tensor(self, coordinates, height, width, box_size, prompt): + def plot_coordinates_to_tensor(self, coordinates, height, width, bbox_height, size_multiplier, prompt): import matplotlib matplotlib.use('Agg') from matplotlib.backends.backend_agg import FigureCanvasAgg as FigureCanvas - # Convert coordinates to separate x and y lists - #x_coords, y_coords = zip(*coordinates) - fig, ax = matplotlib.pyplot.subplots(figsize=(width/100, height/100), dpi=100) - #ax.scatter(x_coords, y_coords, color='yellow', label='_nolegend_') + cmap = matplotlib.pyplot.get_cmap('rainbow') # Draw a box at each coordinate - for x, y in coordinates: + for i, ((x, y), size) in enumerate(zip(coordinates, size_multiplier)): + color_index = i / (len(coordinates) - 1) + color = cmap(color_index) + box_size = bbox_height * size rect = matplotlib.patches.Rectangle((x - box_size/2, y - box_size/2), box_size, box_size, - linewidth=1, edgecolor='green', facecolor='none', alpha=0.5) + linewidth=1, edgecolor=color, facecolor='none', alpha=0.5) ax.add_patch(rect) - # Draw arrows from one point to another to indicate direction + # Draw arrows from one point to another to indicate direction for i in range(len(coordinates) - 1): + color_index = i / (len(coordinates) - 1) + color = cmap(color_index) x1, y1 = coordinates[i] x2, y2 = coordinates[i + 1] ax.annotate("", xy=(x2, y2), xytext=(x1, y1), arrowprops=dict(arrowstyle="->", linestyle="-", lw=1, - color='orange', + color=color, mutation_scale=10)) matplotlib.pyplot.rcParams['text.color'] = '#999999' fig.patch.set_facecolor('#353535') diff --git a/nodes/nodes.py b/nodes/nodes.py index 1e3868f..4c1b815 100644 --- a/nodes/nodes.py +++ b/nodes/nodes.py @@ -2545,7 +2545,7 @@ class GLIGENTextBoxApplyBatch: FUNCTION = "append" CATEGORY = "KJNodes/experimental" DESCRIPTION = """ -Experimental, does not function yet as ComfyUI base changes are needed +Experimental, deprecated, check the GLIGENTextBoxApplyBatchCoords instead. """ def append(self, latents, conditioning_to, clip, gligen_textbox_model, text, width, height, coordinates, interpolation): @@ -2580,13 +2580,11 @@ Experimental, does not function yet as ComfyUI base changes are needed # Concatenate prev and position_params_batch, ensuring both are lists of lists # and each sublist corresponds to a batch item combined_position_params = [prev_item + batch_item for prev_item, batch_item in zip(prev, position_params_batch)] - n[1]['gligen'] = ("position", gligen_textbox_model, combined_position_params) + n[1]['gligen'] = ("position_batched", gligen_textbox_model, combined_position_params) c.append(n) return (c, plot_image_tensor,) - - folder_paths.add_model_folder_path("intristic_loras", os.path.join(script_directory, "intristic_loras")) class Intrinsic_lora_sampling: From e914839605d3ecb8f8894b8c8594f1e8168fc2aa Mon Sep 17 00:00:00 2001 From: kijai <40791699+kijai@users.noreply.github.com> Date: Thu, 2 May 2024 09:37:01 +0300 Subject: [PATCH 66/95] Update curve_nodes.py --- nodes/curve_nodes.py | 55 +++++++++++++++++++++++--------------------- 1 file changed, 29 insertions(+), 26 deletions(-) diff --git a/nodes/curve_nodes.py b/nodes/curve_nodes.py index d44ca32..60deb32 100644 --- a/nodes/curve_nodes.py +++ b/nodes/curve_nodes.py @@ -154,15 +154,17 @@ Grow value is the amount to grow the shape on each frame, creating animated mask "default": 'circle' }), "coordinates": ("STRING", {"forceInput": True}), - "grow": ("INT", {"default": 0, "min": -512, "max": 512, "step": 1}), "frame_width": ("INT", {"default": 512,"min": 16, "max": 4096, "step": 1}), "frame_height": ("INT", {"default": 512,"min": 16, "max": 4096, "step": 1}), "shape_width": ("INT", {"default": 128,"min": 8, "max": 4096, "step": 1}), "shape_height": ("INT", {"default": 128,"min": 8, "max": 4096, "step": 1}), }, + "optional": { + "size_multiplier": ("FLOAT", {"default": [1.0], "forceInput": True}), + } } - def createshapemask(self, coordinates, frame_width, frame_height, shape_width, shape_height, grow, shape): + def createshapemask(self, coordinates, frame_width, frame_height, shape_width, shape_height, shape, size_multiplier=[1.0]): # Define the number of images in the batch coordinates = coordinates.replace("'", '"') coordinates = json.loads(coordinates) @@ -170,14 +172,15 @@ Grow value is the amount to grow the shape on each frame, creating animated mask batch_size = len(coordinates) out = [] color = "white" - + if len(size_multiplier) != batch_size: + size_multiplier = size_multiplier * (batch_size // len(size_multiplier)) + size_multiplier[:batch_size % len(size_multiplier)] for i, coord in enumerate(coordinates): image = Image.new("RGB", (frame_width, frame_height), "black") draw = ImageDraw.Draw(image) # Calculate the size for this frame and ensure it's not less than 0 - current_width = max(0, shape_width + i*grow) - current_height = max(0, shape_height + i*grow) + current_width = max(0, shape_width + i * size_multiplier[i]) + current_height = max(0, shape_height + i * size_multiplier[i]) location_x = coord['x'] location_y = coord['y'] @@ -575,7 +578,9 @@ bounding boxes. import matplotlib matplotlib.use('Agg') from matplotlib.backends.backend_agg import FigureCanvasAgg as FigureCanvas - + text_color = '#999999' + bg_color = '#353535' + matplotlib.pyplot.rcParams['text.color'] = text_color fig, ax = matplotlib.pyplot.subplots(figsize=(width/100, height/100), dpi=100) cmap = matplotlib.pyplot.get_cmap('rainbow') @@ -585,29 +590,27 @@ bounding boxes. color = cmap(color_index) box_size = bbox_height * size rect = matplotlib.patches.Rectangle((x - box_size/2, y - box_size/2), box_size, box_size, - linewidth=1, edgecolor=color, facecolor='none', alpha=0.5) + linewidth=1, edgecolor=color, facecolor='none', alpha=0.5) ax.add_patch(rect) - # Draw arrows from one point to another to indicate direction - for i in range(len(coordinates) - 1): - color_index = i / (len(coordinates) - 1) - color = cmap(color_index) - x1, y1 = coordinates[i] - x2, y2 = coordinates[i + 1] - ax.annotate("", xy=(x2, y2), xytext=(x1, y1), - arrowprops=dict(arrowstyle="->", - linestyle="-", - lw=1, - color=color, - mutation_scale=10)) - matplotlib.pyplot.rcParams['text.color'] = '#999999' - fig.patch.set_facecolor('#353535') - ax.set_facecolor('#353535') - ax.grid(color='#999999', linestyle='-', linewidth=0.5) - ax.set_xlabel('x', color='#999999') - ax.set_ylabel('y', color='#999999') + # Check if there is a next coordinate to draw an arrow to + if i < len(coordinates) - 1: + x1, y1 = coordinates[i] + x2, y2 = coordinates[i + 1] + ax.annotate("", xy=(x2, y2), xytext=(x1, y1), + arrowprops=dict(arrowstyle="->", + linestyle="-", + lw=1, + color=color, + mutation_scale=10)) + + fig.patch.set_facecolor(bg_color) + ax.set_facecolor(bg_color) + ax.grid(color=text_color, linestyle='-', linewidth=0.5) + ax.set_xlabel('x', color=text_color) + ax.set_ylabel('y', color=text_color) for text in ax.get_xticklabels() + ax.get_yticklabels(): - text.set_color('#999999') + text.set_color(text_color) ax.set_title('Gligen pos for: ' + prompt) ax.set_xlabel('X Coordinate') ax.set_ylabel('Y Coordinate') From 500f2cc20b3cbdb74d1629a035f59a712e9ec8a5 Mon Sep 17 00:00:00 2001 From: kijai <40791699+kijai@users.noreply.github.com> Date: Thu, 2 May 2024 10:01:44 +0300 Subject: [PATCH 67/95] Update curve_nodes.py --- nodes/curve_nodes.py | 51 ++++++++++++++++++++++---------------------- 1 file changed, 26 insertions(+), 25 deletions(-) diff --git a/nodes/curve_nodes.py b/nodes/curve_nodes.py index 60deb32..282dae1 100644 --- a/nodes/curve_nodes.py +++ b/nodes/curve_nodes.py @@ -582,8 +582,26 @@ bounding boxes. bg_color = '#353535' matplotlib.pyplot.rcParams['text.color'] = text_color fig, ax = matplotlib.pyplot.subplots(figsize=(width/100, height/100), dpi=100) + fig.patch.set_facecolor(bg_color) + ax.set_facecolor(bg_color) + ax.grid(color=text_color, linestyle='-', linewidth=0.5) + ax.set_xlabel('x', color=text_color) + ax.set_ylabel('y', color=text_color) + for text in ax.get_xticklabels() + ax.get_yticklabels(): + text.set_color(text_color) + ax.set_title('Gligen pos for: ' + prompt) + ax.set_xlabel('X Coordinate') + ax.set_ylabel('Y Coordinate') + #ax.legend().remove() + ax.set_xlim(0, width) # Set the x-axis to match the input latent width + ax.set_ylim(height, 0) # Set the y-axis to match the input latent height, with (0,0) at top-left + # Adjust the margins of the subplot + matplotlib.pyplot.subplots_adjust(left=0.08, right=0.95, bottom=0.05, top=0.95, wspace=0.2, hspace=0.2) cmap = matplotlib.pyplot.get_cmap('rainbow') + image_batch = [] + canvas = FigureCanvas(fig) + width, height = fig.get_size_inches() * fig.get_dpi() # Draw a box at each coordinate for i, ((x, y), size) in enumerate(zip(coordinates, size_multiplier)): color_index = i / (len(coordinates) - 1) @@ -603,30 +621,13 @@ bounding boxes. lw=1, color=color, mutation_scale=10)) - - fig.patch.set_facecolor(bg_color) - ax.set_facecolor(bg_color) - ax.grid(color=text_color, linestyle='-', linewidth=0.5) - ax.set_xlabel('x', color=text_color) - ax.set_ylabel('y', color=text_color) - for text in ax.get_xticklabels() + ax.get_yticklabels(): - text.set_color(text_color) - ax.set_title('Gligen pos for: ' + prompt) - ax.set_xlabel('X Coordinate') - ax.set_ylabel('Y Coordinate') - ax.legend().remove() - ax.set_xlim(0, width) # Set the x-axis to match the input latent width - ax.set_ylim(height, 0) # Set the y-axis to match the input latent height, with (0,0) at top-left - # Adjust the margins of the subplot - matplotlib.pyplot.subplots_adjust(left=0.08, right=0.95, bottom=0.05, top=0.95, wspace=0.2, hspace=0.2) - canvas = FigureCanvas(fig) - canvas.draw() + canvas.draw() + image_np = np.frombuffer(canvas.tostring_rgb(), dtype='uint8').reshape(int(height), int(width), 3).copy() + image_tensor = torch.from_numpy(image_np).float() / 255.0 + image_tensor = image_tensor.unsqueeze(0) + image_batch.append(image_tensor) + matplotlib.pyplot.close(fig) + image_batch_tensor = torch.cat(image_batch, dim=0) - width, height = fig.get_size_inches() * fig.get_dpi() - - image_np = np.frombuffer(canvas.tostring_rgb(), dtype='uint8').reshape(int(height), int(width), 3) - image_tensor = torch.from_numpy(image_np).float() / 255.0 - image_tensor = image_tensor.unsqueeze(0) - - return image_tensor \ No newline at end of file + return image_batch_tensor \ No newline at end of file From d31a7ce2a1e5588979a96b26079d795f548dbed4 Mon Sep 17 00:00:00 2001 From: kijai Date: Thu, 2 May 2024 16:53:02 +0300 Subject: [PATCH 68/95] some cleanup --- __init__.py | 3 +- nodes/image_nodes.py | 12 +- nodes/intrinsic_lora_nodes.py | 115 ++++++++++++++ nodes/nodes.py | 284 ++-------------------------------- 4 files changed, 131 insertions(+), 283 deletions(-) create mode 100644 nodes/intrinsic_lora_nodes.py diff --git a/__init__.py b/__init__.py index 0cac1c9..dac9113 100644 --- a/__init__.py +++ b/__init__.py @@ -3,6 +3,7 @@ from .nodes.curve_nodes import * from .nodes.batchcrop_nodes import * from .nodes.audioscheduler_nodes import * from .nodes.image_nodes import * +from .nodes.intrinsic_lora_nodes import * NODE_CLASS_MAPPINGS = { #constants "INTConstant": INTConstant, @@ -106,7 +107,6 @@ NODE_CLASS_MAPPINGS = { "SV3D_BatchSchedule": SV3D_BatchSchedule, "LoadResAdapterNormalization": LoadResAdapterNormalization, "Superprompt": Superprompt, - "GLIGENTextBoxApplyBatch": GLIGENTextBoxApplyBatch, "GLIGENTextBoxApplyBatchCoords": GLIGENTextBoxApplyBatchCoords, "Intrinsic_lora_sampling": Intrinsic_lora_sampling, @@ -175,7 +175,6 @@ NODE_DISPLAY_NAME_MAPPINGS = { "ImageTransformByNormalizedAmplitude": "ImageTransformByNormalizedAmplitude", "GetLatentsFromBatchIndexed": "GetLatentsFromBatchIndexed", "StringConstant": "StringConstant", - "GLIGENTextBoxApplyBatch": "GLIGENTextBoxApplyBatch (deprecated)", "CondPassThrough": "CondPassThrough", "ImageUpscaleWithModelBatched": "ImageUpscaleWithModelBatched", "ScaleBatchPromptSchedule": "ScaleBatchPromptSchedule", diff --git a/nodes/image_nodes.py b/nodes/image_nodes.py index 042a612..5146f7b 100644 --- a/nodes/image_nodes.py +++ b/nodes/image_nodes.py @@ -660,12 +660,12 @@ class ImagePadForOutpaintMasked: if mask is None: new_mask = torch.ones( - (H + top + bottom, W + left + right), + (B, H + top + bottom, W + left + right), dtype=torch.float32, ) t = torch.zeros( - (H, W), + (B, H, W), dtype=torch.float32 ) else: @@ -673,8 +673,6 @@ class ImagePadForOutpaintMasked: mask = F.pad(mask, (left, right, top, bottom), mode='constant', value=0) mask = 1 - mask t = torch.zeros_like(mask) - - if feathering > 0 and feathering * 2 < H and feathering * 2 < W: @@ -694,14 +692,14 @@ class ImagePadForOutpaintMasked: v = (feathering - d) / feathering if mask is None: - t[i, j] = v * v + t[:, i, j] = v * v else: t[:, top + i, left + j] = v * v if mask is None: mask = new_mask.squeeze(0) - mask[top:top + H, left:left + W] = t - mask = mask.unsqueeze(0) + mask[:, top:top + H, left:left + W] = t + #mask = mask.unsqueeze(0) return (new_image, mask,) diff --git a/nodes/intrinsic_lora_nodes.py b/nodes/intrinsic_lora_nodes.py new file mode 100644 index 0000000..3e41181 --- /dev/null +++ b/nodes/intrinsic_lora_nodes.py @@ -0,0 +1,115 @@ +import folder_paths +import os +import torch +import torch.nn.functional as F +from comfy.utils import ProgressBar +import comfy.sample +from nodes import CLIPTextEncode + +script_directory = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +folder_paths.add_model_folder_path("intristic_loras", os.path.join(script_directory, "intristic_loras")) + +class Intrinsic_lora_sampling: + def __init__(self): + self.loaded_lora = None + + @classmethod + def INPUT_TYPES(s): + return {"required": { "model": ("MODEL",), + "lora_name": (folder_paths.get_filename_list("intristic_loras"), ), + "task": ( + [ + 'depth map', + 'surface normals', + 'albedo', + 'shading', + ], + { + "default": 'depth map' + }), + "text": ("STRING", {"multiline": True, "default": ""}), + "clip": ("CLIP", ), + "vae": ("VAE", ), + "per_batch": ("INT", {"default": 16, "min": 1, "max": 4096, "step": 1}), + }, + "optional": { + "image": ("IMAGE",), + "optional_latent": ("LATENT",), + }, + } + + RETURN_TYPES = ("IMAGE", "LATENT",) + FUNCTION = "onestepsample" + CATEGORY = "KJNodes" + DESCRIPTION = """ +Sampler to use the intrinsic loras: +https://github.com/duxiaodan/intrinsic-lora +These LoRAs are tiny and thus included +with this node pack. +""" + + def onestepsample(self, model, lora_name, clip, vae, text, task, per_batch, image=None, optional_latent=None): + pbar = ProgressBar(3) + + if optional_latent is None: + image_list = [] + for start_idx in range(0, image.shape[0], per_batch): + sub_pixels = vae.vae_encode_crop_pixels(image[start_idx:start_idx+per_batch]) + image_list.append(vae.encode(sub_pixels[:,:,:,:3])) + sample = torch.cat(image_list, dim=0) + else: + sample = optional_latent["samples"] + noise = torch.zeros(sample.size(), dtype=sample.dtype, layout=sample.layout, device="cpu") + prompt = task + "," + text + positive, = CLIPTextEncode.encode(self, clip, prompt) + negative = positive #negative shouldn't do anything in this scenario + + pbar.update(1) + + #custom model sampling to pass latent through as it is + class X0_PassThrough(comfy.model_sampling.EPS): + def calculate_denoised(self, sigma, model_output, model_input): + return model_output + def calculate_input(self, sigma, noise): + return noise + sampling_base = comfy.model_sampling.ModelSamplingDiscrete + sampling_type = X0_PassThrough + + class ModelSamplingAdvanced(sampling_base, sampling_type): + pass + model_sampling = ModelSamplingAdvanced(model.model.model_config) + + #load lora + model_clone = model.clone() + lora_path = folder_paths.get_full_path("intristic_loras", lora_name) + lora = comfy.utils.load_torch_file(lora_path, safe_load=True) + self.loaded_lora = (lora_path, lora) + + model_clone_with_lora = comfy.sd.load_lora_for_models(model_clone, None, lora, 1.0, 0)[0] + + model_clone_with_lora.add_object_patch("model_sampling", model_sampling) + + samples = {"samples": comfy.sample.sample(model_clone_with_lora, noise, 1, 1.0, "euler", "simple", positive, negative, sample, + denoise=1.0, disable_noise=True, start_step=0, last_step=1, + force_full_denoise=True, noise_mask=None, callback=None, disable_pbar=True, seed=None)} + pbar.update(1) + + decoded = [] + for start_idx in range(0, samples["samples"].shape[0], per_batch): + decoded.append(vae.decode(samples["samples"][start_idx:start_idx+per_batch])) + image_out = torch.cat(decoded, dim=0) + + pbar.update(1) + + if task == 'depth map': + imax = image_out.max() + imin = image_out.min() + image_out = (image_out-imin)/(imax-imin) + image_out = torch.max(image_out, dim=3, keepdim=True)[0].repeat(1, 1, 1, 3) + elif task == 'surface normals': + image_out = F.normalize(image_out * 2 - 1, dim=3) / 2 + 0.5 + image_out = 1.0 - image_out + else: + image_out = image_out.clamp(-1.,1.) + + return (image_out, samples,) \ No newline at end of file diff --git a/nodes/nodes.py b/nodes/nodes.py index 4c1b815..dcee08a 100644 --- a/nodes/nodes.py +++ b/nodes/nodes.py @@ -14,9 +14,8 @@ import os import io import model_management -from nodes import MAX_RESOLUTION, CLIPTextEncode +from nodes import MAX_RESOLUTION -import comfy.sample import folder_paths from ..utility.utility import tensor2pil, pil2tensor @@ -178,12 +177,7 @@ class CreateFluidMask: return (torch.cat(out, dim=0),torch.cat(masks, dim=0),) class CreateAudioMask: - def __init__(self): - try: - import librosa - self.librosa = librosa - except ImportError: - print("Can not import librosa. Install it with 'pip install librosa'") + RETURN_TYPES = ("IMAGE",) FUNCTION = "createaudiomask" CATEGORY = "KJNodes/deprecated" @@ -202,14 +196,17 @@ class CreateAudioMask: } def createaudiomask(self, frames, width, height, invert, audio_path, scale): - # Define the number of images in the batch + try: + import librosa + except ImportError: + raise Exception("Can not import librosa. Install it with 'pip install librosa'") batch_size = frames out = [] masks = [] if audio_path == "audio.wav": #I don't know why relative path won't work otherwise... audio_path = os.path.join(script_directory, audio_path) - audio, sr = self.librosa.load(audio_path) - spectrogram = np.abs(self.librosa.stft(audio)) + audio, sr = librosa.load(audio_path) + spectrogram = np.abs(librosa.stft(audio)) for i in range(batch_size): image = Image.new("RGB", (width, height), "black") @@ -2432,265 +2429,6 @@ https://huggingface.co/stabilityai/sv3d latent = torch.zeros([batch_size, 4, height // 8, width // 8]) return (final_positive, final_negative, {"samples": latent}) - - - -def parse_coordinates(coordinates_str): - coordinates = {} - pattern = r'(\d+):\((\d+),(\d+)\)' - matches = re.findall(pattern, coordinates_str) - for match in matches: - index, x, y = map(int, match) - coordinates[index] = (x, y) - return coordinates - -def interpolate_coordinates(coordinates_dict, batch_size): - sorted_coords = sorted(coordinates_dict.items()) - interpolated = {} - - for i, ((index1, (x1, y1)), (index2, (x2, y2))) in enumerate(zip(sorted_coords, sorted_coords[1:])): - distance = index2 - index1 - x_step = (x2 - x1) / distance - y_step = (y2 - y1) / distance - - for j in range(distance): - interpolated_x = round(x1 + j * x_step) - interpolated_y = round(y1 + j * y_step) - interpolated[index1 + j] = (interpolated_x, interpolated_y) - interpolated[sorted_coords[-1][0]] = sorted_coords[-1][1] - - # Ensure we have coordinates for all indices in the batch - last_index, last_coords = sorted_coords[-1] - for i in range(last_index + 1, batch_size): - interpolated[i] = last_coords - - return interpolated - -def interpolate_coordinates_with_curves(coordinates_dict, batch_size): - from scipy.interpolate import CubicSpline - sorted_coords = sorted(coordinates_dict.items()) - x_coords, y_coords = zip(*[coord for index, coord in sorted_coords]) - - # Create the spline curve functions - indices = np.array([index for index, coord in sorted_coords]) - cs_x = CubicSpline(indices, x_coords) - cs_y = CubicSpline(indices, y_coords) - - # Generate interpolated coordinates using the spline functions - interpolated_indices = np.arange(0, batch_size) - interpolated_x = cs_x(interpolated_indices) - interpolated_y = cs_y(interpolated_indices) - - # Round the interpolated coordinates and create the dictionary - interpolated = {i: (round(x), round(y)) for i, (x, y) in enumerate(zip(interpolated_x, interpolated_y))} - return interpolated - -def plot_to_tensor(coordinates_dict, interpolated_dict, height, width, box_size): - from matplotlib.backends.backend_agg import FigureCanvasAgg as FigureCanvas - import matplotlib.patches as patches - - original_x, original_y = zip(*coordinates_dict.values()) - interpolated_x, interpolated_y = zip(*interpolated_dict.values()) - - fig, ax = plt.subplots(figsize=(width/100, height/100), dpi=100) - ax.scatter(original_x, original_y, color='blue', label='Original Points') - ax.scatter(interpolated_x, interpolated_y, color='red', alpha=0.5, label='Interpolated Points') - ax.plot(interpolated_x, interpolated_y, color='grey', linestyle='--', linewidth=0.5) - # Draw a box at each interpolated coordinate - for x, y in interpolated_dict.values(): - rect = patches.Rectangle((x - box_size/2, y - box_size/2), box_size, box_size, - linewidth=1, edgecolor='green', facecolor='none') - ax.add_patch(rect) - ax.set_title('Interpolated Coordinates') - ax.set_xlabel('X Coordinate') - ax.set_ylabel('Y Coordinate') - ax.legend() - ax.set_xlim(0, width) # Set the x-axis to match the input latent width - ax.set_ylim(height, 0) # Set the y-axis to match the input latent height, with (0,0) at top-left - - canvas = FigureCanvas(fig) - canvas.draw() - - width, height = fig.get_size_inches() * fig.get_dpi() - image_np = np.frombuffer(canvas.tostring_rgb(), dtype='uint8').reshape(int(height), int(width), 3) - - image_tensor = torch.from_numpy(image_np).float() / 255.0 - image_tensor = image_tensor.unsqueeze(0) - - plt.close(fig) - - return image_tensor - -class GLIGENTextBoxApplyBatch: - @classmethod - def INPUT_TYPES(s): - return {"required": {"conditioning_to": ("CONDITIONING", ), - "latents": ("LATENT", ), - "clip": ("CLIP", ), - "gligen_textbox_model": ("GLIGEN", ), - "text": ("STRING", {"multiline": True}), - "width": ("INT", {"default": 64, "min": 8, "max": MAX_RESOLUTION, "step": 8}), - "height": ("INT", {"default": 64, "min": 8, "max": MAX_RESOLUTION, "step": 8}), - "coordinates": ("STRING", {"multiline": True}), - "interpolation": ( - [ - 'straight', - 'CubicSpline', - ], - { - "default": 'CubicSpline' - }), - }} - RETURN_TYPES = ("CONDITIONING", "IMAGE",) - FUNCTION = "append" - CATEGORY = "KJNodes/experimental" - DESCRIPTION = """ -Experimental, deprecated, check the GLIGENTextBoxApplyBatchCoords instead. -""" - - def append(self, latents, conditioning_to, clip, gligen_textbox_model, text, width, height, coordinates, interpolation): - - coordinates_dict = parse_coordinates(coordinates) - batch_size = sum(tensor.size(0) for tensor in latents.values()) - c = [] - cond, cond_pooled = clip.encode_from_tokens(clip.tokenize(text), return_pooled=True) - - # Interpolate coordinates for the entire batch - if interpolation == 'CubicSpline': - interpolated_coords = interpolate_coordinates_with_curves(coordinates_dict, batch_size) - if interpolation == 'straight': - interpolated_coords = interpolate_coordinates(coordinates_dict, batch_size) - - plot_image_tensor = plot_to_tensor(coordinates_dict, interpolated_coords, 512, 512, height) - for t in conditioning_to: - n = [t[0], t[1].copy()] - - position_params_batch = [[] for _ in range(batch_size)] # Initialize a list of empty lists for each batch item - - for i in range(batch_size): - x_position, y_position = interpolated_coords[i] - position_param = (cond_pooled, height // 8, width // 8, y_position // 8, x_position // 8) - position_params_batch[i].append(position_param) # Append position_param to the correct sublist - print("x ",x_position, "y ", y_position) - prev = [] - if "gligen" in n[1]: - prev = n[1]['gligen'][2] - else: - prev = [[] for _ in range(batch_size)] - # Concatenate prev and position_params_batch, ensuring both are lists of lists - # and each sublist corresponds to a batch item - combined_position_params = [prev_item + batch_item for prev_item, batch_item in zip(prev, position_params_batch)] - n[1]['gligen'] = ("position_batched", gligen_textbox_model, combined_position_params) - c.append(n) - - return (c, plot_image_tensor,) - -folder_paths.add_model_folder_path("intristic_loras", os.path.join(script_directory, "intristic_loras")) - -class Intrinsic_lora_sampling: - def __init__(self): - self.loaded_lora = None - - @classmethod - def INPUT_TYPES(s): - return {"required": { "model": ("MODEL",), - "lora_name": (folder_paths.get_filename_list("intristic_loras"), ), - "task": ( - [ - 'depth map', - 'surface normals', - 'albedo', - 'shading', - ], - { - "default": 'depth map' - }), - "text": ("STRING", {"multiline": True, "default": ""}), - "clip": ("CLIP", ), - "vae": ("VAE", ), - "per_batch": ("INT", {"default": 16, "min": 1, "max": 4096, "step": 1}), - }, - "optional": { - "image": ("IMAGE",), - "optional_latent": ("LATENT",), - }, - } - - RETURN_TYPES = ("IMAGE", "LATENT",) - FUNCTION = "onestepsample" - CATEGORY = "KJNodes" - DESCRIPTION = """ -Sampler to use the intrinsic loras: -https://github.com/duxiaodan/intrinsic-lora -These LoRAs are tiny and thus included -with this node pack. -""" - - def onestepsample(self, model, lora_name, clip, vae, text, task, per_batch, image=None, optional_latent=None): - pbar = comfy.utils.ProgressBar(3) - - if optional_latent is None: - image_list = [] - for start_idx in range(0, image.shape[0], per_batch): - sub_pixels = vae.vae_encode_crop_pixels(image[start_idx:start_idx+per_batch]) - image_list.append(vae.encode(sub_pixels[:,:,:,:3])) - sample = torch.cat(image_list, dim=0) - else: - sample = optional_latent["samples"] - noise = torch.zeros(sample.size(), dtype=sample.dtype, layout=sample.layout, device="cpu") - prompt = task + "," + text - positive, = CLIPTextEncode.encode(self, clip, prompt) - negative = positive #negative shouldn't do anything in this scenario - - pbar.update(1) - - #custom model sampling to pass latent through as it is - class X0_PassThrough(comfy.model_sampling.EPS): - def calculate_denoised(self, sigma, model_output, model_input): - return model_output - def calculate_input(self, sigma, noise): - return noise - sampling_base = comfy.model_sampling.ModelSamplingDiscrete - sampling_type = X0_PassThrough - - class ModelSamplingAdvanced(sampling_base, sampling_type): - pass - model_sampling = ModelSamplingAdvanced(model.model.model_config) - - #load lora - model_clone = model.clone() - lora_path = folder_paths.get_full_path("intristic_loras", lora_name) - lora = comfy.utils.load_torch_file(lora_path, safe_load=True) - self.loaded_lora = (lora_path, lora) - - model_clone_with_lora = comfy.sd.load_lora_for_models(model_clone, None, lora, 1.0, 0)[0] - - model_clone_with_lora.add_object_patch("model_sampling", model_sampling) - - samples = {"samples": comfy.sample.sample(model_clone_with_lora, noise, 1, 1.0, "euler", "simple", positive, negative, sample, - denoise=1.0, disable_noise=True, start_step=0, last_step=1, - force_full_denoise=True, noise_mask=None, callback=None, disable_pbar=True, seed=None)} - pbar.update(1) - - decoded = [] - for start_idx in range(0, samples["samples"].shape[0], per_batch): - decoded.append(vae.decode(samples["samples"][start_idx:start_idx+per_batch])) - image_out = torch.cat(decoded, dim=0) - - pbar.update(1) - - if task == 'depth map': - imax = image_out.max() - imin = image_out.min() - image_out = (image_out-imin)/(imax-imin) - image_out = torch.max(image_out, dim=3, keepdim=True)[0].repeat(1, 1, 1, 3) - elif task == 'surface normals': - image_out = F.normalize(image_out * 2 - 1, dim=3) / 2 + 0.5 - image_out = 1.0 - image_out - else: - image_out = image_out.clamp(-1.,1.) - - return (image_out, samples,) class RemapMaskRange: @classmethod @@ -2842,7 +2580,6 @@ or a .txt file with RealEstate camera intrinsics and coordinates, in a 3D plot. def plot(self, pose_file_path, scale, base_xval, zval, use_exact_fx, relative_c2w, use_viewer, cameractrl_poses=None): import matplotlib as mpl import matplotlib.pyplot as plt - import io from torchvision.transforms import ToTensor x_min = -2.0 * scale @@ -3052,7 +2789,6 @@ If no image is provided, mode is set to text-to-image args.disable_metadata = False import requests - from io import BytesIO from torchvision import transforms data = { @@ -3068,7 +2804,7 @@ If no image is provided, mode is set to text-to-image to_pil = transforms.ToPILImage() pil_image = to_pil(image) # Save the PIL Image to a BytesIO object - buffer = BytesIO() + buffer = io.BytesIO() pil_image.save(buffer, format='PNG') buffer.seek(0) files = {"image": ("image.png", buffer, "image/png")} @@ -3105,7 +2841,7 @@ If no image is provided, mode is set to text-to-image if response.status_code == 200: # Convert the response content to a PIL Image - image = Image.open(BytesIO(response.content)) + image = Image.open(io.BytesIO(response.content)) # Convert the PIL Image to a PyTorch tensor transform = transforms.ToTensor() tensor_image = transform(image) From 9c6da66100b32b72c3ededc15078eaedf2e4bb6b Mon Sep 17 00:00:00 2001 From: kijai <40791699+kijai@users.noreply.github.com> Date: Thu, 2 May 2024 18:19:05 +0300 Subject: [PATCH 69/95] Update curve_nodes.py --- nodes/curve_nodes.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nodes/curve_nodes.py b/nodes/curve_nodes.py index 282dae1..b1568a8 100644 --- a/nodes/curve_nodes.py +++ b/nodes/curve_nodes.py @@ -290,7 +290,7 @@ class WeightScheduleConvert: }, } - RETURN_TYPES = ("FLOAT", "STRING",) + RETURN_TYPES = ("FLOAT", "STRING", "INT",) FUNCTION = "execute" CATEGORY = "KJNodes" DESCRIPTION = """ @@ -357,7 +357,7 @@ Converts different value lists/series to another type. out = torch.tensor(float_values, dtype=torch.float32), elif output_type == 'match_input': out = float_values, - return (out, [str(value) for value in float_values],) + return (out, [str(value) for value in float_values], [int(value) for value in float_values]) class FloatToMask: From 7b2b1c35c132ea69d990a0df7900dfbe0f6467ca Mon Sep 17 00:00:00 2001 From: kijai <40791699+kijai@users.noreply.github.com> Date: Thu, 2 May 2024 20:50:45 +0300 Subject: [PATCH 70/95] Update image_nodes.py --- nodes/image_nodes.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/nodes/image_nodes.py b/nodes/image_nodes.py index 5146f7b..4a11f3f 100644 --- a/nodes/image_nodes.py +++ b/nodes/image_nodes.py @@ -636,7 +636,7 @@ class ImagePadForOutpaintMasked: "top": ("INT", {"default": 0, "min": 0, "max": MAX_RESOLUTION, "step": 8}), "right": ("INT", {"default": 0, "min": 0, "max": MAX_RESOLUTION, "step": 8}), "bottom": ("INT", {"default": 0, "min": 0, "max": MAX_RESOLUTION, "step": 8}), - "feathering": ("INT", {"default": 40, "min": 0, "max": MAX_RESOLUTION, "step": 1}), + "feathering": ("INT", {"default": 0, "min": 0, "max": MAX_RESOLUTION, "step": 1}), }, "optional": { "mask": ("MASK",), @@ -697,9 +697,8 @@ class ImagePadForOutpaintMasked: t[:, top + i, left + j] = v * v if mask is None: - mask = new_mask.squeeze(0) + mask = new_mask mask[:, top:top + H, left:left + W] = t - #mask = mask.unsqueeze(0) return (new_image, mask,) From b47aa365f53975605980fde3fcecd21209740719 Mon Sep 17 00:00:00 2001 From: kijai <40791699+kijai@users.noreply.github.com> Date: Thu, 2 May 2024 20:59:29 +0300 Subject: [PATCH 71/95] Update nodes.py --- nodes/nodes.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/nodes/nodes.py b/nodes/nodes.py index dcee08a..f0c653f 100644 --- a/nodes/nodes.py +++ b/nodes/nodes.py @@ -18,6 +18,7 @@ from nodes import MAX_RESOLUTION import folder_paths from ..utility.utility import tensor2pil, pil2tensor +from comfy.utils import ProgressBar script_directory = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) folder_paths.add_model_folder_path("kjnodes_fonts", os.path.join(script_directory, "fonts")) @@ -751,7 +752,7 @@ controls the number of images processed at once. black, white = white, black steps = images.shape[0] - pbar = comfy.utils.ProgressBar(steps) + pbar = ProgressBar(steps) tensors_out = [] for start_idx in range(0, images.shape[0], per_batch): @@ -1345,15 +1346,15 @@ Segments an image or batch of images using CLIPSeg. device = torch.device("cuda") else: device = torch.device("cpu") - dtype = comfy.model_management.unet_dtype() + dtype = model_management.unet_dtype() model = CLIPSegForImageSegmentation.from_pretrained("CIDAS/clipseg-rd64-refined") model.to(dtype) model.to(device) images = images.to(device) processor = CLIPSegProcessor.from_pretrained("CIDAS/clipseg-rd64-refined") - pbar = comfy.utils.ProgressBar(images.shape[0]) - autocast_condition = (dtype != torch.float32) and not comfy.model_management.is_device_mps(device) - with torch.autocast(comfy.model_management.get_autocast_device(device), dtype=dtype) if autocast_condition else nullcontext(): + pbar = ProgressBar(images.shape[0]) + autocast_condition = (dtype != torch.float32) and not model_management.is_device_mps(device) + with torch.autocast(model_management.get_autocast_device(device), dtype=dtype) if autocast_condition else nullcontext(): for image in images: image = (image* 255).type(torch.uint8) prompt = text From 654fb272613003b152ac2c4bbfc05c6a61ace672 Mon Sep 17 00:00:00 2001 From: kijai <40791699+kijai@users.noreply.github.com> Date: Thu, 2 May 2024 21:15:16 +0300 Subject: [PATCH 72/95] Update image_nodes.py --- nodes/image_nodes.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/nodes/image_nodes.py b/nodes/image_nodes.py index 4a11f3f..7241239 100644 --- a/nodes/image_nodes.py +++ b/nodes/image_nodes.py @@ -697,10 +697,10 @@ class ImagePadForOutpaintMasked: t[:, top + i, left + j] = v * v if mask is None: - mask = new_mask - mask[:, top:top + H, left:left + W] = t - - return (new_image, mask,) + new_mask[:, top:top + H, left:left + W] = t + return (new_image, new_mask,) + else: + return (new_image, mask,) class ImageAndMaskPreview(SaveImage): def __init__(self): From 90f639c6f5ba8b6dcf773260825e68fb862978fb Mon Sep 17 00:00:00 2001 From: kijai <40791699+kijai@users.noreply.github.com> Date: Thu, 2 May 2024 21:55:34 +0300 Subject: [PATCH 73/95] Update image_nodes.py --- nodes/image_nodes.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/nodes/image_nodes.py b/nodes/image_nodes.py index 7241239..96db37a 100644 --- a/nodes/image_nodes.py +++ b/nodes/image_nodes.py @@ -649,6 +649,10 @@ class ImagePadForOutpaintMasked: CATEGORY = "image" def expand_image(self, image, left, top, right, bottom, feathering, mask=None): + if mask is not None: + if torch.allclose(mask, torch.zeros_like(mask)): + print("Warning: The incoming mask is fully black. Handling it as None.") + mask = None B, H, W, C = image.size() new_image = torch.ones( From 9fa6a26689036032e3e789472035de1c2f984005 Mon Sep 17 00:00:00 2001 From: kijai <40791699+kijai@users.noreply.github.com> Date: Thu, 2 May 2024 23:54:12 +0300 Subject: [PATCH 74/95] Fix GLIGEN bbox origin --- nodes/curve_nodes.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/nodes/curve_nodes.py b/nodes/curve_nodes.py index b1568a8..c48e672 100644 --- a/nodes/curve_nodes.py +++ b/nodes/curve_nodes.py @@ -554,7 +554,7 @@ bounding boxes. for i in range(batch_size): x_position, y_position = coordinates[i] - position_param = (cond_pooled, int((height // 8) * size_multiplier[i]), int((width // 8) * size_multiplier[i]), y_position // 8, x_position // 8) + position_param = (cond_pooled, int((height // 8) * size_multiplier[i]), int((width // 8) * size_multiplier[i]), (y_position - height / 2) // 8, (x_position - width / 2) // 8) position_params_batch[i].append(position_param) # Append position_param to the correct sublist prev = [] @@ -570,11 +570,11 @@ bounding boxes. image_height = latents['samples'].shape[-2] * 8 image_width = latents['samples'].shape[-1] * 8 - plot_image_tensor = self.plot_coordinates_to_tensor(coordinates, image_height, image_width, height, size_multiplier, text) + plot_image_tensor = self.plot_coordinates_to_tensor(coordinates, image_height, image_width, height, width, size_multiplier, text) return (c, plot_image_tensor,) - def plot_coordinates_to_tensor(self, coordinates, height, width, bbox_height, size_multiplier, prompt): + def plot_coordinates_to_tensor(self, coordinates, height, width, bbox_height, bbox_width, size_multiplier, prompt): import matplotlib matplotlib.use('Agg') from matplotlib.backends.backend_agg import FigureCanvasAgg as FigureCanvas @@ -606,8 +606,9 @@ bounding boxes. for i, ((x, y), size) in enumerate(zip(coordinates, size_multiplier)): color_index = i / (len(coordinates) - 1) color = cmap(color_index) - box_size = bbox_height * size - rect = matplotlib.patches.Rectangle((x - box_size/2, y - box_size/2), box_size, box_size, + draw_height = bbox_height * size + draw_width = bbox_width * size + rect = matplotlib.patches.Rectangle((x - draw_width/2, y - draw_height/2), draw_width, draw_height, linewidth=1, edgecolor=color, facecolor='none', alpha=0.5) ax.add_patch(rect) @@ -620,7 +621,7 @@ bounding boxes. linestyle="-", lw=1, color=color, - mutation_scale=10)) + mutation_scale=20)) canvas.draw() image_np = np.frombuffer(canvas.tostring_rgb(), dtype='uint8').reshape(int(height), int(width), 3).copy() image_tensor = torch.from_numpy(image_np).float() / 255.0 From 85bd6dfccbf7ff85ad50d9569c725d2cb39a4b03 Mon Sep 17 00:00:00 2001 From: kijai <40791699+kijai@users.noreply.github.com> Date: Fri, 3 May 2024 01:00:33 +0300 Subject: [PATCH 75/95] import fixes --- nodes/intrinsic_lora_nodes.py | 4 ++-- nodes/nodes.py | 6 ++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/nodes/intrinsic_lora_nodes.py b/nodes/intrinsic_lora_nodes.py index 3e41181..798cf74 100644 --- a/nodes/intrinsic_lora_nodes.py +++ b/nodes/intrinsic_lora_nodes.py @@ -2,7 +2,7 @@ import folder_paths import os import torch import torch.nn.functional as F -from comfy.utils import ProgressBar +from comfy.utils import ProgressBar, load_torch_file import comfy.sample from nodes import CLIPTextEncode @@ -82,7 +82,7 @@ with this node pack. #load lora model_clone = model.clone() lora_path = folder_paths.get_full_path("intristic_loras", lora_name) - lora = comfy.utils.load_torch_file(lora_path, safe_load=True) + lora = load_torch_file(lora_path, safe_load=True) self.loaded_lora = (lora_path, lora) model_clone_with_lora = comfy.sd.load_lora_for_models(model_clone, None, lora, 1.0, 0)[0] diff --git a/nodes/nodes.py b/nodes/nodes.py index f0c653f..154345a 100644 --- a/nodes/nodes.py +++ b/nodes/nodes.py @@ -2195,9 +2195,10 @@ class StableZero123_BatchSchedule: CATEGORY = "KJNodes/experimental" def encode(self, clip_vision, init_image, vae, width, height, batch_size, azimuth_points_string, elevation_points_string, interpolation): + from comfy.utils import common_upscale output = clip_vision.encode_image(init_image) pooled = output.image_embeds.unsqueeze(0) - pixels = comfy.utils.common_upscale(init_image.movedim(-1,1), width, height, "bilinear", "center").movedim(1,-1) + pixels = common_upscale(init_image.movedim(-1,1), width, height, "bilinear", "center").movedim(1,-1) encode_pixels = pixels[:,:,:,:3] t = vae.encode(encode_pixels) @@ -2488,9 +2489,10 @@ class LoadResAdapterNormalization: raise Exception("Invalid model path") else: print("ResAdapter: Loading ResAdapter normalization weights") + from comfy.utils import load_torch_file prefix_to_remove = 'diffusion_model.' model_clone = model.clone() - norm_state_dict = comfy.utils.load_torch_file(resadapter_full_path) + norm_state_dict = load_torch_file(resadapter_full_path) new_values = {key[len(prefix_to_remove):]: value for key, value in norm_state_dict.items() if key.startswith(prefix_to_remove)} print("ResAdapter: Attempting to add patches with ResAdapter weights") try: From 28ff3676b67c88460b42045f98994ca43701c51b Mon Sep 17 00:00:00 2001 From: Kijai <40791699+kijai@users.noreply.github.com> Date: Fri, 3 May 2024 11:27:01 +0300 Subject: [PATCH 76/95] Continue restructuring --- __init__.py | 295 ++++------- nodes/mask_nodes.py | 1165 +++++++++++++++++++++++++++++++++++++++++++ nodes/nodes.py | 1156 +----------------------------------------- 3 files changed, 1273 insertions(+), 1343 deletions(-) create mode 100644 nodes/mask_nodes.py diff --git a/__init__.py b/__init__.py index dac9113..adcf11e 100644 --- a/__init__.py +++ b/__init__.py @@ -4,207 +4,122 @@ from .nodes.batchcrop_nodes import * from .nodes.audioscheduler_nodes import * from .nodes.image_nodes import * from .nodes.intrinsic_lora_nodes import * -NODE_CLASS_MAPPINGS = { +from .nodes.mask_nodes import * +NODE_CONFIG = { #constants - "INTConstant": INTConstant, - "FloatConstant": FloatConstant, - "StringConstant": StringConstant, - "StringConstantMultiline": StringConstantMultiline, + "INTConstant": {"class": INTConstant, "name": "INT Constant"}, + "FloatConstant": {"class": FloatConstant, "name": "Float Constant"}, + "StringConstant": {"class": StringConstant, "name": "String Constant"}, + "StringConstantMultiline": {"class": StringConstantMultiline, "name": "String Constant Multiline"}, #conditioning - "ConditioningMultiCombine": ConditioningMultiCombine, - "ConditioningSetMaskAndCombine": ConditioningSetMaskAndCombine, - "ConditioningSetMaskAndCombine3": ConditioningSetMaskAndCombine3, - "ConditioningSetMaskAndCombine4": ConditioningSetMaskAndCombine4, - "ConditioningSetMaskAndCombine5": ConditioningSetMaskAndCombine5, - "CondPassThrough": CondPassThrough, + "ConditioningMultiCombine": {"class": ConditioningMultiCombine, "name": "Conditioning Multi Combine"}, + "ConditioningSetMaskAndCombine": {"class": ConditioningSetMaskAndCombine, "name": "ConditioningSetMaskAndCombine"}, + "ConditioningSetMaskAndCombine3": {"class": ConditioningSetMaskAndCombine3, "name": "ConditioningSetMaskAndCombine3"}, + "ConditioningSetMaskAndCombine4": {"class": ConditioningSetMaskAndCombine4, "name": "ConditioningSetMaskAndCombine4"}, + "ConditioningSetMaskAndCombine5": {"class": ConditioningSetMaskAndCombine5, "name": "ConditioningSetMaskAndCombine5"}, + "CondPassThrough": {"class": CondPassThrough}, #masking - "BatchCLIPSeg": BatchCLIPSeg, - "RoundMask": RoundMask, - "ResizeMask": ResizeMask, - "OffsetMask": OffsetMask, - "MaskBatchMulti": MaskBatchMulti, - "GrowMaskWithBlur": GrowMaskWithBlur, - "ColorToMask": ColorToMask, - "CreateGradientMask": CreateGradientMask, - "CreateTextMask": CreateTextMask, - "CreateAudioMask": CreateAudioMask, - "CreateFadeMask": CreateFadeMask, - "CreateFadeMaskAdvanced": CreateFadeMaskAdvanced, - "CreateFluidMask" :CreateFluidMask, - "CreateShapeMask": CreateShapeMask, - "CreateVoronoiMask": CreateVoronoiMask, - "CreateMagicMask": CreateMagicMask, - "RemapMaskRange": RemapMaskRange, - "GetMaskSize": GetMaskSize, + "BatchCLIPSeg": {"class": BatchCLIPSeg, "name": "Batch CLIPSeg"}, + "ColorToMask": {"class": ColorToMask, "name": "Color To Mask"}, + "CreateGradientMask": {"class": CreateGradientMask, "name": "Create Gradient Mask"}, + "CreateTextMask": {"class": CreateTextMask, "name": "Create Text Mask"}, + "CreateAudioMask": {"class": CreateAudioMask, "name": "Create Audio Mask"}, + "CreateFadeMask": {"class": CreateFadeMask, "name": "Create Fade Mask"}, + "CreateFadeMaskAdvanced": {"class": CreateFadeMaskAdvanced, "name": "Create Fade Mask Advanced"}, + "CreateFluidMask": {"class": CreateFluidMask, "name": "Create Fluid Mask"}, + "CreateShapeMask": {"class": CreateShapeMask, "name": "Create Shape Mask"}, + "CreateVoronoiMask": {"class": CreateVoronoiMask, "name": "Create Voronoi Mask"}, + "CreateMagicMask": {"class": CreateMagicMask, "name": "Create Magic Mask"}, + "GetMaskSize": {"class": GetMaskSize, "name": "Get Mask Size"}, + "GrowMaskWithBlur": {"class": GrowMaskWithBlur, "name": "Grow Mask With Blur"}, + "MaskBatchMulti": {"class": MaskBatchMulti, "name": "Mask Batch Multi"}, + "OffsetMask": {"class": OffsetMask, "name": "Offset Mask"}, + "RemapMaskRange": {"class": RemapMaskRange, "name": "Remap Mask Range"}, + "ResizeMask": {"class": ResizeMask, "name": "Resize Mask"}, + "RoundMask": {"class": RoundMask, "name": "Round Mask"}, #images - "ImageBatchMulti": ImageBatchMulti, - "ColorMatch": ColorMatch, - "CrossFadeImages": CrossFadeImages, - "GetImageRangeFromBatch": GetImageRangeFromBatch, - "SaveImageWithAlpha": SaveImageWithAlpha, - "ReverseImageBatch": ReverseImageBatch, - "ImageGridComposite2x2": ImageGridComposite2x2, - "ImageGridComposite3x3": ImageGridComposite3x3, - "ImageConcanate": ImageConcanate, - "ImageBatchTestPattern": ImageBatchTestPattern, - "ReplaceImagesInBatch": ReplaceImagesInBatch, - "ImageGrabPIL": ImageGrabPIL, - "AddLabel": AddLabel, - "ImageUpscaleWithModelBatched": ImageUpscaleWithModelBatched, - "GetImagesFromBatchIndexed": GetImagesFromBatchIndexed, - "InsertImagesToBatchIndexed": InsertImagesToBatchIndexed, - "ImageBatchRepeatInterleaving": ImageBatchRepeatInterleaving, - "ImageNormalize_Neg1_To_1": ImageNormalize_Neg1_To_1, - "RemapImageRange": RemapImageRange, - "ImagePass": ImagePass, - "ImagePadForOutpaintMasked": ImagePadForOutpaintMasked, - "ImageAndMaskPreview": ImageAndMaskPreview, - "SplitImageChannels": SplitImageChannels, - "MergeImageChannels": MergeImageChannels, + "ColorMatch": {"class": ColorMatch, "name": "Color Match"}, + "AddLabel": {"class": AddLabel, "name": "Add Label"}, + "ImageBatchMulti": {"class": ImageBatchMulti, "name": "Image Batch Multi"}, + "ImageBatchTestPattern": {"class": ImageBatchTestPattern, "name": "Image Batch Test Pattern"}, + "ImageConcanate": {"class": ImageConcanate, "name": "Image Concanate"}, + "ImageGrabPIL": {"class": ImageGrabPIL, "name": "Image Grab PIL"}, + "ImageGridComposite2x2": {"class": ImageGridComposite2x2, "name": "Image Grid Composite 2x2"}, + "ImageGridComposite3x3": {"class": ImageGridComposite3x3, "name": "Image Grid Composite 3x3"}, + "ImageNormalize_Neg1_To_1": {"class": ImageNormalize_Neg1_To_1, "name": "Image Normalize -1 to 1"}, + "ImagePass": {"class": ImagePass}, + "ImagePadForOutpaintMasked": {"class": ImagePadForOutpaintMasked, "name": "Image Pad For Outpaint Masked"}, + "ImageUpscaleWithModelBatched": {"class": ImageUpscaleWithModelBatched, "name": "Image Upscale With Model Batched"}, + "InsertImagesToBatchIndexed": {"class": InsertImagesToBatchIndexed, "name": "Insert Images To Batch Indexed"}, + "MergeImageChannels": {"class": MergeImageChannels, "name": "Merge Image Channels"}, + "ReverseImageBatch": {"class": ReverseImageBatch, "name": "Reverse Image Batch"}, + "RemapImageRange": {"class": RemapImageRange, "name": "Remap Image Range"}, + "SaveImageWithAlpha": {"class": SaveImageWithAlpha, "name": "Save Image With Alpha"}, + "SplitImageChannels": {"class": SplitImageChannels, "name": "Split Image Channels"}, + "CrossFadeImages": {"class": CrossFadeImages, "name": "Cross Fade Images"}, + "GetImageRangeFromBatch": {"class": GetImageRangeFromBatch, "name": "Get Image Range From Batch"}, #batch cropping - "BatchCropFromMask": BatchCropFromMask, - "BatchCropFromMaskAdvanced": BatchCropFromMaskAdvanced, - "FilterZeroMasksAndCorrespondingImages": FilterZeroMasksAndCorrespondingImages, - "InsertImageBatchByIndexes": InsertImageBatchByIndexes, - "BatchUncrop": BatchUncrop, - "BatchUncropAdvanced": BatchUncropAdvanced, - "SplitBboxes": SplitBboxes, - "BboxToInt": BboxToInt, - "BboxVisualize": BboxVisualize, + "BatchCropFromMask": {"class": BatchCropFromMask, "name": "Batch Crop From Mask"}, + "BatchCropFromMaskAdvanced": {"class": BatchCropFromMaskAdvanced, "name": "Batch Crop From Mask Advanced"}, + "FilterZeroMasksAndCorrespondingImages": {"class": FilterZeroMasksAndCorrespondingImages}, + "InsertImageBatchByIndexes": {"class": InsertImageBatchByIndexes, "name": "Insert Image Batch By Indexes"}, + "BatchUncrop": {"class": BatchUncrop, "name": "Batch Uncrop"}, + "BatchUncropAdvanced": {"class": BatchUncropAdvanced, "name": "Batch Uncrop Advanced"}, + "SplitBboxes": {"class": SplitBboxes, "name": "Split Bboxes"}, + "BboxToInt": {"class": BboxToInt, "name": "Bbox To Int"}, + "BboxVisualize": {"class": BboxVisualize, "name": "Bbox Visualize"}, #noise - "GenerateNoise": GenerateNoise, - "FlipSigmasAdjusted": FlipSigmasAdjusted, - "InjectNoiseToLatent": InjectNoiseToLatent, - "CustomSigmas": CustomSigmas, + "GenerateNoise": {"class": GenerateNoise, "name": "Generate Noise"}, + "FlipSigmasAdjusted": {"class": FlipSigmasAdjusted, "name": "Flip Sigmas Adjusted"}, + "InjectNoiseToLatent": {"class": InjectNoiseToLatent, "name": "Inject Noise To Latent"}, + "CustomSigmas": {"class": CustomSigmas, "name": "Custom Sigmas"}, #utility - "WidgetToString": WidgetToString, - "DummyLatentOut": DummyLatentOut, - "GetLatentsFromBatchIndexed": GetLatentsFromBatchIndexed, - "ScaleBatchPromptSchedule": ScaleBatchPromptSchedule, - "CameraPoseVisualizer": CameraPoseVisualizer, - "JoinStrings": JoinStrings, - "JoinStringMulti": JoinStringMulti, - "Sleep": Sleep, - "VRAM_Debug" : VRAM_Debug, - "SomethingToString" : SomethingToString, - "EmptyLatentImagePresets": EmptyLatentImagePresets, + "WidgetToString": {"class": WidgetToString, "name": "Widget To String"}, + "DummyLatentOut": {"class": DummyLatentOut, "name": "Dummy Latent Out"}, + "GetLatentsFromBatchIndexed": {"class": GetLatentsFromBatchIndexed, "name": "Get Latents From Batch Indexed"}, + "ScaleBatchPromptSchedule": {"class": ScaleBatchPromptSchedule, "name": "Scale Batch Prompt Schedule"}, + "CameraPoseVisualizer": {"class": CameraPoseVisualizer, "name": "Camera Pose Visualizer"}, + "JoinStrings": {"class": JoinStrings, "name": "Join Strings"}, + "JoinStringMulti": {"class": JoinStringMulti, "name": "Join String Multi"}, + "Sleep": {"class": Sleep, "name": "Sleep"}, + "VRAM_Debug": {"class": VRAM_Debug, "name": "VRAM Debug"}, + "SomethingToString": {"class": SomethingToString, "name": "Something To String"}, + "EmptyLatentImagePresets": {"class": EmptyLatentImagePresets, "name": "Empty Latent Image Presets"}, #audioscheduler stuff - "NormalizedAmplitudeToMask": NormalizedAmplitudeToMask, - "OffsetMaskByNormalizedAmplitude": OffsetMaskByNormalizedAmplitude, - "ImageTransformByNormalizedAmplitude": ImageTransformByNormalizedAmplitude, + "NormalizedAmplitudeToMask": {"class": NormalizedAmplitudeToMask}, + "OffsetMaskByNormalizedAmplitude": {"class": OffsetMaskByNormalizedAmplitude}, + "ImageTransformByNormalizedAmplitude": {"class": ImageTransformByNormalizedAmplitude}, #curve nodes - "SplineEditor": SplineEditor, - "CreateShapeMaskOnPath": CreateShapeMaskOnPath, - "WeightScheduleExtend": WeightScheduleExtend, - "MaskOrImageToWeight": MaskOrImageToWeight, - "WeightScheduleConvert": WeightScheduleConvert, - "FloatToMask": FloatToMask, - "FloatToSigmas": FloatToSigmas, + "SplineEditor": {"class": SplineEditor, "name": "Spline Editor"}, + "CreateShapeMaskOnPath": {"class": CreateShapeMaskOnPath, "name": "Create Shape Mask On Path"}, + "WeightScheduleExtend": {"class": WeightScheduleExtend, "name": "Weight Schedule Extend"}, + "MaskOrImageToWeight": {"class": MaskOrImageToWeight, "name": "Mask Or Image To Weight"}, + "WeightScheduleConvert": {"class": WeightScheduleConvert, "name": "Weight Schedule Convert"}, + "FloatToMask": {"class": FloatToMask, "name": "Float To Mask"}, + "FloatToSigmas": {"class": FloatToSigmas, "name": "Float To Sigmas"}, #experimental - "StabilityAPI_SD3": StabilityAPI_SD3, - "SoundReactive": SoundReactive, - "StableZero123_BatchSchedule": StableZero123_BatchSchedule, - "SV3D_BatchSchedule": SV3D_BatchSchedule, - "LoadResAdapterNormalization": LoadResAdapterNormalization, - "Superprompt": Superprompt, - "GLIGENTextBoxApplyBatchCoords": GLIGENTextBoxApplyBatchCoords, - "Intrinsic_lora_sampling": Intrinsic_lora_sampling, + "StabilityAPI_SD3": {"class": StabilityAPI_SD3, "name": "Stability API SD3"}, + "SoundReactive": {"class": SoundReactive, "name": "Sound Reactive"}, + "StableZero123_BatchSchedule": {"class": StableZero123_BatchSchedule, "name": "Stable Zero123 Batch Schedule"}, + "SV3D_BatchSchedule": {"class": SV3D_BatchSchedule, "name": "SV3D Batch Schedule"}, + "LoadResAdapterNormalization": {"class": LoadResAdapterNormalization}, + "Superprompt": {"class": Superprompt, "name": "Superprompt"}, + "GLIGENTextBoxApplyBatchCoords": {"class": GLIGENTextBoxApplyBatchCoords}, + "Intrinsic_lora_sampling": {"class": Intrinsic_lora_sampling, "name": "Intrinsic Lora Sampling"}, +} + +def generate_node_mappings(node_config): + node_class_mappings = {} + node_display_name_mappings = {} + + for node_name, node_info in node_config.items(): + node_class_mappings[node_name] = node_info["class"] + node_display_name_mappings[node_name] = node_info.get("name", node_info["class"].__name__) + + return node_class_mappings, node_display_name_mappings + +NODE_CLASS_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS = generate_node_mappings(NODE_CONFIG) -} -NODE_DISPLAY_NAME_MAPPINGS = { - "INTConstant": "INT Constant", - "FloatConstant": "Float Constant", - "ImageBatchMulti": "Image Batch Multi", - "MaskBatchMulti": "Mask Batch Multi", - "ConditioningMultiCombine": "Conditioning Multi Combine", - "ConditioningSetMaskAndCombine": "ConditioningSetMaskAndCombine", - "ConditioningSetMaskAndCombine3": "ConditioningSetMaskAndCombine3", - "ConditioningSetMaskAndCombine4": "ConditioningSetMaskAndCombine4", - "ConditioningSetMaskAndCombine5": "ConditioningSetMaskAndCombine5", - "GrowMaskWithBlur": "GrowMaskWithBlur", - "ColorToMask": "ColorToMask", - "CreateGradientMask": "CreateGradientMask", - "CreateTextMask" : "CreateTextMask", - "CreateFadeMask" : "CreateFadeMask (Deprecated)", - "CreateFadeMaskAdvanced" : "CreateFadeMaskAdvanced", - "CreateFluidMask" : "CreateFluidMask", - "CreateAudioMask" : "CreateAudioMask (Deprecated)", - "VRAM_Debug" : "VRAM Debug", - "CrossFadeImages": "CrossFadeImages", - "SomethingToString": "SomethingToString", - "EmptyLatentImagePresets": "EmptyLatentImagePresets", - "ColorMatch": "ColorMatch", - "GetImageRangeFromBatch": "GetImageRangeFromBatch", - "InsertImagesToBatchIndexed": "InsertImagesToBatchIndexed", - "SaveImageWithAlpha": "SaveImageWithAlpha", - "ReverseImageBatch": "ReverseImageBatch", - "ImageGridComposite2x2": "ImageGridComposite2x2", - "ImageGridComposite3x3": "ImageGridComposite3x3", - "ImageConcanate": "ImageConcatenate", - "ImageBatchTestPattern": "ImageBatchTestPattern", - "ReplaceImagesInBatch": "ReplaceImagesInBatch", - "BatchCropFromMask": "BatchCropFromMask", - "BatchCropFromMaskAdvanced": "BatchCropFromMaskAdvanced", - "FilterZeroMasksAndCorrespondingImages": "FilterZeroMasksAndCorrespondingImages", - "InsertImageBatchByIndexes": "InsertImageBatchByIndexes", - "BatchUncrop": "BatchUncrop", - "BatchUncropAdvanced": "BatchUncropAdvanced", - "BatchCLIPSeg": "BatchCLIPSeg", - "RoundMask": "RoundMask", - "ResizeMask": "ResizeMask", - "OffsetMask": "OffsetMask", - "WidgetToString": "WidgetToString", - "CreateShapeMask": "CreateShapeMask", - "CreateVoronoiMask": "CreateVoronoiMask", - "CreateMagicMask": "CreateMagicMask", - "BboxToInt": "BboxToInt", - "SplitBboxes": "SplitBboxes", - "ImageGrabPIL": "ImageGrabPIL", - "DummyLatentOut": "DummyLatentOut", - "FlipSigmasAdjusted": "FlipSigmasAdjusted", - "InjectNoiseToLatent": "InjectNoiseToLatent", - "AddLabel": "AddLabel", - "SoundReactive": "SoundReactive", - "GenerateNoise": "GenerateNoise", - "StableZero123_BatchSchedule": "StableZero123_BatchSchedule", - "SV3D_BatchSchedule": "SV3D_BatchSchedule", - "GetImagesFromBatchIndexed": "GetImagesFromBatchIndexed", - "ImageBatchRepeatInterleaving": "ImageBatchRepeatInterleaving", - "NormalizedAmplitudeToMask": "NormalizedAmplitudeToMask", - "OffsetMaskByNormalizedAmplitude": "OffsetMaskByNormalizedAmplitude", - "ImageTransformByNormalizedAmplitude": "ImageTransformByNormalizedAmplitude", - "GetLatentsFromBatchIndexed": "GetLatentsFromBatchIndexed", - "StringConstant": "StringConstant", - "CondPassThrough": "CondPassThrough", - "ImageUpscaleWithModelBatched": "ImageUpscaleWithModelBatched", - "ScaleBatchPromptSchedule": "ScaleBatchPromptSchedule", - "ImageNormalize_Neg1_To_1": "ImageNormalize_Neg1_To_1", - "Intrinsic_lora_sampling": "Intrinsic_lora_sampling", - "RemapMaskRange": "RemapMaskRange", - "LoadResAdapterNormalization": "LoadResAdapterNormalization", - "Superprompt": "Superprompt", - "RemapImageRange": "RemapImageRange", - "CameraPoseVisualizer": "CameraPoseVisualizer", - "BboxVisualize": "BboxVisualize", - "StringConstantMultiline": "StringConstantMultiline", - "JoinStrings": "JoinStrings", - "Sleep": "🛌 Sleep 🛌", - "ImagePadForOutpaintMasked": "Pad Image For Outpaint Masked", - "ImageAndMaskPreview": "Image & Mask Preview", - "StabilityAPI_SD3": "Stability API SD3", - "MaskOrImageToWeight": "Mask Or Image To Weight", - "WeightScheduleConvert": "Weight Schedule Convert", - "FloatToMask": "Float To Mask", - "FloatToSigmas": "Float To Sigmas", - "CustomSigmas": "Custom Sigmas", - "ImagePass": "ImagePass", - "SplitImageChannels": "Split Image Channels", - "MergeImageChannels": "Merge Image Channels", - #curve nodes - "SplineEditor": "Spline Editor", - "CreateShapeMaskOnPath": "Create Shape Mask On Path", - "WeightScheduleExtend": "Weight Schedule Extend" -} __all__ = ["NODE_CLASS_MAPPINGS", "NODE_DISPLAY_NAME_MAPPINGS", "WEB_DIRECTORY"] WEB_DIRECTORY = "./web" diff --git a/nodes/mask_nodes.py b/nodes/mask_nodes.py new file mode 100644 index 0000000..9ea1f65 --- /dev/null +++ b/nodes/mask_nodes.py @@ -0,0 +1,1165 @@ +import torch +import torch.nn.functional as F +from torchvision.transforms import functional as TF +from PIL import Image, ImageDraw, ImageFilter, ImageFont +import scipy.ndimage +import numpy as np + +import matplotlib.pyplot as plt +from contextlib import nullcontext +import os + +import model_management +from comfy.utils import ProgressBar +from nodes import MAX_RESOLUTION + +import folder_paths + +from ..utility.utility import tensor2pil, pil2tensor + +script_directory = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + +class BatchCLIPSeg: + + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(s): + + return {"required": + { + "images": ("IMAGE",), + "text": ("STRING", {"multiline": False}), + "threshold": ("FLOAT", {"default": 0.1,"min": 0.0, "max": 10.0, "step": 0.001}), + "binary_mask": ("BOOLEAN", {"default": True}), + "combine_mask": ("BOOLEAN", {"default": False}), + "use_cuda": ("BOOLEAN", {"default": True}), + }, + } + + CATEGORY = "KJNodes/masking" + RETURN_TYPES = ("MASK",) + RETURN_NAMES = ("Mask",) + FUNCTION = "segment_image" + DESCRIPTION = """ +Segments an image or batch of images using CLIPSeg. +""" + + def segment_image(self, images, text, threshold, binary_mask, combine_mask, use_cuda): + from transformers import CLIPSegProcessor, CLIPSegForImageSegmentation + out = [] + height, width, _ = images[0].shape + if use_cuda and torch.cuda.is_available(): + device = torch.device("cuda") + else: + device = torch.device("cpu") + dtype = model_management.unet_dtype() + model = CLIPSegForImageSegmentation.from_pretrained("CIDAS/clipseg-rd64-refined") + model.to(dtype) + model.to(device) + images = images.to(device) + processor = CLIPSegProcessor.from_pretrained("CIDAS/clipseg-rd64-refined") + pbar = ProgressBar(images.shape[0]) + autocast_condition = (dtype != torch.float32) and not model_management.is_device_mps(device) + with torch.autocast(model_management.get_autocast_device(device), dtype=dtype) if autocast_condition else nullcontext(): + for image in images: + image = (image* 255).type(torch.uint8) + prompt = text + input_prc = processor(text=prompt, images=image, return_tensors="pt") + # Move the processed input to the device + for key in input_prc: + input_prc[key] = input_prc[key].to(device) + + outputs = model(**input_prc) + + tensor = torch.sigmoid(outputs[0]) + tensor_thresholded = torch.where(tensor > threshold, tensor, torch.tensor(0, dtype=torch.float)) + tensor_normalized = (tensor_thresholded - tensor_thresholded.min()) / (tensor_thresholded.max() - tensor_thresholded.min()) + tensor = tensor_normalized + + # Resize the mask + if len(tensor.shape) == 3: + tensor = tensor.unsqueeze(0) + resized_tensor = F.interpolate(tensor, size=(height, width), mode='nearest') + + # Remove the extra dimensions + resized_tensor = resized_tensor[0, 0, :, :] + pbar.update(1) + out.append(resized_tensor) + + results = torch.stack(out).cpu().float() + + if combine_mask: + combined_results = torch.max(results, dim=0)[0] + results = combined_results.unsqueeze(0).repeat(len(images),1,1) + + if binary_mask: + results = results.round() + + return results, + +class CreateTextMask: + + RETURN_TYPES = ("IMAGE", "MASK",) + FUNCTION = "createtextmask" + CATEGORY = "KJNodes/text" + DESCRIPTION = """ +Creates a text image and mask. +Looks for fonts from this folder: +ComfyUI/custom_nodes/ComfyUI-KJNodes/fonts + +If start_rotation and/or end_rotation are different values, +creates animation between them. +""" + + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "invert": ("BOOLEAN", {"default": False}), + "frames": ("INT", {"default": 1,"min": 1, "max": 4096, "step": 1}), + "text_x": ("INT", {"default": 0,"min": 0, "max": 4096, "step": 1}), + "text_y": ("INT", {"default": 0,"min": 0, "max": 4096, "step": 1}), + "font_size": ("INT", {"default": 32,"min": 8, "max": 4096, "step": 1}), + "font_color": ("STRING", {"default": "white"}), + "text": ("STRING", {"default": "HELLO!", "multiline": True}), + "font": (folder_paths.get_filename_list("kjnodes_fonts"), ), + "width": ("INT", {"default": 512,"min": 16, "max": 4096, "step": 1}), + "height": ("INT", {"default": 512,"min": 16, "max": 4096, "step": 1}), + "start_rotation": ("INT", {"default": 0,"min": 0, "max": 359, "step": 1}), + "end_rotation": ("INT", {"default": 0,"min": -359, "max": 359, "step": 1}), + }, + } + + def createtextmask(self, frames, width, height, invert, text_x, text_y, text, font_size, font_color, font, start_rotation, end_rotation): + # Define the number of images in the batch + batch_size = frames + out = [] + masks = [] + rotation = start_rotation + if start_rotation != end_rotation: + rotation_increment = (end_rotation - start_rotation) / (batch_size - 1) + + font_path = folder_paths.get_full_path("kjnodes_fonts", font) + # Generate the text + for i in range(batch_size): + image = Image.new("RGB", (width, height), "black") + draw = ImageDraw.Draw(image) + font = ImageFont.truetype(font_path, font_size) + + # Split the text into words + words = text.split() + + # Initialize variables for line creation + lines = [] + current_line = [] + current_line_width = 0 + try: #new pillow + # Iterate through words to create lines + for word in words: + word_width = font.getbbox(word)[2] + if current_line_width + word_width <= width - 2 * text_x: + current_line.append(word) + current_line_width += word_width + font.getbbox(" ")[2] # Add space width + else: + lines.append(" ".join(current_line)) + current_line = [word] + current_line_width = word_width + except: #old pillow + for word in words: + word_width = font.getsize(word)[0] + if current_line_width + word_width <= width - 2 * text_x: + current_line.append(word) + current_line_width += word_width + font.getsize(" ")[0] # Add space width + else: + lines.append(" ".join(current_line)) + current_line = [word] + current_line_width = word_width + + # Add the last line if it's not empty + if current_line: + lines.append(" ".join(current_line)) + + # Draw each line of text separately + y_offset = text_y + for line in lines: + text_width = font.getlength(line) + text_height = font_size + text_center_x = text_x + text_width / 2 + text_center_y = y_offset + text_height / 2 + try: + draw.text((text_x, y_offset), line, font=font, fill=font_color, features=['-liga']) + except: + draw.text((text_x, y_offset), line, font=font, fill=font_color) + y_offset += text_height # Move to the next line + + if start_rotation != end_rotation: + image = image.rotate(rotation, center=(text_center_x, text_center_y)) + rotation += rotation_increment + + image = np.array(image).astype(np.float32) / 255.0 + image = torch.from_numpy(image)[None,] + mask = image[:, :, :, 0] + masks.append(mask) + out.append(image) + + if invert: + return (1.0 - torch.cat(out, dim=0), 1.0 - torch.cat(masks, dim=0),) + return (torch.cat(out, dim=0),torch.cat(masks, dim=0),) + +class ColorToMask: + + RETURN_TYPES = ("MASK",) + FUNCTION = "clip" + CATEGORY = "KJNodes/masking" + DESCRIPTION = """ +Converts chosen RGB value to a mask. +With batch inputs, the **per_batch** +controls the number of images processed at once. +""" + + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "images": ("IMAGE",), + "invert": ("BOOLEAN", {"default": False}), + "red": ("INT", {"default": 0,"min": 0, "max": 255, "step": 1}), + "green": ("INT", {"default": 0,"min": 0, "max": 255, "step": 1}), + "blue": ("INT", {"default": 0,"min": 0, "max": 255, "step": 1}), + "threshold": ("INT", {"default": 10,"min": 0, "max": 255, "step": 1}), + "per_batch": ("INT", {"default": 16, "min": 1, "max": 4096, "step": 1}), + }, + } + + def clip(self, images, red, green, blue, threshold, invert, per_batch): + + color = torch.tensor([red, green, blue], dtype=torch.uint8) + black = torch.tensor([0, 0, 0], dtype=torch.uint8) + white = torch.tensor([255, 255, 255], dtype=torch.uint8) + + if invert: + black, white = white, black + + steps = images.shape[0] + pbar = ProgressBar(steps) + tensors_out = [] + + for start_idx in range(0, images.shape[0], per_batch): + + # Calculate color distances + color_distances = torch.norm(images[start_idx:start_idx+per_batch] * 255 - color, dim=-1) + + # Create a mask based on the threshold + mask = color_distances <= threshold + + # Apply the mask to create new images + mask_out = torch.where(mask.unsqueeze(-1), white, black).float() + mask_out = mask_out.mean(dim=-1) + + tensors_out.append(mask_out.cpu()) + batch_count = mask_out.shape[0] + pbar.update(batch_count) + + tensors_out = torch.cat(tensors_out, dim=0) + tensors_out = torch.clamp(tensors_out, min=0.0, max=1.0) + return tensors_out, + +class CreateFluidMask: + + RETURN_TYPES = ("IMAGE", "MASK") + FUNCTION = "createfluidmask" + CATEGORY = "KJNodes/masking/generate" + + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "invert": ("BOOLEAN", {"default": False}), + "frames": ("INT", {"default": 0,"min": 0, "max": 255, "step": 1}), + "width": ("INT", {"default": 256,"min": 16, "max": 4096, "step": 1}), + "height": ("INT", {"default": 256,"min": 16, "max": 4096, "step": 1}), + "inflow_count": ("INT", {"default": 3,"min": 0, "max": 255, "step": 1}), + "inflow_velocity": ("INT", {"default": 1,"min": 0, "max": 255, "step": 1}), + "inflow_radius": ("INT", {"default": 8,"min": 0, "max": 255, "step": 1}), + "inflow_padding": ("INT", {"default": 50,"min": 0, "max": 255, "step": 1}), + "inflow_duration": ("INT", {"default": 60,"min": 0, "max": 255, "step": 1}), + }, + } + #using code from https://github.com/GregTJ/stable-fluids + def createfluidmask(self, frames, width, height, invert, inflow_count, inflow_velocity, inflow_radius, inflow_padding, inflow_duration): + from ..utility.fluid import Fluid + from scipy.spatial import erf + out = [] + masks = [] + RESOLUTION = width, height + DURATION = frames + + INFLOW_PADDING = inflow_padding + INFLOW_DURATION = inflow_duration + INFLOW_RADIUS = inflow_radius + INFLOW_VELOCITY = inflow_velocity + INFLOW_COUNT = inflow_count + + print('Generating fluid solver, this may take some time.') + fluid = Fluid(RESOLUTION, 'dye') + + center = np.floor_divide(RESOLUTION, 2) + r = np.min(center) - INFLOW_PADDING + + points = np.linspace(-np.pi, np.pi, INFLOW_COUNT, endpoint=False) + points = tuple(np.array((np.cos(p), np.sin(p))) for p in points) + normals = tuple(-p for p in points) + points = tuple(r * p + center for p in points) + + inflow_velocity = np.zeros_like(fluid.velocity) + inflow_dye = np.zeros(fluid.shape) + for p, n in zip(points, normals): + mask = np.linalg.norm(fluid.indices - p[:, None, None], axis=0) <= INFLOW_RADIUS + inflow_velocity[:, mask] += n[:, None] * INFLOW_VELOCITY + inflow_dye[mask] = 1 + + + for f in range(DURATION): + print(f'Computing frame {f + 1} of {DURATION}.') + if f <= INFLOW_DURATION: + fluid.velocity += inflow_velocity + fluid.dye += inflow_dye + + curl = fluid.step()[1] + # Using the error function to make the contrast a bit higher. + # Any other sigmoid function e.g. smoothstep would work. + curl = (erf(curl * 2) + 1) / 4 + + color = np.dstack((curl, np.ones(fluid.shape), fluid.dye)) + color = (np.clip(color, 0, 1) * 255).astype('uint8') + image = np.array(color).astype(np.float32) / 255.0 + image = torch.from_numpy(image)[None,] + mask = image[:, :, :, 0] + masks.append(mask) + out.append(image) + + if invert: + return (1.0 - torch.cat(out, dim=0),1.0 - torch.cat(masks, dim=0),) + return (torch.cat(out, dim=0),torch.cat(masks, dim=0),) + +class CreateAudioMask: + + RETURN_TYPES = ("IMAGE",) + FUNCTION = "createaudiomask" + CATEGORY = "KJNodes/deprecated" + + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "invert": ("BOOLEAN", {"default": False}), + "frames": ("INT", {"default": 16,"min": 1, "max": 255, "step": 1}), + "scale": ("FLOAT", {"default": 0.5,"min": 0.0, "max": 2.0, "step": 0.01}), + "audio_path": ("STRING", {"default": "audio.wav"}), + "width": ("INT", {"default": 256,"min": 16, "max": 4096, "step": 1}), + "height": ("INT", {"default": 256,"min": 16, "max": 4096, "step": 1}), + }, + } + + def createaudiomask(self, frames, width, height, invert, audio_path, scale): + try: + import librosa + except ImportError: + raise Exception("Can not import librosa. Install it with 'pip install librosa'") + batch_size = frames + out = [] + masks = [] + if audio_path == "audio.wav": #I don't know why relative path won't work otherwise... + audio_path = os.path.join(script_directory, audio_path) + audio, sr = librosa.load(audio_path) + spectrogram = np.abs(librosa.stft(audio)) + + for i in range(batch_size): + image = Image.new("RGB", (width, height), "black") + draw = ImageDraw.Draw(image) + frame = spectrogram[:, i] + circle_radius = int(height * np.mean(frame)) + circle_radius *= scale + circle_center = (width // 2, height // 2) # Calculate the center of the image + + draw.ellipse([(circle_center[0] - circle_radius, circle_center[1] - circle_radius), + (circle_center[0] + circle_radius, circle_center[1] + circle_radius)], + fill='white') + + image = np.array(image).astype(np.float32) / 255.0 + image = torch.from_numpy(image)[None,] + mask = image[:, :, :, 0] + masks.append(mask) + out.append(image) + + if invert: + return (1.0 - torch.cat(out, dim=0),) + return (torch.cat(out, dim=0),torch.cat(masks, dim=0),) + +class CreateGradientMask: + + RETURN_TYPES = ("MASK",) + FUNCTION = "createmask" + CATEGORY = "KJNodes/masking/generate" + + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "invert": ("BOOLEAN", {"default": False}), + "frames": ("INT", {"default": 0,"min": 0, "max": 255, "step": 1}), + "width": ("INT", {"default": 256,"min": 16, "max": 4096, "step": 1}), + "height": ("INT", {"default": 256,"min": 16, "max": 4096, "step": 1}), + }, + } + def createmask(self, frames, width, height, invert): + # Define the number of images in the batch + batch_size = frames + out = [] + # Create an empty array to store the image batch + image_batch = np.zeros((batch_size, height, width), dtype=np.float32) + # Generate the black to white gradient for each image + for i in range(batch_size): + gradient = np.linspace(1.0, 0.0, width, dtype=np.float32) + time = i / frames # Calculate the time variable + offset_gradient = gradient - time # Offset the gradient values based on time + image_batch[i] = offset_gradient.reshape(1, -1) + output = torch.from_numpy(image_batch) + mask = output + out.append(mask) + if invert: + return (1.0 - torch.cat(out, dim=0),) + return (torch.cat(out, dim=0),) + +class CreateFadeMask: + + RETURN_TYPES = ("MASK",) + FUNCTION = "createfademask" + CATEGORY = "KJNodes/deprecated" + + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "invert": ("BOOLEAN", {"default": False}), + "frames": ("INT", {"default": 2,"min": 2, "max": 255, "step": 1}), + "width": ("INT", {"default": 256,"min": 16, "max": 4096, "step": 1}), + "height": ("INT", {"default": 256,"min": 16, "max": 4096, "step": 1}), + "interpolation": (["linear", "ease_in", "ease_out", "ease_in_out"],), + "start_level": ("FLOAT", {"default": 1.0,"min": 0.0, "max": 1.0, "step": 0.01}), + "midpoint_level": ("FLOAT", {"default": 0.5,"min": 0.0, "max": 1.0, "step": 0.01}), + "end_level": ("FLOAT", {"default": 0.0,"min": 0.0, "max": 1.0, "step": 0.01}), + "midpoint_frame": ("INT", {"default": 0,"min": 0, "max": 4096, "step": 1}), + }, + } + + def createfademask(self, frames, width, height, invert, interpolation, start_level, midpoint_level, end_level, midpoint_frame): + def ease_in(t): + return t * t + + def ease_out(t): + return 1 - (1 - t) * (1 - t) + + def ease_in_out(t): + return 3 * t * t - 2 * t * t * t + + batch_size = frames + out = [] + image_batch = np.zeros((batch_size, height, width), dtype=np.float32) + + if midpoint_frame == 0: + midpoint_frame = batch_size // 2 + + for i in range(batch_size): + if i <= midpoint_frame: + t = i / midpoint_frame + if interpolation == "ease_in": + t = ease_in(t) + elif interpolation == "ease_out": + t = ease_out(t) + elif interpolation == "ease_in_out": + t = ease_in_out(t) + color = start_level - t * (start_level - midpoint_level) + else: + t = (i - midpoint_frame) / (batch_size - midpoint_frame) + if interpolation == "ease_in": + t = ease_in(t) + elif interpolation == "ease_out": + t = ease_out(t) + elif interpolation == "ease_in_out": + t = ease_in_out(t) + color = midpoint_level - t * (midpoint_level - end_level) + + color = np.clip(color, 0, 255) + image = np.full((height, width), color, dtype=np.float32) + image_batch[i] = image + + output = torch.from_numpy(image_batch) + mask = output + out.append(mask) + + if invert: + return (1.0 - torch.cat(out, dim=0),) + return (torch.cat(out, dim=0),) + +class CreateFadeMaskAdvanced: + + RETURN_TYPES = ("MASK",) + FUNCTION = "createfademask" + CATEGORY = "KJNodes/masking/generate" + DESCRIPTION = """ +Create a batch of masks interpolated between given frames and values. +Uses same syntax as Fizz' BatchValueSchedule. +First value is the frame index (not that this starts from 0, not 1) +and the second value inside the brackets is the float value of the mask in range 0.0 - 1.0 + +For example the default values: +0:(0.0) +7:(1.0) +15:(0.0) + +Would create a mask batch fo 16 frames, starting from black, +interpolating with the chosen curve to fully white at the 8th frame, +and interpolating from that to fully black at the 16th frame. +""" + + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "points_string": ("STRING", {"default": "0:(0.0),\n7:(1.0),\n15:(0.0)\n", "multiline": True}), + "invert": ("BOOLEAN", {"default": False}), + "frames": ("INT", {"default": 16,"min": 2, "max": 255, "step": 1}), + "width": ("INT", {"default": 512,"min": 1, "max": 4096, "step": 1}), + "height": ("INT", {"default": 512,"min": 1, "max": 4096, "step": 1}), + "interpolation": (["linear", "ease_in", "ease_out", "ease_in_out"],), + }, + } + + def createfademask(self, frames, width, height, invert, points_string, interpolation): + def ease_in(t): + return t * t + + def ease_out(t): + return 1 - (1 - t) * (1 - t) + + def ease_in_out(t): + return 3 * t * t - 2 * t * t * t + + # Parse the input string into a list of tuples + points = [] + points_string = points_string.rstrip(',\n') + for point_str in points_string.split(','): + frame_str, color_str = point_str.split(':') + frame = int(frame_str.strip()) + color = float(color_str.strip()[1:-1]) # Remove parentheses around color + points.append((frame, color)) + + # Check if the last frame is already in the points + if len(points) == 0 or points[-1][0] != frames - 1: + # If not, add it with the color of the last specified frame + points.append((frames - 1, points[-1][1] if points else 0)) + + # Sort the points by frame number + points.sort(key=lambda x: x[0]) + + batch_size = frames + out = [] + image_batch = np.zeros((batch_size, height, width), dtype=np.float32) + + # Index of the next point to interpolate towards + next_point = 1 + + for i in range(batch_size): + while next_point < len(points) and i > points[next_point][0]: + next_point += 1 + + # Interpolate between the previous point and the next point + prev_point = next_point - 1 + t = (i - points[prev_point][0]) / (points[next_point][0] - points[prev_point][0]) + if interpolation == "ease_in": + t = ease_in(t) + elif interpolation == "ease_out": + t = ease_out(t) + elif interpolation == "ease_in_out": + t = ease_in_out(t) + elif interpolation == "linear": + pass # No need to modify `t` for linear interpolation + + color = points[prev_point][1] - t * (points[prev_point][1] - points[next_point][1]) + color = np.clip(color, 0, 255) + image = np.full((height, width), color, dtype=np.float32) + image_batch[i] = image + + output = torch.from_numpy(image_batch) + mask = output + out.append(mask) + + if invert: + return (1.0 - torch.cat(out, dim=0),) + return (torch.cat(out, dim=0),) + +class CreateMagicMask: + + RETURN_TYPES = ("MASK", "MASK",) + RETURN_NAMES = ("mask", "mask_inverted",) + FUNCTION = "createmagicmask" + CATEGORY = "KJNodes/masking/generate" + + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "frames": ("INT", {"default": 16,"min": 2, "max": 4096, "step": 1}), + "depth": ("INT", {"default": 12,"min": 1, "max": 500, "step": 1}), + "distortion": ("FLOAT", {"default": 1.5,"min": 0.0, "max": 100.0, "step": 0.01}), + "seed": ("INT", {"default": 123,"min": 0, "max": 99999999, "step": 1}), + "transitions": ("INT", {"default": 1,"min": 1, "max": 20, "step": 1}), + "frame_width": ("INT", {"default": 512,"min": 16, "max": 4096, "step": 1}), + "frame_height": ("INT", {"default": 512,"min": 16, "max": 4096, "step": 1}), + }, + } + + def createmagicmask(self, frames, transitions, depth, distortion, seed, frame_width, frame_height): + from ..utility.magictex import coordinate_grid, random_transform, magic + rng = np.random.default_rng(seed) + out = [] + coords = coordinate_grid((frame_width, frame_height)) + + # Calculate the number of frames for each transition + frames_per_transition = frames // transitions + + # Generate a base set of parameters + base_params = { + "coords": random_transform(coords, rng), + "depth": depth, + "distortion": distortion, + } + for t in range(transitions): + # Generate a second set of parameters that is at most max_diff away from the base parameters + params1 = base_params.copy() + params2 = base_params.copy() + + params1['coords'] = random_transform(coords, rng) + params2['coords'] = random_transform(coords, rng) + + for i in range(frames_per_transition): + # Compute the interpolation factor + alpha = i / frames_per_transition + + # Interpolate between the two sets of parameters + params = params1.copy() + params['coords'] = (1 - alpha) * params1['coords'] + alpha * params2['coords'] + + tex = magic(**params) + + dpi = frame_width / 10 + fig = plt.figure(figsize=(10, 10), dpi=dpi) + + ax = fig.add_subplot(111) + plt.subplots_adjust(left=0, right=1, bottom=0, top=1) + + ax.get_yaxis().set_ticks([]) + ax.get_xaxis().set_ticks([]) + ax.imshow(tex, aspect='auto') + + fig.canvas.draw() + img = np.array(fig.canvas.renderer._renderer) + + plt.close(fig) + + pil_img = Image.fromarray(img).convert("L") + mask = torch.tensor(np.array(pil_img)) / 255.0 + + out.append(mask) + + return (torch.stack(out, dim=0), 1.0 - torch.stack(out, dim=0),) + +class CreateShapeMask: + + RETURN_TYPES = ("MASK", "MASK",) + RETURN_NAMES = ("mask", "mask_inverted",) + FUNCTION = "createshapemask" + CATEGORY = "KJNodes/masking/generate" + DESCRIPTION = """ +Creates a mask or batch of masks with the specified shape. +Locations are center locations. +Grow value is the amount to grow the shape on each frame, creating animated masks. +""" + + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "shape": ( + [ 'circle', + 'square', + 'triangle', + ], + { + "default": 'circle' + }), + "frames": ("INT", {"default": 1,"min": 1, "max": 4096, "step": 1}), + "location_x": ("INT", {"default": 256,"min": 0, "max": 4096, "step": 1}), + "location_y": ("INT", {"default": 256,"min": 0, "max": 4096, "step": 1}), + "grow": ("INT", {"default": 0, "min": -512, "max": 512, "step": 1}), + "frame_width": ("INT", {"default": 512,"min": 16, "max": 4096, "step": 1}), + "frame_height": ("INT", {"default": 512,"min": 16, "max": 4096, "step": 1}), + "shape_width": ("INT", {"default": 128,"min": 8, "max": 4096, "step": 1}), + "shape_height": ("INT", {"default": 128,"min": 8, "max": 4096, "step": 1}), + }, + } + + def createshapemask(self, frames, frame_width, frame_height, location_x, location_y, shape_width, shape_height, grow, shape): + # Define the number of images in the batch + batch_size = frames + out = [] + color = "white" + for i in range(batch_size): + image = Image.new("RGB", (frame_width, frame_height), "black") + draw = ImageDraw.Draw(image) + + # Calculate the size for this frame and ensure it's not less than 0 + current_width = max(0, shape_width + i*grow) + current_height = max(0, shape_height + i*grow) + + if shape == 'circle' or shape == 'square': + # Define the bounding box for the shape + left_up_point = (location_x - current_width // 2, location_y - current_height // 2) + right_down_point = (location_x + current_width // 2, location_y + current_height // 2) + two_points = [left_up_point, right_down_point] + + if shape == 'circle': + draw.ellipse(two_points, fill=color) + elif shape == 'square': + draw.rectangle(two_points, fill=color) + + elif shape == 'triangle': + # Define the points for the triangle + left_up_point = (location_x - current_width // 2, location_y + current_height // 2) # bottom left + right_down_point = (location_x + current_width // 2, location_y + current_height // 2) # bottom right + top_point = (location_x, location_y - current_height // 2) # top point + draw.polygon([top_point, left_up_point, right_down_point], fill=color) + + image = pil2tensor(image) + mask = image[:, :, :, 0] + out.append(mask) + outstack = torch.cat(out, dim=0) + return (outstack, 1.0 - outstack,) + +class CreateVoronoiMask: + + RETURN_TYPES = ("MASK", "MASK",) + RETURN_NAMES = ("mask", "mask_inverted",) + FUNCTION = "createvoronoi" + CATEGORY = "KJNodes/masking/generate" + + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "frames": ("INT", {"default": 16,"min": 2, "max": 4096, "step": 1}), + "num_points": ("INT", {"default": 15,"min": 1, "max": 4096, "step": 1}), + "line_width": ("INT", {"default": 4,"min": 1, "max": 4096, "step": 1}), + "speed": ("FLOAT", {"default": 0.5,"min": 0.0, "max": 1.0, "step": 0.01}), + "frame_width": ("INT", {"default": 512,"min": 16, "max": 4096, "step": 1}), + "frame_height": ("INT", {"default": 512,"min": 16, "max": 4096, "step": 1}), + }, + } + + def createvoronoi(self, frames, num_points, line_width, speed, frame_width, frame_height): + from scipy.spatial import Voronoi + # Define the number of images in the batch + batch_size = frames + out = [] + + # Calculate aspect ratio + aspect_ratio = frame_width / frame_height + + # Create start and end points for each point, considering the aspect ratio + start_points = np.random.rand(num_points, 2) + start_points[:, 0] *= aspect_ratio + + end_points = np.random.rand(num_points, 2) + end_points[:, 0] *= aspect_ratio + + for i in range(batch_size): + # Interpolate the points' positions based on the current frame + t = (i * speed) / (batch_size - 1) # normalize to [0, 1] over the frames + t = np.clip(t, 0, 1) # ensure t is in [0, 1] + points = (1 - t) * start_points + t * end_points # lerp + + # Adjust points for aspect ratio + points[:, 0] *= aspect_ratio + + vor = Voronoi(points) + + # Create a blank image with a white background + fig, ax = plt.subplots() + plt.subplots_adjust(left=0, right=1, bottom=0, top=1) + ax.set_xlim([0, aspect_ratio]); ax.set_ylim([0, 1]) # adjust x limits + ax.axis('off') + ax.margins(0, 0) + fig.set_size_inches(aspect_ratio * frame_height/100, frame_height/100) # adjust figure size + ax.fill_between([0, 1], [0, 1], color='white') + + # Plot each Voronoi ridge + for simplex in vor.ridge_vertices: + simplex = np.asarray(simplex) + if np.all(simplex >= 0): + plt.plot(vor.vertices[simplex, 0], vor.vertices[simplex, 1], 'k-', linewidth=line_width) + + fig.canvas.draw() + img = np.array(fig.canvas.renderer._renderer) + + plt.close(fig) + + pil_img = Image.fromarray(img).convert("L") + mask = torch.tensor(np.array(pil_img)) / 255.0 + + out.append(mask) + + return (torch.stack(out, dim=0), 1.0 - torch.stack(out, dim=0),) + +class GetMaskSize: + @classmethod + def INPUT_TYPES(s): + return {"required": { + "mask": ("MASK",), + }} + + RETURN_TYPES = ("MASK","INT", "INT", ) + RETURN_NAMES = ("mask", "width", "height",) + FUNCTION = "getsize" + CATEGORY = "KJNodes/masking" + DESCRIPTION = """ +Returns the width and height of the mask, +and passes through the mask unchanged. + +""" + + def getsize(self, mask): + width = mask.shape[2] + height = mask.shape[1] + return {"ui": { + "text": [f"{width}x{height}"]}, + "result": (mask, width, height) + } + +class GrowMaskWithBlur: + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "mask": ("MASK",), + "expand": ("INT", {"default": 0, "min": -MAX_RESOLUTION, "max": MAX_RESOLUTION, "step": 1}), + "incremental_expandrate": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 100.0, "step": 0.1}), + "tapered_corners": ("BOOLEAN", {"default": True}), + "flip_input": ("BOOLEAN", {"default": False}), + "blur_radius": ("FLOAT", { + "default": 0.0, + "min": 0.0, + "max": 100, + "step": 0.1 + }), + "lerp_alpha": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}), + "decay_factor": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}), + }, + "optional": { + "fill_holes": ("BOOLEAN", {"default": False}), + }, + } + + CATEGORY = "KJNodes/masking" + RETURN_TYPES = ("MASK", "MASK",) + RETURN_NAMES = ("mask", "mask_inverted",) + FUNCTION = "expand_mask" + DESCRIPTION = """ +# GrowMaskWithBlur +- mask: Input mask or mask batch +- expand: Expand or contract mask or mask batch by a given amount +- incremental_expandrate: increase expand rate by a given amount per frame +- tapered_corners: use tapered corners +- flip_input: flip input mask +- blur_radius: value higher than 0 will blur the mask +- lerp_alpha: alpha value for interpolation between frames +- decay_factor: decay value for interpolation between frames +- fill_holes: fill holes in the mask (slow)""" + + def expand_mask(self, mask, expand, tapered_corners, flip_input, blur_radius, incremental_expandrate, lerp_alpha, decay_factor, fill_holes=False): + alpha = lerp_alpha + decay = decay_factor + if flip_input: + mask = 1.0 - mask + c = 0 if tapered_corners else 1 + kernel = np.array([[c, 1, c], + [1, 1, 1], + [c, 1, c]]) + growmask = mask.reshape((-1, mask.shape[-2], mask.shape[-1])).cpu() + out = [] + previous_output = None + current_expand = expand + for m in growmask: + output = m.numpy() + for _ in range(abs(round(current_expand))): + if current_expand < 0: + output = scipy.ndimage.grey_erosion(output, footprint=kernel) + else: + output = scipy.ndimage.grey_dilation(output, footprint=kernel) + if current_expand < 0: + current_expand -= abs(incremental_expandrate) + else: + current_expand += abs(incremental_expandrate) + if fill_holes: + binary_mask = output > 0 + output = scipy.ndimage.binary_fill_holes(binary_mask) + output = output.astype(np.float32) * 255 + output = torch.from_numpy(output) + if alpha < 1.0 and previous_output is not None: + # Interpolate between the previous and current frame + output = alpha * output + (1 - alpha) * previous_output + if decay < 1.0 and previous_output is not None: + # Add the decayed previous output to the current frame + output += decay * previous_output + output = output / output.max() + previous_output = output + out.append(output) + + if blur_radius != 0: + # Convert the tensor list to PIL images, apply blur, and convert back + for idx, tensor in enumerate(out): + # Convert tensor to PIL image + pil_image = tensor2pil(tensor.cpu().detach())[0] + # Apply Gaussian blur + pil_image = pil_image.filter(ImageFilter.GaussianBlur(blur_radius)) + # Convert back to tensor + out[idx] = pil2tensor(pil_image) + blurred = torch.cat(out, dim=0) + return (blurred, 1.0 - blurred) + else: + return (torch.stack(out, dim=0), 1.0 - torch.stack(out, dim=0),) + +class MaskBatchMulti: + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "inputcount": ("INT", {"default": 2, "min": 2, "max": 1000, "step": 1}), + "mask_1": ("MASK", ), + "mask_2": ("MASK", ), + }, + } + + RETURN_TYPES = ("MASK",) + RETURN_NAMES = ("masks",) + FUNCTION = "combine" + CATEGORY = "KJNodes/masking" + DESCRIPTION = """ +Creates an image batch from multiple masks. +You can set how many inputs the node has, +with the **inputcount** and clicking update. +""" + + def combine(self, inputcount, **kwargs): + mask = kwargs["mask_1"] + for c in range(1, inputcount): + new_mask = kwargs[f"mask_{c + 1}"] + if mask.shape[1:] != new_mask.shape[1:]: + new_mask = F.interpolate(new_mask.unsqueeze(1), size=(mask.shape[1], mask.shape[2]), mode="bicubic").squeeze(1) + mask = torch.cat((mask, new_mask), dim=0) + return (mask,) + +class OffsetMask: + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "mask": ("MASK",), + "x": ("INT", { "default": 0, "min": -4096, "max": MAX_RESOLUTION, "step": 1, "display": "number" }), + "y": ("INT", { "default": 0, "min": -4096, "max": MAX_RESOLUTION, "step": 1, "display": "number" }), + "angle": ("INT", { "default": 0, "min": -360, "max": 360, "step": 1, "display": "number" }), + "duplication_factor": ("INT", { "default": 1, "min": 1, "max": 1000, "step": 1, "display": "number" }), + "roll": ("BOOLEAN", { "default": False }), + "incremental": ("BOOLEAN", { "default": False }), + "padding_mode": ( + [ + 'empty', + 'border', + 'reflection', + + ], { + "default": 'empty' + }), + } + } + + RETURN_TYPES = ("MASK",) + RETURN_NAMES = ("mask",) + FUNCTION = "offset" + CATEGORY = "KJNodes/masking" + DESCRIPTION = """ +Offsets the mask by the specified amount. + - mask: Input mask or mask batch + - x: Horizontal offset + - y: Vertical offset + - angle: Angle in degrees + - roll: roll edge wrapping + - duplication_factor: Number of times to duplicate the mask to form a batch + - border padding_mode: Padding mode for the mask +""" + + def offset(self, mask, x, y, angle, roll=False, incremental=False, duplication_factor=1, padding_mode="empty"): + # Create duplicates of the mask batch + mask = mask.repeat(duplication_factor, 1, 1).clone() + + batch_size, height, width = mask.shape + + if angle != 0 and incremental: + for i in range(batch_size): + rotation_angle = angle * (i+1) + mask[i] = TF.rotate(mask[i].unsqueeze(0), rotation_angle).squeeze(0) + elif angle > 0: + for i in range(batch_size): + mask[i] = TF.rotate(mask[i].unsqueeze(0), angle).squeeze(0) + + if roll: + if incremental: + for i in range(batch_size): + shift_x = min(x*(i+1), width-1) + shift_y = min(y*(i+1), height-1) + if shift_x != 0: + mask[i] = torch.roll(mask[i], shifts=shift_x, dims=1) + if shift_y != 0: + mask[i] = torch.roll(mask[i], shifts=shift_y, dims=0) + else: + shift_x = min(x, width-1) + shift_y = min(y, height-1) + if shift_x != 0: + mask = torch.roll(mask, shifts=shift_x, dims=2) + if shift_y != 0: + mask = torch.roll(mask, shifts=shift_y, dims=1) + else: + + for i in range(batch_size): + if incremental: + temp_x = min(x * (i+1), width-1) + temp_y = min(y * (i+1), height-1) + else: + temp_x = min(x, width-1) + temp_y = min(y, height-1) + if temp_x > 0: + if padding_mode == 'empty': + mask[i] = torch.cat([torch.zeros((height, temp_x)), mask[i, :, :-temp_x]], dim=1) + elif padding_mode in ['replicate', 'reflect']: + mask[i] = F.pad(mask[i, :, :-temp_x], (0, temp_x), mode=padding_mode) + elif temp_x < 0: + if padding_mode == 'empty': + mask[i] = torch.cat([mask[i, :, :temp_x], torch.zeros((height, -temp_x))], dim=1) + elif padding_mode in ['replicate', 'reflect']: + mask[i] = F.pad(mask[i, :, -temp_x:], (temp_x, 0), mode=padding_mode) + + if temp_y > 0: + if padding_mode == 'empty': + mask[i] = torch.cat([torch.zeros((temp_y, width)), mask[i, :-temp_y, :]], dim=0) + elif padding_mode in ['replicate', 'reflect']: + mask[i] = F.pad(mask[i, :-temp_y, :], (0, temp_y), mode=padding_mode) + elif temp_y < 0: + if padding_mode == 'empty': + mask[i] = torch.cat([mask[i, :temp_y, :], torch.zeros((-temp_y, width))], dim=0) + elif padding_mode in ['replicate', 'reflect']: + mask[i] = F.pad(mask[i, -temp_y:, :], (temp_y, 0), mode=padding_mode) + + return mask, + +class RoundMask: + @classmethod + def INPUT_TYPES(s): + return {"required": { + "mask": ("MASK",), + }} + + RETURN_TYPES = ("MASK",) + FUNCTION = "round" + CATEGORY = "KJNodes/masking" + DESCRIPTION = """ +Rounds the mask or batch of masks to a binary mask. +RoundMask example + +""" + + def round(self, mask): + mask = mask.round() + return (mask,) + +class ResizeMask: + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "mask": ("MASK",), + "width": ("INT", { "default": 512, "min": 0, "max": MAX_RESOLUTION, "step": 8, "display": "number" }), + "height": ("INT", { "default": 512, "min": 0, "max": MAX_RESOLUTION, "step": 8, "display": "number" }), + "keep_proportions": ("BOOLEAN", { "default": False }), + } + } + + RETURN_TYPES = ("MASK", "INT", "INT",) + RETURN_NAMES = ("mask", "width", "height",) + FUNCTION = "resize" + CATEGORY = "KJNodes/masking" + DESCRIPTION = """ +Resizes the mask or batch of masks to the specified width and height. +""" + + def resize(self, mask, width, height, keep_proportions): + if keep_proportions: + _, oh, ow, _ = mask.shape + width = ow if width == 0 else width + height = oh if height == 0 else height + ratio = min(width / ow, height / oh) + width = round(ow*ratio) + height = round(oh*ratio) + + outputs = mask.unsqueeze(0) # Add an extra dimension for batch size + outputs = F.interpolate(outputs, size=(height, width), mode="nearest") + outputs = outputs.squeeze(0) # Remove the extra dimension after interpolation + + return(outputs, outputs.shape[2], outputs.shape[1],) + +class RemapMaskRange: + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "mask": ("MASK",), + "min": ("FLOAT", {"default": 0.0,"min": -10.0, "max": 1.0, "step": 0.01}), + "max": ("FLOAT", {"default": 1.0,"min": 0.0, "max": 10.0, "step": 0.01}), + } + } + + RETURN_TYPES = ("MASK",) + RETURN_NAMES = ("mask",) + FUNCTION = "remap" + CATEGORY = "KJNodes/masking" + DESCRIPTION = """ +Sets new min and max values for the mask. +""" + + def remap(self, mask, min, max): + + # Find the maximum value in the mask + mask_max = torch.max(mask) + + # If the maximum mask value is zero, avoid division by zero by setting it to 1 + mask_max = mask_max if mask_max > 0 else 1 + + # Scale the mask values to the new range defined by min and max + # The highest pixel value in the mask will be scaled to max + scaled_mask = (mask / mask_max) * (max - min) + min + + # Clamp the values to ensure they are within [0.0, 1.0] + scaled_mask = torch.clamp(scaled_mask, min=0.0, max=1.0) + + return (scaled_mask, ) \ No newline at end of file diff --git a/nodes/nodes.py b/nodes/nodes.py index 154345a..2b60ed8 100644 --- a/nodes/nodes.py +++ b/nodes/nodes.py @@ -1,24 +1,14 @@ import torch import torch.nn.functional as F -from torchvision.transforms import functional as TF - -import scipy.ndimage import matplotlib.pyplot as plt import numpy as np -from PIL import ImageFilter, Image, ImageDraw, ImageFont -from contextlib import nullcontext +from PIL import Image -import json -import re -import os -import io +import json, re, os, io, time import model_management -from nodes import MAX_RESOLUTION - import folder_paths -from ..utility.utility import tensor2pil, pil2tensor -from comfy.utils import ProgressBar +from nodes import MAX_RESOLUTION script_directory = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) folder_paths.add_model_folder_path("kjnodes_fonts", os.path.join(script_directory, "fonts")) @@ -99,340 +89,7 @@ class StringConstantMultiline: return (new_string, ) -class CreateFluidMask: - - RETURN_TYPES = ("IMAGE", "MASK") - FUNCTION = "createfluidmask" - CATEGORY = "KJNodes/masking/generate" - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "invert": ("BOOLEAN", {"default": False}), - "frames": ("INT", {"default": 0,"min": 0, "max": 255, "step": 1}), - "width": ("INT", {"default": 256,"min": 16, "max": 4096, "step": 1}), - "height": ("INT", {"default": 256,"min": 16, "max": 4096, "step": 1}), - "inflow_count": ("INT", {"default": 3,"min": 0, "max": 255, "step": 1}), - "inflow_velocity": ("INT", {"default": 1,"min": 0, "max": 255, "step": 1}), - "inflow_radius": ("INT", {"default": 8,"min": 0, "max": 255, "step": 1}), - "inflow_padding": ("INT", {"default": 50,"min": 0, "max": 255, "step": 1}), - "inflow_duration": ("INT", {"default": 60,"min": 0, "max": 255, "step": 1}), - }, - } - #using code from https://github.com/GregTJ/stable-fluids - def createfluidmask(self, frames, width, height, invert, inflow_count, inflow_velocity, inflow_radius, inflow_padding, inflow_duration): - from ..utility.fluid import Fluid - from scipy.spatial import erf - out = [] - masks = [] - RESOLUTION = width, height - DURATION = frames - - INFLOW_PADDING = inflow_padding - INFLOW_DURATION = inflow_duration - INFLOW_RADIUS = inflow_radius - INFLOW_VELOCITY = inflow_velocity - INFLOW_COUNT = inflow_count - - print('Generating fluid solver, this may take some time.') - fluid = Fluid(RESOLUTION, 'dye') - - center = np.floor_divide(RESOLUTION, 2) - r = np.min(center) - INFLOW_PADDING - - points = np.linspace(-np.pi, np.pi, INFLOW_COUNT, endpoint=False) - points = tuple(np.array((np.cos(p), np.sin(p))) for p in points) - normals = tuple(-p for p in points) - points = tuple(r * p + center for p in points) - - inflow_velocity = np.zeros_like(fluid.velocity) - inflow_dye = np.zeros(fluid.shape) - for p, n in zip(points, normals): - mask = np.linalg.norm(fluid.indices - p[:, None, None], axis=0) <= INFLOW_RADIUS - inflow_velocity[:, mask] += n[:, None] * INFLOW_VELOCITY - inflow_dye[mask] = 1 - - - for f in range(DURATION): - print(f'Computing frame {f + 1} of {DURATION}.') - if f <= INFLOW_DURATION: - fluid.velocity += inflow_velocity - fluid.dye += inflow_dye - - curl = fluid.step()[1] - # Using the error function to make the contrast a bit higher. - # Any other sigmoid function e.g. smoothstep would work. - curl = (erf(curl * 2) + 1) / 4 - - color = np.dstack((curl, np.ones(fluid.shape), fluid.dye)) - color = (np.clip(color, 0, 1) * 255).astype('uint8') - image = np.array(color).astype(np.float32) / 255.0 - image = torch.from_numpy(image)[None,] - mask = image[:, :, :, 0] - masks.append(mask) - out.append(image) - - if invert: - return (1.0 - torch.cat(out, dim=0),1.0 - torch.cat(masks, dim=0),) - return (torch.cat(out, dim=0),torch.cat(masks, dim=0),) - -class CreateAudioMask: - - RETURN_TYPES = ("IMAGE",) - FUNCTION = "createaudiomask" - CATEGORY = "KJNodes/deprecated" - - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "invert": ("BOOLEAN", {"default": False}), - "frames": ("INT", {"default": 16,"min": 1, "max": 255, "step": 1}), - "scale": ("FLOAT", {"default": 0.5,"min": 0.0, "max": 2.0, "step": 0.01}), - "audio_path": ("STRING", {"default": "audio.wav"}), - "width": ("INT", {"default": 256,"min": 16, "max": 4096, "step": 1}), - "height": ("INT", {"default": 256,"min": 16, "max": 4096, "step": 1}), - }, - } - - def createaudiomask(self, frames, width, height, invert, audio_path, scale): - try: - import librosa - except ImportError: - raise Exception("Can not import librosa. Install it with 'pip install librosa'") - batch_size = frames - out = [] - masks = [] - if audio_path == "audio.wav": #I don't know why relative path won't work otherwise... - audio_path = os.path.join(script_directory, audio_path) - audio, sr = librosa.load(audio_path) - spectrogram = np.abs(librosa.stft(audio)) - - for i in range(batch_size): - image = Image.new("RGB", (width, height), "black") - draw = ImageDraw.Draw(image) - frame = spectrogram[:, i] - circle_radius = int(height * np.mean(frame)) - circle_radius *= scale - circle_center = (width // 2, height // 2) # Calculate the center of the image - - draw.ellipse([(circle_center[0] - circle_radius, circle_center[1] - circle_radius), - (circle_center[0] + circle_radius, circle_center[1] + circle_radius)], - fill='white') - - image = np.array(image).astype(np.float32) / 255.0 - image = torch.from_numpy(image)[None,] - mask = image[:, :, :, 0] - masks.append(mask) - out.append(image) - - if invert: - return (1.0 - torch.cat(out, dim=0),) - return (torch.cat(out, dim=0),torch.cat(masks, dim=0),) - -class CreateGradientMask: - - RETURN_TYPES = ("MASK",) - FUNCTION = "createmask" - CATEGORY = "KJNodes/masking/generate" - - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "invert": ("BOOLEAN", {"default": False}), - "frames": ("INT", {"default": 0,"min": 0, "max": 255, "step": 1}), - "width": ("INT", {"default": 256,"min": 16, "max": 4096, "step": 1}), - "height": ("INT", {"default": 256,"min": 16, "max": 4096, "step": 1}), - }, - } - def createmask(self, frames, width, height, invert): - # Define the number of images in the batch - batch_size = frames - out = [] - # Create an empty array to store the image batch - image_batch = np.zeros((batch_size, height, width), dtype=np.float32) - # Generate the black to white gradient for each image - for i in range(batch_size): - gradient = np.linspace(1.0, 0.0, width, dtype=np.float32) - time = i / frames # Calculate the time variable - offset_gradient = gradient - time # Offset the gradient values based on time - image_batch[i] = offset_gradient.reshape(1, -1) - output = torch.from_numpy(image_batch) - mask = output - out.append(mask) - if invert: - return (1.0 - torch.cat(out, dim=0),) - return (torch.cat(out, dim=0),) - -class CreateFadeMask: - - RETURN_TYPES = ("MASK",) - FUNCTION = "createfademask" - CATEGORY = "KJNodes/deprecated" - - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "invert": ("BOOLEAN", {"default": False}), - "frames": ("INT", {"default": 2,"min": 2, "max": 255, "step": 1}), - "width": ("INT", {"default": 256,"min": 16, "max": 4096, "step": 1}), - "height": ("INT", {"default": 256,"min": 16, "max": 4096, "step": 1}), - "interpolation": (["linear", "ease_in", "ease_out", "ease_in_out"],), - "start_level": ("FLOAT", {"default": 1.0,"min": 0.0, "max": 1.0, "step": 0.01}), - "midpoint_level": ("FLOAT", {"default": 0.5,"min": 0.0, "max": 1.0, "step": 0.01}), - "end_level": ("FLOAT", {"default": 0.0,"min": 0.0, "max": 1.0, "step": 0.01}), - "midpoint_frame": ("INT", {"default": 0,"min": 0, "max": 4096, "step": 1}), - }, - } - - def createfademask(self, frames, width, height, invert, interpolation, start_level, midpoint_level, end_level, midpoint_frame): - def ease_in(t): - return t * t - - def ease_out(t): - return 1 - (1 - t) * (1 - t) - - def ease_in_out(t): - return 3 * t * t - 2 * t * t * t - - batch_size = frames - out = [] - image_batch = np.zeros((batch_size, height, width), dtype=np.float32) - - if midpoint_frame == 0: - midpoint_frame = batch_size // 2 - - for i in range(batch_size): - if i <= midpoint_frame: - t = i / midpoint_frame - if interpolation == "ease_in": - t = ease_in(t) - elif interpolation == "ease_out": - t = ease_out(t) - elif interpolation == "ease_in_out": - t = ease_in_out(t) - color = start_level - t * (start_level - midpoint_level) - else: - t = (i - midpoint_frame) / (batch_size - midpoint_frame) - if interpolation == "ease_in": - t = ease_in(t) - elif interpolation == "ease_out": - t = ease_out(t) - elif interpolation == "ease_in_out": - t = ease_in_out(t) - color = midpoint_level - t * (midpoint_level - end_level) - - color = np.clip(color, 0, 255) - image = np.full((height, width), color, dtype=np.float32) - image_batch[i] = image - - output = torch.from_numpy(image_batch) - mask = output - out.append(mask) - - if invert: - return (1.0 - torch.cat(out, dim=0),) - return (torch.cat(out, dim=0),) - -class CreateFadeMaskAdvanced: - - RETURN_TYPES = ("MASK",) - FUNCTION = "createfademask" - CATEGORY = "KJNodes/masking/generate" - DESCRIPTION = """ -Create a batch of masks interpolated between given frames and values. -Uses same syntax as Fizz' BatchValueSchedule. -First value is the frame index (not that this starts from 0, not 1) -and the second value inside the brackets is the float value of the mask in range 0.0 - 1.0 - -For example the default values: -0:(0.0) -7:(1.0) -15:(0.0) - -Would create a mask batch fo 16 frames, starting from black, -interpolating with the chosen curve to fully white at the 8th frame, -and interpolating from that to fully black at the 16th frame. -""" - - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "points_string": ("STRING", {"default": "0:(0.0),\n7:(1.0),\n15:(0.0)\n", "multiline": True}), - "invert": ("BOOLEAN", {"default": False}), - "frames": ("INT", {"default": 16,"min": 2, "max": 255, "step": 1}), - "width": ("INT", {"default": 512,"min": 1, "max": 4096, "step": 1}), - "height": ("INT", {"default": 512,"min": 1, "max": 4096, "step": 1}), - "interpolation": (["linear", "ease_in", "ease_out", "ease_in_out"],), - }, - } - - def createfademask(self, frames, width, height, invert, points_string, interpolation): - def ease_in(t): - return t * t - - def ease_out(t): - return 1 - (1 - t) * (1 - t) - - def ease_in_out(t): - return 3 * t * t - 2 * t * t * t - - # Parse the input string into a list of tuples - points = [] - points_string = points_string.rstrip(',\n') - for point_str in points_string.split(','): - frame_str, color_str = point_str.split(':') - frame = int(frame_str.strip()) - color = float(color_str.strip()[1:-1]) # Remove parentheses around color - points.append((frame, color)) - - # Check if the last frame is already in the points - if len(points) == 0 or points[-1][0] != frames - 1: - # If not, add it with the color of the last specified frame - points.append((frames - 1, points[-1][1] if points else 0)) - - # Sort the points by frame number - points.sort(key=lambda x: x[0]) - - batch_size = frames - out = [] - image_batch = np.zeros((batch_size, height, width), dtype=np.float32) - - # Index of the next point to interpolate towards - next_point = 1 - - for i in range(batch_size): - while next_point < len(points) and i > points[next_point][0]: - next_point += 1 - - # Interpolate between the previous point and the next point - prev_point = next_point - 1 - t = (i - points[prev_point][0]) / (points[next_point][0] - points[prev_point][0]) - if interpolation == "ease_in": - t = ease_in(t) - elif interpolation == "ease_out": - t = ease_out(t) - elif interpolation == "ease_in_out": - t = ease_in_out(t) - elif interpolation == "linear": - pass # No need to modify `t` for linear interpolation - - color = points[prev_point][1] - t * (points[prev_point][1] - points[next_point][1]) - color = np.clip(color, 0, 255) - image = np.full((height, width), color, dtype=np.float32) - image_batch[i] = image - - output = torch.from_numpy(image_batch) - mask = output - out.append(mask) - - if invert: - return (1.0 - torch.cat(out, dim=0),) - return (torch.cat(out, dim=0),) class ScaleBatchPromptSchedule: @@ -515,266 +172,7 @@ Selects and returns the latents at the specified indices as an latent batch. samples["samples"] = chosen_latents return (samples,) -class CreateTextMask: - RETURN_TYPES = ("IMAGE", "MASK",) - FUNCTION = "createtextmask" - CATEGORY = "KJNodes/text" - DESCRIPTION = """ -Creates a text image and mask. -Looks for fonts from this folder: -ComfyUI/custom_nodes/ComfyUI-KJNodes/fonts - -If start_rotation and/or end_rotation are different values, -creates animation between them. -""" - - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "invert": ("BOOLEAN", {"default": False}), - "frames": ("INT", {"default": 1,"min": 1, "max": 4096, "step": 1}), - "text_x": ("INT", {"default": 0,"min": 0, "max": 4096, "step": 1}), - "text_y": ("INT", {"default": 0,"min": 0, "max": 4096, "step": 1}), - "font_size": ("INT", {"default": 32,"min": 8, "max": 4096, "step": 1}), - "font_color": ("STRING", {"default": "white"}), - "text": ("STRING", {"default": "HELLO!", "multiline": True}), - "font": (folder_paths.get_filename_list("kjnodes_fonts"), ), - "width": ("INT", {"default": 512,"min": 16, "max": 4096, "step": 1}), - "height": ("INT", {"default": 512,"min": 16, "max": 4096, "step": 1}), - "start_rotation": ("INT", {"default": 0,"min": 0, "max": 359, "step": 1}), - "end_rotation": ("INT", {"default": 0,"min": -359, "max": 359, "step": 1}), - }, - } - - def createtextmask(self, frames, width, height, invert, text_x, text_y, text, font_size, font_color, font, start_rotation, end_rotation): - # Define the number of images in the batch - batch_size = frames - out = [] - masks = [] - rotation = start_rotation - if start_rotation != end_rotation: - rotation_increment = (end_rotation - start_rotation) / (batch_size - 1) - - font_path = folder_paths.get_full_path("kjnodes_fonts", font) - # Generate the text - for i in range(batch_size): - image = Image.new("RGB", (width, height), "black") - draw = ImageDraw.Draw(image) - font = ImageFont.truetype(font_path, font_size) - - # Split the text into words - words = text.split() - - # Initialize variables for line creation - lines = [] - current_line = [] - current_line_width = 0 - try: #new pillow - # Iterate through words to create lines - for word in words: - word_width = font.getbbox(word)[2] - if current_line_width + word_width <= width - 2 * text_x: - current_line.append(word) - current_line_width += word_width + font.getbbox(" ")[2] # Add space width - else: - lines.append(" ".join(current_line)) - current_line = [word] - current_line_width = word_width - except: #old pillow - for word in words: - word_width = font.getsize(word)[0] - if current_line_width + word_width <= width - 2 * text_x: - current_line.append(word) - current_line_width += word_width + font.getsize(" ")[0] # Add space width - else: - lines.append(" ".join(current_line)) - current_line = [word] - current_line_width = word_width - - # Add the last line if it's not empty - if current_line: - lines.append(" ".join(current_line)) - - # Draw each line of text separately - y_offset = text_y - for line in lines: - text_width = font.getlength(line) - text_height = font_size - text_center_x = text_x + text_width / 2 - text_center_y = y_offset + text_height / 2 - try: - draw.text((text_x, y_offset), line, font=font, fill=font_color, features=['-liga']) - except: - draw.text((text_x, y_offset), line, font=font, fill=font_color) - y_offset += text_height # Move to the next line - - if start_rotation != end_rotation: - image = image.rotate(rotation, center=(text_center_x, text_center_y)) - rotation += rotation_increment - - image = np.array(image).astype(np.float32) / 255.0 - image = torch.from_numpy(image)[None,] - mask = image[:, :, :, 0] - masks.append(mask) - out.append(image) - - if invert: - return (1.0 - torch.cat(out, dim=0), 1.0 - torch.cat(masks, dim=0),) - return (torch.cat(out, dim=0),torch.cat(masks, dim=0),) - -class GrowMaskWithBlur: - @classmethod - def INPUT_TYPES(cls): - return { - "required": { - "mask": ("MASK",), - "expand": ("INT", {"default": 0, "min": -MAX_RESOLUTION, "max": MAX_RESOLUTION, "step": 1}), - "incremental_expandrate": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 100.0, "step": 0.1}), - "tapered_corners": ("BOOLEAN", {"default": True}), - "flip_input": ("BOOLEAN", {"default": False}), - "blur_radius": ("FLOAT", { - "default": 0.0, - "min": 0.0, - "max": 100, - "step": 0.1 - }), - "lerp_alpha": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}), - "decay_factor": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}), - }, - "optional": { - "fill_holes": ("BOOLEAN", {"default": False}), - }, - } - - CATEGORY = "KJNodes/masking" - RETURN_TYPES = ("MASK", "MASK",) - RETURN_NAMES = ("mask", "mask_inverted",) - FUNCTION = "expand_mask" - DESCRIPTION = """ -# GrowMaskWithBlur -- mask: Input mask or mask batch -- expand: Expand or contract mask or mask batch by a given amount -- incremental_expandrate: increase expand rate by a given amount per frame -- tapered_corners: use tapered corners -- flip_input: flip input mask -- blur_radius: value higher than 0 will blur the mask -- lerp_alpha: alpha value for interpolation between frames -- decay_factor: decay value for interpolation between frames -- fill_holes: fill holes in the mask (slow)""" - - def expand_mask(self, mask, expand, tapered_corners, flip_input, blur_radius, incremental_expandrate, lerp_alpha, decay_factor, fill_holes=False): - alpha = lerp_alpha - decay = decay_factor - if flip_input: - mask = 1.0 - mask - c = 0 if tapered_corners else 1 - kernel = np.array([[c, 1, c], - [1, 1, 1], - [c, 1, c]]) - growmask = mask.reshape((-1, mask.shape[-2], mask.shape[-1])).cpu() - out = [] - previous_output = None - current_expand = expand - for m in growmask: - output = m.numpy() - for _ in range(abs(round(current_expand))): - if current_expand < 0: - output = scipy.ndimage.grey_erosion(output, footprint=kernel) - else: - output = scipy.ndimage.grey_dilation(output, footprint=kernel) - if current_expand < 0: - current_expand -= abs(incremental_expandrate) - else: - current_expand += abs(incremental_expandrate) - if fill_holes: - binary_mask = output > 0 - output = scipy.ndimage.binary_fill_holes(binary_mask) - output = output.astype(np.float32) * 255 - output = torch.from_numpy(output) - if alpha < 1.0 and previous_output is not None: - # Interpolate between the previous and current frame - output = alpha * output + (1 - alpha) * previous_output - if decay < 1.0 and previous_output is not None: - # Add the decayed previous output to the current frame - output += decay * previous_output - output = output / output.max() - previous_output = output - out.append(output) - - if blur_radius != 0: - # Convert the tensor list to PIL images, apply blur, and convert back - for idx, tensor in enumerate(out): - # Convert tensor to PIL image - pil_image = tensor2pil(tensor.cpu().detach())[0] - # Apply Gaussian blur - pil_image = pil_image.filter(ImageFilter.GaussianBlur(blur_radius)) - # Convert back to tensor - out[idx] = pil2tensor(pil_image) - blurred = torch.cat(out, dim=0) - return (blurred, 1.0 - blurred) - else: - return (torch.stack(out, dim=0), 1.0 - torch.stack(out, dim=0),) - -class ColorToMask: - - RETURN_TYPES = ("MASK",) - FUNCTION = "clip" - CATEGORY = "KJNodes/masking" - DESCRIPTION = """ -Converts chosen RGB value to a mask. -With batch inputs, the **per_batch** -controls the number of images processed at once. -""" - - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "images": ("IMAGE",), - "invert": ("BOOLEAN", {"default": False}), - "red": ("INT", {"default": 0,"min": 0, "max": 255, "step": 1}), - "green": ("INT", {"default": 0,"min": 0, "max": 255, "step": 1}), - "blue": ("INT", {"default": 0,"min": 0, "max": 255, "step": 1}), - "threshold": ("INT", {"default": 10,"min": 0, "max": 255, "step": 1}), - "per_batch": ("INT", {"default": 16, "min": 1, "max": 4096, "step": 1}), - }, - } - - def clip(self, images, red, green, blue, threshold, invert, per_batch): - - color = torch.tensor([red, green, blue], dtype=torch.uint8) - black = torch.tensor([0, 0, 0], dtype=torch.uint8) - white = torch.tensor([255, 255, 255], dtype=torch.uint8) - - if invert: - black, white = white, black - - steps = images.shape[0] - pbar = ProgressBar(steps) - tensors_out = [] - - for start_idx in range(0, images.shape[0], per_batch): - - # Calculate color distances - color_distances = torch.norm(images[start_idx:start_idx+per_batch] * 255 - color, dim=-1) - - # Create a mask based on the threshold - mask = color_distances <= threshold - - # Apply the mask to create new images - mask_out = torch.where(mask.unsqueeze(-1), white, black).float() - mask_out = mask_out.mean(dim=-1) - - tensors_out.append(mask_out.cpu()) - batch_count = mask_out.shape[0] - pbar.update(batch_count) - - tensors_out = torch.cat(tensors_out, dim=0) - tensors_out = torch.clamp(tensors_out, min=0.0, max=1.0) - return tensors_out, - class ConditioningMultiCombine: @classmethod def INPUT_TYPES(s): @@ -804,37 +202,6 @@ Combines multiple conditioning nodes into one return (cond, inputcount,) - -class MaskBatchMulti: - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "inputcount": ("INT", {"default": 2, "min": 2, "max": 1000, "step": 1}), - "mask_1": ("MASK", ), - "mask_2": ("MASK", ), - }, - } - - RETURN_TYPES = ("MASK",) - RETURN_NAMES = ("masks",) - FUNCTION = "combine" - CATEGORY = "KJNodes/masking" - DESCRIPTION = """ -Creates an image batch from multiple masks. -You can set how many inputs the node has, -with the **inputcount** and clicking update. -""" - - def combine(self, inputcount, **kwargs): - mask = kwargs["mask_1"] - for c in range(1, inputcount): - new_mask = kwargs[f"mask_{c + 1}"] - if mask.shape[1:] != new_mask.shape[1:]: - new_mask = F.interpolate(new_mask.unsqueeze(1), size=(mask.shape[1], mask.shape[2]), mode="bicubic").squeeze(1) - mask = torch.cat((mask, new_mask), dim=0) - return (mask,) - class JoinStrings: @classmethod def INPUT_TYPES(cls): @@ -1311,264 +678,6 @@ class EmptyLatentImagePresets: return (latent, int(width), int(height),) -class BatchCLIPSeg: - - def __init__(self): - pass - - @classmethod - def INPUT_TYPES(s): - - return {"required": - { - "images": ("IMAGE",), - "text": ("STRING", {"multiline": False}), - "threshold": ("FLOAT", {"default": 0.1,"min": 0.0, "max": 10.0, "step": 0.001}), - "binary_mask": ("BOOLEAN", {"default": True}), - "combine_mask": ("BOOLEAN", {"default": False}), - "use_cuda": ("BOOLEAN", {"default": True}), - }, - } - - CATEGORY = "KJNodes/masking" - RETURN_TYPES = ("MASK",) - RETURN_NAMES = ("Mask",) - FUNCTION = "segment_image" - DESCRIPTION = """ -Segments an image or batch of images using CLIPSeg. -""" - - def segment_image(self, images, text, threshold, binary_mask, combine_mask, use_cuda): - from transformers import CLIPSegProcessor, CLIPSegForImageSegmentation - out = [] - height, width, _ = images[0].shape - if use_cuda and torch.cuda.is_available(): - device = torch.device("cuda") - else: - device = torch.device("cpu") - dtype = model_management.unet_dtype() - model = CLIPSegForImageSegmentation.from_pretrained("CIDAS/clipseg-rd64-refined") - model.to(dtype) - model.to(device) - images = images.to(device) - processor = CLIPSegProcessor.from_pretrained("CIDAS/clipseg-rd64-refined") - pbar = ProgressBar(images.shape[0]) - autocast_condition = (dtype != torch.float32) and not model_management.is_device_mps(device) - with torch.autocast(model_management.get_autocast_device(device), dtype=dtype) if autocast_condition else nullcontext(): - for image in images: - image = (image* 255).type(torch.uint8) - prompt = text - input_prc = processor(text=prompt, images=image, return_tensors="pt") - # Move the processed input to the device - for key in input_prc: - input_prc[key] = input_prc[key].to(device) - - outputs = model(**input_prc) - - tensor = torch.sigmoid(outputs[0]) - tensor_thresholded = torch.where(tensor > threshold, tensor, torch.tensor(0, dtype=torch.float)) - tensor_normalized = (tensor_thresholded - tensor_thresholded.min()) / (tensor_thresholded.max() - tensor_thresholded.min()) - tensor = tensor_normalized - - # Resize the mask - if len(tensor.shape) == 3: - tensor = tensor.unsqueeze(0) - resized_tensor = F.interpolate(tensor, size=(height, width), mode='nearest') - - # Remove the extra dimensions - resized_tensor = resized_tensor[0, 0, :, :] - pbar.update(1) - out.append(resized_tensor) - - results = torch.stack(out).cpu().float() - - if combine_mask: - combined_results = torch.max(results, dim=0)[0] - results = combined_results.unsqueeze(0).repeat(len(images),1,1) - - if binary_mask: - results = results.round() - - return results, - -class GetMaskSize: - @classmethod - def INPUT_TYPES(s): - return {"required": { - "mask": ("MASK",), - }} - - RETURN_TYPES = ("MASK","INT", "INT", ) - RETURN_NAMES = ("mask", "width", "height",) - FUNCTION = "getsize" - CATEGORY = "KJNodes/masking" - DESCRIPTION = """ -Returns the width and height of the mask, -and passes through the mask unchanged. - -""" - - def getsize(self, mask): - width = mask.shape[2] - height = mask.shape[1] - return (mask, width, height,) - -class RoundMask: - @classmethod - def INPUT_TYPES(s): - return {"required": { - "mask": ("MASK",), - }} - - RETURN_TYPES = ("MASK",) - FUNCTION = "round" - CATEGORY = "KJNodes/masking" - DESCRIPTION = """ -Rounds the mask or batch of masks to a binary mask. -RoundMask example - -""" - - def round(self, mask): - mask = mask.round() - return (mask,) - -class ResizeMask: - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "mask": ("MASK",), - "width": ("INT", { "default": 512, "min": 0, "max": MAX_RESOLUTION, "step": 8, "display": "number" }), - "height": ("INT", { "default": 512, "min": 0, "max": MAX_RESOLUTION, "step": 8, "display": "number" }), - "keep_proportions": ("BOOLEAN", { "default": False }), - } - } - - RETURN_TYPES = ("MASK", "INT", "INT",) - RETURN_NAMES = ("mask", "width", "height",) - FUNCTION = "resize" - CATEGORY = "KJNodes/masking" - DESCRIPTION = """ -Resizes the mask or batch of masks to the specified width and height. -""" - - def resize(self, mask, width, height, keep_proportions): - if keep_proportions: - _, oh, ow, _ = mask.shape - width = ow if width == 0 else width - height = oh if height == 0 else height - ratio = min(width / ow, height / oh) - width = round(ow*ratio) - height = round(oh*ratio) - - outputs = mask.unsqueeze(0) # Add an extra dimension for batch size - outputs = F.interpolate(outputs, size=(height, width), mode="nearest") - outputs = outputs.squeeze(0) # Remove the extra dimension after interpolation - - return(outputs, outputs.shape[2], outputs.shape[1],) - -class OffsetMask: - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "mask": ("MASK",), - "x": ("INT", { "default": 0, "min": -4096, "max": MAX_RESOLUTION, "step": 1, "display": "number" }), - "y": ("INT", { "default": 0, "min": -4096, "max": MAX_RESOLUTION, "step": 1, "display": "number" }), - "angle": ("INT", { "default": 0, "min": -360, "max": 360, "step": 1, "display": "number" }), - "duplication_factor": ("INT", { "default": 1, "min": 1, "max": 1000, "step": 1, "display": "number" }), - "roll": ("BOOLEAN", { "default": False }), - "incremental": ("BOOLEAN", { "default": False }), - "padding_mode": ( - [ - 'empty', - 'border', - 'reflection', - - ], { - "default": 'empty' - }), - } - } - - RETURN_TYPES = ("MASK",) - RETURN_NAMES = ("mask",) - FUNCTION = "offset" - CATEGORY = "KJNodes/masking" - DESCRIPTION = """ -Offsets the mask by the specified amount. - - mask: Input mask or mask batch - - x: Horizontal offset - - y: Vertical offset - - angle: Angle in degrees - - roll: roll edge wrapping - - duplication_factor: Number of times to duplicate the mask to form a batch - - border padding_mode: Padding mode for the mask -""" - - def offset(self, mask, x, y, angle, roll=False, incremental=False, duplication_factor=1, padding_mode="empty"): - # Create duplicates of the mask batch - mask = mask.repeat(duplication_factor, 1, 1).clone() - - batch_size, height, width = mask.shape - - if angle != 0 and incremental: - for i in range(batch_size): - rotation_angle = angle * (i+1) - mask[i] = TF.rotate(mask[i].unsqueeze(0), rotation_angle).squeeze(0) - elif angle > 0: - for i in range(batch_size): - mask[i] = TF.rotate(mask[i].unsqueeze(0), angle).squeeze(0) - - if roll: - if incremental: - for i in range(batch_size): - shift_x = min(x*(i+1), width-1) - shift_y = min(y*(i+1), height-1) - if shift_x != 0: - mask[i] = torch.roll(mask[i], shifts=shift_x, dims=1) - if shift_y != 0: - mask[i] = torch.roll(mask[i], shifts=shift_y, dims=0) - else: - shift_x = min(x, width-1) - shift_y = min(y, height-1) - if shift_x != 0: - mask = torch.roll(mask, shifts=shift_x, dims=2) - if shift_y != 0: - mask = torch.roll(mask, shifts=shift_y, dims=1) - else: - - for i in range(batch_size): - if incremental: - temp_x = min(x * (i+1), width-1) - temp_y = min(y * (i+1), height-1) - else: - temp_x = min(x, width-1) - temp_y = min(y, height-1) - if temp_x > 0: - if padding_mode == 'empty': - mask[i] = torch.cat([torch.zeros((height, temp_x)), mask[i, :, :-temp_x]], dim=1) - elif padding_mode in ['replicate', 'reflect']: - mask[i] = F.pad(mask[i, :, :-temp_x], (0, temp_x), mode=padding_mode) - elif temp_x < 0: - if padding_mode == 'empty': - mask[i] = torch.cat([mask[i, :, :temp_x], torch.zeros((height, -temp_x))], dim=1) - elif padding_mode in ['replicate', 'reflect']: - mask[i] = F.pad(mask[i, :, -temp_x:], (temp_x, 0), mode=padding_mode) - - if temp_y > 0: - if padding_mode == 'empty': - mask[i] = torch.cat([torch.zeros((temp_y, width)), mask[i, :-temp_y, :]], dim=0) - elif padding_mode in ['replicate', 'reflect']: - mask[i] = F.pad(mask[i, :-temp_y, :], (0, temp_y), mode=padding_mode) - elif temp_y < 0: - if padding_mode == 'empty': - mask[i] = torch.cat([mask[i, :temp_y, :], torch.zeros((-temp_y, width))], dim=0) - elif padding_mode in ['replicate', 'reflect']: - mask[i] = F.pad(mask[i, -temp_y:, :], (temp_y, 0), mode=padding_mode) - - return mask, class WidgetToString: @@ -1621,228 +730,6 @@ To see node id's, enable node id display from Manager badge menu. raise NameError(f"Node not found: {id}") return (', '.join(results).strip(', '), ) -class CreateShapeMask: - - RETURN_TYPES = ("MASK", "MASK",) - RETURN_NAMES = ("mask", "mask_inverted",) - FUNCTION = "createshapemask" - CATEGORY = "KJNodes/masking/generate" - DESCRIPTION = """ -Creates a mask or batch of masks with the specified shape. -Locations are center locations. -Grow value is the amount to grow the shape on each frame, creating animated masks. -""" - - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "shape": ( - [ 'circle', - 'square', - 'triangle', - ], - { - "default": 'circle' - }), - "frames": ("INT", {"default": 1,"min": 1, "max": 4096, "step": 1}), - "location_x": ("INT", {"default": 256,"min": 0, "max": 4096, "step": 1}), - "location_y": ("INT", {"default": 256,"min": 0, "max": 4096, "step": 1}), - "grow": ("INT", {"default": 0, "min": -512, "max": 512, "step": 1}), - "frame_width": ("INT", {"default": 512,"min": 16, "max": 4096, "step": 1}), - "frame_height": ("INT", {"default": 512,"min": 16, "max": 4096, "step": 1}), - "shape_width": ("INT", {"default": 128,"min": 8, "max": 4096, "step": 1}), - "shape_height": ("INT", {"default": 128,"min": 8, "max": 4096, "step": 1}), - }, - } - - def createshapemask(self, frames, frame_width, frame_height, location_x, location_y, shape_width, shape_height, grow, shape): - # Define the number of images in the batch - batch_size = frames - out = [] - color = "white" - for i in range(batch_size): - image = Image.new("RGB", (frame_width, frame_height), "black") - draw = ImageDraw.Draw(image) - - # Calculate the size for this frame and ensure it's not less than 0 - current_width = max(0, shape_width + i*grow) - current_height = max(0, shape_height + i*grow) - - if shape == 'circle' or shape == 'square': - # Define the bounding box for the shape - left_up_point = (location_x - current_width // 2, location_y - current_height // 2) - right_down_point = (location_x + current_width // 2, location_y + current_height // 2) - two_points = [left_up_point, right_down_point] - - if shape == 'circle': - draw.ellipse(two_points, fill=color) - elif shape == 'square': - draw.rectangle(two_points, fill=color) - - elif shape == 'triangle': - # Define the points for the triangle - left_up_point = (location_x - current_width // 2, location_y + current_height // 2) # bottom left - right_down_point = (location_x + current_width // 2, location_y + current_height // 2) # bottom right - top_point = (location_x, location_y - current_height // 2) # top point - draw.polygon([top_point, left_up_point, right_down_point], fill=color) - - image = pil2tensor(image) - mask = image[:, :, :, 0] - out.append(mask) - outstack = torch.cat(out, dim=0) - return (outstack, 1.0 - outstack,) - -class CreateVoronoiMask: - - RETURN_TYPES = ("MASK", "MASK",) - RETURN_NAMES = ("mask", "mask_inverted",) - FUNCTION = "createvoronoi" - CATEGORY = "KJNodes/masking/generate" - - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "frames": ("INT", {"default": 16,"min": 2, "max": 4096, "step": 1}), - "num_points": ("INT", {"default": 15,"min": 1, "max": 4096, "step": 1}), - "line_width": ("INT", {"default": 4,"min": 1, "max": 4096, "step": 1}), - "speed": ("FLOAT", {"default": 0.5,"min": 0.0, "max": 1.0, "step": 0.01}), - "frame_width": ("INT", {"default": 512,"min": 16, "max": 4096, "step": 1}), - "frame_height": ("INT", {"default": 512,"min": 16, "max": 4096, "step": 1}), - }, - } - - def createvoronoi(self, frames, num_points, line_width, speed, frame_width, frame_height): - from scipy.spatial import Voronoi - # Define the number of images in the batch - batch_size = frames - out = [] - - # Calculate aspect ratio - aspect_ratio = frame_width / frame_height - - # Create start and end points for each point, considering the aspect ratio - start_points = np.random.rand(num_points, 2) - start_points[:, 0] *= aspect_ratio - - end_points = np.random.rand(num_points, 2) - end_points[:, 0] *= aspect_ratio - - for i in range(batch_size): - # Interpolate the points' positions based on the current frame - t = (i * speed) / (batch_size - 1) # normalize to [0, 1] over the frames - t = np.clip(t, 0, 1) # ensure t is in [0, 1] - points = (1 - t) * start_points + t * end_points # lerp - - # Adjust points for aspect ratio - points[:, 0] *= aspect_ratio - - vor = Voronoi(points) - - # Create a blank image with a white background - fig, ax = plt.subplots() - plt.subplots_adjust(left=0, right=1, bottom=0, top=1) - ax.set_xlim([0, aspect_ratio]); ax.set_ylim([0, 1]) # adjust x limits - ax.axis('off') - ax.margins(0, 0) - fig.set_size_inches(aspect_ratio * frame_height/100, frame_height/100) # adjust figure size - ax.fill_between([0, 1], [0, 1], color='white') - - # Plot each Voronoi ridge - for simplex in vor.ridge_vertices: - simplex = np.asarray(simplex) - if np.all(simplex >= 0): - plt.plot(vor.vertices[simplex, 0], vor.vertices[simplex, 1], 'k-', linewidth=line_width) - - fig.canvas.draw() - img = np.array(fig.canvas.renderer._renderer) - - plt.close(fig) - - pil_img = Image.fromarray(img).convert("L") - mask = torch.tensor(np.array(pil_img)) / 255.0 - - out.append(mask) - - return (torch.stack(out, dim=0), 1.0 - torch.stack(out, dim=0),) - -class CreateMagicMask: - - RETURN_TYPES = ("MASK", "MASK",) - RETURN_NAMES = ("mask", "mask_inverted",) - FUNCTION = "createmagicmask" - CATEGORY = "KJNodes/masking/generate" - - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "frames": ("INT", {"default": 16,"min": 2, "max": 4096, "step": 1}), - "depth": ("INT", {"default": 12,"min": 1, "max": 500, "step": 1}), - "distortion": ("FLOAT", {"default": 1.5,"min": 0.0, "max": 100.0, "step": 0.01}), - "seed": ("INT", {"default": 123,"min": 0, "max": 99999999, "step": 1}), - "transitions": ("INT", {"default": 1,"min": 1, "max": 20, "step": 1}), - "frame_width": ("INT", {"default": 512,"min": 16, "max": 4096, "step": 1}), - "frame_height": ("INT", {"default": 512,"min": 16, "max": 4096, "step": 1}), - }, - } - - def createmagicmask(self, frames, transitions, depth, distortion, seed, frame_width, frame_height): - from ..utility.magictex import coordinate_grid, random_transform, magic - rng = np.random.default_rng(seed) - out = [] - coords = coordinate_grid((frame_width, frame_height)) - - # Calculate the number of frames for each transition - frames_per_transition = frames // transitions - - # Generate a base set of parameters - base_params = { - "coords": random_transform(coords, rng), - "depth": depth, - "distortion": distortion, - } - for t in range(transitions): - # Generate a second set of parameters that is at most max_diff away from the base parameters - params1 = base_params.copy() - params2 = base_params.copy() - - params1['coords'] = random_transform(coords, rng) - params2['coords'] = random_transform(coords, rng) - - for i in range(frames_per_transition): - # Compute the interpolation factor - alpha = i / frames_per_transition - - # Interpolate between the two sets of parameters - params = params1.copy() - params['coords'] = (1 - alpha) * params1['coords'] + alpha * params2['coords'] - - tex = magic(**params) - - dpi = frame_width / 10 - fig = plt.figure(figsize=(10, 10), dpi=dpi) - - ax = fig.add_subplot(111) - plt.subplots_adjust(left=0, right=1, bottom=0, top=1) - - ax.get_yaxis().set_ticks([]) - ax.get_xaxis().set_ticks([]) - ax.imshow(tex, aspect='auto') - - fig.canvas.draw() - img = np.array(fig.canvas.renderer._renderer) - - plt.close(fig) - - pil_img = Image.fromarray(img).convert("L") - mask = torch.tensor(np.array(pil_img)) / 255.0 - - out.append(mask) - - return (torch.stack(out, dim=0), 1.0 - torch.stack(out, dim=0),) - class BboxToInt: @classmethod @@ -2432,42 +1319,6 @@ https://huggingface.co/stabilityai/sv3d latent = torch.zeros([batch_size, 4, height // 8, width // 8]) return (final_positive, final_negative, {"samples": latent}) -class RemapMaskRange: - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "mask": ("MASK",), - "min": ("FLOAT", {"default": 0.0,"min": -10.0, "max": 1.0, "step": 0.01}), - "max": ("FLOAT", {"default": 1.0,"min": 0.0, "max": 10.0, "step": 0.01}), - } - } - - RETURN_TYPES = ("MASK",) - RETURN_NAMES = ("mask",) - FUNCTION = "remap" - CATEGORY = "KJNodes/masking" - DESCRIPTION = """ -Sets new min and max values for the mask. -""" - - def remap(self, mask, min, max): - - # Find the maximum value in the mask - mask_max = torch.max(mask) - - # If the maximum mask value is zero, avoid division by zero by setting it to 1 - mask_max = mask_max if mask_max > 0 else 1 - - # Scale the mask values to the new range defined by min and max - # The highest pixel value in the mask will be scaled to max - scaled_mask = (mask / mask_max) * (max - min) + min - - # Clamp the values to ensure they are within [0.0, 1.0] - scaled_mask = torch.clamp(scaled_mask, min=0.0, max=1.0) - - return (scaled_mask, ) - class LoadResAdapterNormalization: @classmethod def INPUT_TYPES(s): @@ -2553,7 +1404,6 @@ https://huggingface.co/roborovski/superprompt-v1 return (out, ) - class CameraPoseVisualizer: @classmethod From 811baef7991880bb7c62feccc55846b2eea1cbf3 Mon Sep 17 00:00:00 2001 From: Kijai <40791699+kijai@users.noreply.github.com> Date: Fri, 3 May 2024 13:15:35 +0300 Subject: [PATCH 77/95] fix --- __init__.py | 1 + nodes/mask_nodes.py | 9 +++++---- web/js/jsnodes.js | 21 +++++++++++++++++++++ 3 files changed, 27 insertions(+), 4 deletions(-) diff --git a/__init__.py b/__init__.py index adcf11e..b568427 100644 --- a/__init__.py +++ b/__init__.py @@ -40,6 +40,7 @@ NODE_CONFIG = { #images "ColorMatch": {"class": ColorMatch, "name": "Color Match"}, "AddLabel": {"class": AddLabel, "name": "Add Label"}, + "ImageAndMaskPreview": {"class": ImageAndMaskPreview}, "ImageBatchMulti": {"class": ImageBatchMulti, "name": "Image Batch Multi"}, "ImageBatchTestPattern": {"class": ImageBatchTestPattern, "name": "Image Batch Test Pattern"}, "ImageConcanate": {"class": ImageConcanate, "name": "Image Concanate"}, diff --git a/nodes/mask_nodes.py b/nodes/mask_nodes.py index 9ea1f65..6889279 100644 --- a/nodes/mask_nodes.py +++ b/nodes/mask_nodes.py @@ -830,8 +830,8 @@ class GetMaskSize: "mask": ("MASK",), }} - RETURN_TYPES = ("MASK","INT", "INT", ) - RETURN_NAMES = ("mask", "width", "height",) + RETURN_TYPES = ("MASK","INT", "INT", "INT",) + RETURN_NAMES = ("mask", "width", "height", "count",) FUNCTION = "getsize" CATEGORY = "KJNodes/masking" DESCRIPTION = """ @@ -843,9 +843,10 @@ and passes through the mask unchanged. def getsize(self, mask): width = mask.shape[2] height = mask.shape[1] + count = mask.shape[0] return {"ui": { - "text": [f"{width}x{height}"]}, - "result": (mask, width, height) + "text": [f"{count}x{width}x{height}"]}, + "result": (mask, width, height, count) } class GrowMaskWithBlur: diff --git a/web/js/jsnodes.js b/web/js/jsnodes.js index 9a9483e..da34cd3 100644 --- a/web/js/jsnodes.js +++ b/web/js/jsnodes.js @@ -73,6 +73,27 @@ app.registerExtension({ }); } break; + + case "GetMaskSize": + const onConnectInput = nodeType.prototype.onConnectInput; + nodeType.prototype.onConnectInput = function (targetSlot, type, output, originNode, originSlot) { + const v = onConnectInput?.(this, arguments); + targetSlot.outputs[1]["name"] = "width" + targetSlot.outputs[2]["name"] = "height" + targetSlot.outputs[3]["name"] = "count" + return v; + } + const onExecuted = nodeType.prototype.onExecuted; + nodeType.prototype.onExecuted = function(message) { + const r = onExecuted? onExecuted.apply(this,arguments): undefined + let values = message["text"].toString().split('x').map(Number); + this.outputs[1]["name"] = values[1] + " width" + this.outputs[2]["name"] = values[2] + " height" + this.outputs[3]["name"] = values[0] + " count" + return r + } + break; + case "JoinStringMulti": nodeType.prototype.onNodeCreated = function () { this._type = "STRING" From 4d25b205b5a589207ad6e7ce384c3dd91f2f7afb Mon Sep 17 00:00:00 2001 From: Kijai <40791699+kijai@users.noreply.github.com> Date: Fri, 3 May 2024 13:50:47 +0300 Subject: [PATCH 78/95] Rename GetMaskSize to GetMaskSizeAndCount and add image equivalent --- __init__.py | 5 +++-- nodes/image_nodes.py | 30 ++++++++++++++++++++++++++++-- nodes/mask_nodes.py | 4 ++-- web/js/jsnodes.js | 30 +++++++++++++++++++++++++----- 4 files changed, 58 insertions(+), 11 deletions(-) diff --git a/__init__.py b/__init__.py index b568427..d79bb68 100644 --- a/__init__.py +++ b/__init__.py @@ -30,7 +30,7 @@ NODE_CONFIG = { "CreateShapeMask": {"class": CreateShapeMask, "name": "Create Shape Mask"}, "CreateVoronoiMask": {"class": CreateVoronoiMask, "name": "Create Voronoi Mask"}, "CreateMagicMask": {"class": CreateMagicMask, "name": "Create Magic Mask"}, - "GetMaskSize": {"class": GetMaskSize, "name": "Get Mask Size"}, + "GetMaskSizeAndCount": {"class": GetMaskSizeAndCount, "name": "Get Mask Size & Count"}, "GrowMaskWithBlur": {"class": GrowMaskWithBlur, "name": "Grow Mask With Blur"}, "MaskBatchMulti": {"class": MaskBatchMulti, "name": "Mask Batch Multi"}, "OffsetMask": {"class": OffsetMask, "name": "Offset Mask"}, @@ -38,8 +38,9 @@ NODE_CONFIG = { "ResizeMask": {"class": ResizeMask, "name": "Resize Mask"}, "RoundMask": {"class": RoundMask, "name": "Round Mask"}, #images - "ColorMatch": {"class": ColorMatch, "name": "Color Match"}, "AddLabel": {"class": AddLabel, "name": "Add Label"}, + "ColorMatch": {"class": ColorMatch, "name": "Color Match"}, + "GetImageSizeAndCount": {"class": GetImageSizeAndCount, "name": "Get Image Size & Count"}, "ImageAndMaskPreview": {"class": ImageAndMaskPreview}, "ImageBatchMulti": {"class": ImageBatchMulti, "name": "Image Batch Multi"}, "ImageBatchTestPattern": {"class": ImageBatchTestPattern, "name": "Image Batch Test Pattern"}, diff --git a/nodes/image_nodes.py b/nodes/image_nodes.py index 96db37a..50648bf 100644 --- a/nodes/image_nodes.py +++ b/nodes/image_nodes.py @@ -28,7 +28,7 @@ class ImagePass: } RETURN_TYPES = ("IMAGE",) FUNCTION = "passthrough" - CATEGORY = "KJNodes/misc" + CATEGORY = "KJNodes/image" DESCRIPTION = """ Passes the image through without modifying it. """ @@ -455,7 +455,33 @@ ComfyUI/custom_nodes/ComfyUI-KJNodes/fonts combined_images = processed_batch return (combined_images,) + +class GetImageSizeAndCount: + @classmethod + def INPUT_TYPES(s): + return {"required": { + "image": ("IMAGE",), + }} + RETURN_TYPES = ("IMAGE","INT", "INT", "INT",) + RETURN_NAMES = ("image", "width", "height", "count",) + FUNCTION = "getsize" + CATEGORY = "KJNodes/masking" + DESCRIPTION = """ +Returns the width and height of the image, +and passes it through unchanged. + +""" + + def getsize(self, image): + width = image.shape[2] + height = image.shape[1] + count = image.shape[0] + return {"ui": { + "text": [f"{count}x{width}x{height}"]}, + "result": (image, width, height, count) + } + class ImageBatchRepeatInterleaving: RETURN_TYPES = ("IMAGE",) @@ -527,7 +553,7 @@ class ImageNormalize_Neg1_To_1: }} RETURN_TYPES = ("IMAGE",) FUNCTION = "normalize" - CATEGORY = "KJNodes/misc" + CATEGORY = "KJNodes/image" DESCRIPTION = """ Normalize the images to be in the range [-1, 1] """ diff --git a/nodes/mask_nodes.py b/nodes/mask_nodes.py index 6889279..a4cc38f 100644 --- a/nodes/mask_nodes.py +++ b/nodes/mask_nodes.py @@ -823,7 +823,7 @@ class CreateVoronoiMask: return (torch.stack(out, dim=0), 1.0 - torch.stack(out, dim=0),) -class GetMaskSize: +class GetMaskSizeAndCount: @classmethod def INPUT_TYPES(s): return {"required": { @@ -836,7 +836,7 @@ class GetMaskSize: CATEGORY = "KJNodes/masking" DESCRIPTION = """ Returns the width and height of the mask, -and passes through the mask unchanged. +and passes it through unchanged. """ diff --git a/web/js/jsnodes.js b/web/js/jsnodes.js index da34cd3..c09e893 100644 --- a/web/js/jsnodes.js +++ b/web/js/jsnodes.js @@ -74,18 +74,38 @@ app.registerExtension({ } break; - case "GetMaskSize": - const onConnectInput = nodeType.prototype.onConnectInput; + case "GetMaskSizeAndCount": + const onGetMaskSizeConnectInput = nodeType.prototype.onConnectInput; nodeType.prototype.onConnectInput = function (targetSlot, type, output, originNode, originSlot) { - const v = onConnectInput?.(this, arguments); + const v = onGetMaskSizeConnectInput?.(this, arguments); targetSlot.outputs[1]["name"] = "width" targetSlot.outputs[2]["name"] = "height" targetSlot.outputs[3]["name"] = "count" return v; } - const onExecuted = nodeType.prototype.onExecuted; + const onGetMaskSizeExecuted = nodeType.prototype.onExecuted; nodeType.prototype.onExecuted = function(message) { - const r = onExecuted? onExecuted.apply(this,arguments): undefined + const r = onGetMaskSizeExecuted? onGetMaskSizeExecuted.apply(this,arguments): undefined + let values = message["text"].toString().split('x').map(Number); + this.outputs[1]["name"] = values[1] + " width" + this.outputs[2]["name"] = values[2] + " height" + this.outputs[3]["name"] = values[0] + " count" + return r + } + break; + + case "GetImageSizeAndCount": + const onGetImageSizeConnectInput = nodeType.prototype.onConnectInput; + nodeType.prototype.onConnectInput = function (targetSlot, type, output, originNode, originSlot) { + const v = onGetImageSizeConnectInput?.(this, arguments); + targetSlot.outputs[1]["name"] = "width" + targetSlot.outputs[2]["name"] = "height" + targetSlot.outputs[3]["name"] = "count" + return v; + } + const onGetImageSizeExecuted = nodeType.prototype.onExecuted; + nodeType.prototype.onExecuted = function(message) { + const r = onGetImageSizeExecuted? onGetImageSizeExecuted.apply(this,arguments): undefined let values = message["text"].toString().split('x').map(Number); this.outputs[1]["name"] = values[1] + " width" this.outputs[2]["name"] = values[2] + " height" From b8480611046944d2c3600f2da19659bdc9bb52f0 Mon Sep 17 00:00:00 2001 From: Kijai <40791699+kijai@users.noreply.github.com> Date: Fri, 3 May 2024 13:52:31 +0300 Subject: [PATCH 79/95] description update --- nodes/image_nodes.py | 2 +- nodes/mask_nodes.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/nodes/image_nodes.py b/nodes/image_nodes.py index 50648bf..898673d 100644 --- a/nodes/image_nodes.py +++ b/nodes/image_nodes.py @@ -468,7 +468,7 @@ class GetImageSizeAndCount: FUNCTION = "getsize" CATEGORY = "KJNodes/masking" DESCRIPTION = """ -Returns the width and height of the image, +Returns width, height and batch size of the image, and passes it through unchanged. """ diff --git a/nodes/mask_nodes.py b/nodes/mask_nodes.py index a4cc38f..8b1d9f0 100644 --- a/nodes/mask_nodes.py +++ b/nodes/mask_nodes.py @@ -835,7 +835,7 @@ class GetMaskSizeAndCount: FUNCTION = "getsize" CATEGORY = "KJNodes/masking" DESCRIPTION = """ -Returns the width and height of the mask, +Returns the width, height and batch size of the mask, and passes it through unchanged. """ From 8436ac816629fe8a9221e58e0c070e8f768acc1c Mon Sep 17 00:00:00 2001 From: Kijai <40791699+kijai@users.noreply.github.com> Date: Fri, 3 May 2024 14:52:10 +0300 Subject: [PATCH 80/95] Add NormalizedAmplitudeToFloatList --- __init__.py | 1 + nodes/audioscheduler_nodes.py | 21 +++++++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/__init__.py b/__init__.py index d79bb68..095abd6 100644 --- a/__init__.py +++ b/__init__.py @@ -89,6 +89,7 @@ NODE_CONFIG = { "EmptyLatentImagePresets": {"class": EmptyLatentImagePresets, "name": "Empty Latent Image Presets"}, #audioscheduler stuff "NormalizedAmplitudeToMask": {"class": NormalizedAmplitudeToMask}, + "NormalizedAmplitudeToFloatList": {"class": NormalizedAmplitudeToFloatList}, "OffsetMaskByNormalizedAmplitude": {"class": OffsetMaskByNormalizedAmplitude}, "ImageTransformByNormalizedAmplitude": {"class": ImageTransformByNormalizedAmplitude}, #curve nodes diff --git a/nodes/audioscheduler_nodes.py b/nodes/audioscheduler_nodes.py index 030ead8..69d0422 100644 --- a/nodes/audioscheduler_nodes.py +++ b/nodes/audioscheduler_nodes.py @@ -95,6 +95,27 @@ Creates masks based on the normalized amplitude. out.append(mask) return (torch.cat(out, dim=0),) + +class NormalizedAmplitudeToFloatList: + @classmethod + def INPUT_TYPES(s): + return {"required": { + "normalized_amp": ("NORMALIZED_AMPLITUDE",), + },} + + CATEGORY = "KJNodes/audio" + RETURN_TYPES = ("FLOAT",) + FUNCTION = "convert" + DESCRIPTION = """ +Works as a bridge to the AudioScheduler -nodes: +https://github.com/a1lazydog/ComfyUI-AudioScheduler +Creates a list of floats from the normalized amplitude. +""" + + def convert(self, normalized_amp): + # Ensure normalized_amp is an array and within the range [0, 1] + normalized_amp = np.clip(normalized_amp, 0.0, 1.0) + return (normalized_amp.tolist(),) class OffsetMaskByNormalizedAmplitude: @classmethod From 3e84ca7d96c67b2a42ef2dd16a920602fbb198ab Mon Sep 17 00:00:00 2001 From: Kijai <40791699+kijai@users.noreply.github.com> Date: Fri, 3 May 2024 15:06:13 +0300 Subject: [PATCH 81/95] Add value remap option to WeightScheduleConvert --- nodes/curve_nodes.py | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/nodes/curve_nodes.py b/nodes/curve_nodes.py index c48e672..a22924c 100644 --- a/nodes/curve_nodes.py +++ b/nodes/curve_nodes.py @@ -287,6 +287,9 @@ class WeightScheduleConvert: "optional": { "remap_to_frames": ("INT", {"default": 0}), "interpolation_curve": ("FLOAT", {"forceInput": True}), + "remap_values": ("BOOLEAN", {"default": False}), + "remap_min": ("FLOAT", {"default": 0.0, "min": -100000, "max": 100000.0, "step": 0.01}), + "remap_max": ("FLOAT", {"default": 1.0, "min": -100000, "max": 100000.0, "step": 0.01}), }, } @@ -308,7 +311,7 @@ Converts different value lists/series to another type. else: raise ValueError("Unsupported input type") - def execute(self, input_values, output_type, invert, repeat, remap_to_frames=0, interpolation_curve=None): + def execute(self, input_values, output_type, invert, repeat, remap_to_frames=0, interpolation_curve=None, remap_min=0.0, remap_max=1.0, remap_values=False): import pandas as pd input_type = self.detect_input_type(input_values) @@ -345,6 +348,8 @@ Converts different value lists/series to another type. float_values = np.interp(np.linspace(0, 1, remap_to_frames), np.linspace(0, 1, len(normalized_values)), normalized_values).tolist() float_values = float_values * repeat + if remap_values: + float_values = self.remap_values(float_values, remap_min, remap_max) if output_type == 'list': out = float_values, @@ -358,6 +363,20 @@ Converts different value lists/series to another type. elif output_type == 'match_input': out = float_values, return (out, [str(value) for value in float_values], [int(value) for value in float_values]) + + def remap_values(self, values, target_min, target_max): + # Determine the current range + current_min = min(values) + current_max = max(values) + current_range = current_max - current_min + + # Determine the target range + target_range = target_max - target_min + + # Perform the linear interpolation for each value + remapped_values = [(value - current_min) / current_range * target_range + target_min for value in values] + + return remapped_values class FloatToMask: From 64352cac11aed2c98a55b9d848b35bde8d5cfb6b Mon Sep 17 00:00:00 2001 From: Kijai <40791699+kijai@users.noreply.github.com> Date: Fri, 3 May 2024 17:22:00 +0300 Subject: [PATCH 82/95] Initial InstanceDiffusion coordinate creator node --- __init__.py | 2 + nodes/curve_nodes.py | 211 ++++++++++++++++++++++++++++------------ web/js/spline_editor.js | 1 + 3 files changed, 153 insertions(+), 61 deletions(-) diff --git a/__init__.py b/__init__.py index 095abd6..3d31d1c 100644 --- a/__init__.py +++ b/__init__.py @@ -100,6 +100,7 @@ NODE_CONFIG = { "WeightScheduleConvert": {"class": WeightScheduleConvert, "name": "Weight Schedule Convert"}, "FloatToMask": {"class": FloatToMask, "name": "Float To Mask"}, "FloatToSigmas": {"class": FloatToSigmas, "name": "Float To Sigmas"}, + "PlotCoordinates": {"class": PlotCoordinates, "name": "Plot Coordinates"}, #experimental "StabilityAPI_SD3": {"class": StabilityAPI_SD3, "name": "Stability API SD3"}, "SoundReactive": {"class": SoundReactive, "name": "Sound Reactive"}, @@ -109,6 +110,7 @@ NODE_CONFIG = { "Superprompt": {"class": Superprompt, "name": "Superprompt"}, "GLIGENTextBoxApplyBatchCoords": {"class": GLIGENTextBoxApplyBatchCoords}, "Intrinsic_lora_sampling": {"class": Intrinsic_lora_sampling, "name": "Intrinsic Lora Sampling"}, + "CreateInstanceDiffusionTracking": {"class": CreateInstanceDiffusionTracking}, } def generate_node_mappings(node_config): diff --git a/nodes/curve_nodes.py b/nodes/curve_nodes.py index a22924c..2f52540 100644 --- a/nodes/curve_nodes.py +++ b/nodes/curve_nodes.py @@ -4,6 +4,98 @@ from PIL import Image, ImageDraw import numpy as np from ..utility.utility import pil2tensor +def plot_coordinates_to_tensor(coordinates, height, width, bbox_height, bbox_width, size_multiplier, prompt): + import matplotlib + matplotlib.use('Agg') + from matplotlib.backends.backend_agg import FigureCanvasAgg as FigureCanvas + text_color = '#999999' + bg_color = '#353535' + matplotlib.pyplot.rcParams['text.color'] = text_color + fig, ax = matplotlib.pyplot.subplots(figsize=(width/100, height/100), dpi=100) + fig.patch.set_facecolor(bg_color) + ax.set_facecolor(bg_color) + ax.grid(color=text_color, linestyle='-', linewidth=0.5) + ax.set_xlabel('x', color=text_color) + ax.set_ylabel('y', color=text_color) + for text in ax.get_xticklabels() + ax.get_yticklabels(): + text.set_color(text_color) + ax.set_title('position for: ' + prompt) + ax.set_xlabel('X Coordinate') + ax.set_ylabel('Y Coordinate') + #ax.legend().remove() + ax.set_xlim(0, width) # Set the x-axis to match the input latent width + ax.set_ylim(height, 0) # Set the y-axis to match the input latent height, with (0,0) at top-left + # Adjust the margins of the subplot + matplotlib.pyplot.subplots_adjust(left=0.08, right=0.95, bottom=0.05, top=0.95, wspace=0.2, hspace=0.2) + + cmap = matplotlib.pyplot.get_cmap('rainbow') + image_batch = [] + canvas = FigureCanvas(fig) + width, height = fig.get_size_inches() * fig.get_dpi() + # Draw a box at each coordinate + for i, ((x, y), size) in enumerate(zip(coordinates, size_multiplier)): + color_index = i / (len(coordinates) - 1) + color = cmap(color_index) + draw_height = bbox_height * size + draw_width = bbox_width * size + rect = matplotlib.patches.Rectangle((x - draw_width/2, y - draw_height/2), draw_width, draw_height, + linewidth=1, edgecolor=color, facecolor='none', alpha=0.5) + ax.add_patch(rect) + + # Check if there is a next coordinate to draw an arrow to + if i < len(coordinates) - 1: + x1, y1 = coordinates[i] + x2, y2 = coordinates[i + 1] + ax.annotate("", xy=(x2, y2), xytext=(x1, y1), + arrowprops=dict(arrowstyle="->", + linestyle="-", + lw=1, + color=color, + mutation_scale=20)) + canvas.draw() + image_np = np.frombuffer(canvas.tostring_rgb(), dtype='uint8').reshape(int(height), int(width), 3).copy() + image_tensor = torch.from_numpy(image_np).float() / 255.0 + image_tensor = image_tensor.unsqueeze(0) + image_batch.append(image_tensor) + + matplotlib.pyplot.close(fig) + image_batch_tensor = torch.cat(image_batch, dim=0) + + return image_batch_tensor + +class PlotCoordinates: + @classmethod + def INPUT_TYPES(s): + return {"required": { + "coordinates": ("STRING", {"forceInput": True}), + "text": ("STRING", {"default": 'title', "multiline": False}), + "width": ("INT", {"default": 512, "min": 8, "max": 4096, "step": 8}), + "height": ("INT", {"default": 512, "min": 8, "max": 4096, "step": 8}), + "bbox_width": ("INT", {"default": 128, "min": 8, "max": 4096, "step": 8}), + "bbox_height": ("INT", {"default": 128, "min": 8, "max": 4096, "step": 8}), + }, + "optional": {"size_multiplier": ("FLOAT", {"default": [1.0], "forceInput": True})}, + } + RETURN_TYPES = ("IMAGE", ) + RETURN_NAMES = ("images", ) + FUNCTION = "append" + CATEGORY = "KJNodes/experimental" + DESCRIPTION = """ +Plots coordinates to sequence of images using Matplotlib. + +""" + + def append(self, coordinates, text, width, height, bbox_width, bbox_height, size_multiplier=[1.0]): + coordinates = json.loads(coordinates.replace("'", '"')) + coordinates = [(coord['x'], coord['y']) for coord in coordinates] + batch_size = len(coordinates) + if len(size_multiplier) != batch_size: + size_multiplier = size_multiplier * (batch_size // len(size_multiplier)) + size_multiplier[:batch_size % len(size_multiplier)] + + plot_image_tensor = plot_coordinates_to_tensor(coordinates, height, width, bbox_height, bbox_width, size_multiplier, text) + + return (plot_image_tensor,) + class SplineEditor: @classmethod @@ -58,7 +150,7 @@ class SplineEditor: RETURN_TYPES = ("MASK", "STRING", "FLOAT", "INT") RETURN_NAMES = ("mask", "coord_str", "float", "count") FUNCTION = "splinedata" - CATEGORY = "KJNodes/experimental" + CATEGORY = "KJNodes/weights" DESCRIPTION = """ # WORK IN PROGRESS Do not count on this as part of your workflow yet, @@ -234,7 +326,7 @@ class MaskOrImageToWeight: } RETURN_TYPES = ("FLOAT", "STRING",) FUNCTION = "execute" - CATEGORY = "KJNodes" + CATEGORY = "KJNodes/weights" DESCRIPTION = """ Gets the mean values from mask or image batch and returns that as the selected output type. @@ -295,7 +387,7 @@ class WeightScheduleConvert: } RETURN_TYPES = ("FLOAT", "STRING", "INT",) FUNCTION = "execute" - CATEGORY = "KJNodes" + CATEGORY = "KJNodes/weights" DESCRIPTION = """ Converts different value lists/series to another type. """ @@ -392,7 +484,7 @@ class FloatToMask: } RETURN_TYPES = ("MASK",) FUNCTION = "execute" - CATEGORY = "KJNodes" + CATEGORY = "KJNodes/masking/generate" DESCRIPTION = """ Generates a batch of masks based on the input float values. The batch size is determined by the length of the input float values. @@ -441,7 +533,7 @@ class WeightScheduleExtend: } RETURN_TYPES = ("FLOAT",) FUNCTION = "execute" - CATEGORY = "KJNodes" + CATEGORY = "KJNodes/weights" DESCRIPTION = """ Extends, and converts if needed, different value lists/series """ @@ -589,65 +681,62 @@ bounding boxes. image_height = latents['samples'].shape[-2] * 8 image_width = latents['samples'].shape[-1] * 8 - plot_image_tensor = self.plot_coordinates_to_tensor(coordinates, image_height, image_width, height, width, size_multiplier, text) + plot_image_tensor = plot_coordinates_to_tensor(coordinates, image_height, image_width, height, width, size_multiplier, text) return (c, plot_image_tensor,) - def plot_coordinates_to_tensor(self, coordinates, height, width, bbox_height, bbox_width, size_multiplier, prompt): - import matplotlib - matplotlib.use('Agg') - from matplotlib.backends.backend_agg import FigureCanvasAgg as FigureCanvas - text_color = '#999999' - bg_color = '#353535' - matplotlib.pyplot.rcParams['text.color'] = text_color - fig, ax = matplotlib.pyplot.subplots(figsize=(width/100, height/100), dpi=100) - fig.patch.set_facecolor(bg_color) - ax.set_facecolor(bg_color) - ax.grid(color=text_color, linestyle='-', linewidth=0.5) - ax.set_xlabel('x', color=text_color) - ax.set_ylabel('y', color=text_color) - for text in ax.get_xticklabels() + ax.get_yticklabels(): - text.set_color(text_color) - ax.set_title('Gligen pos for: ' + prompt) - ax.set_xlabel('X Coordinate') - ax.set_ylabel('Y Coordinate') - #ax.legend().remove() - ax.set_xlim(0, width) # Set the x-axis to match the input latent width - ax.set_ylim(height, 0) # Set the y-axis to match the input latent height, with (0,0) at top-left - # Adjust the margins of the subplot - matplotlib.pyplot.subplots_adjust(left=0.08, right=0.95, bottom=0.05, top=0.95, wspace=0.2, hspace=0.2) +class CreateInstanceDiffusionTracking: + + RETURN_TYPES = ("TRACKING",) + RETURN_NAMES = ("TRACKING",) + FUNCTION = "tracking" + CATEGORY = "KJNodes/experimental" + DESCRIPTION = """ +Creates tracking data to be used with InstanceDiffusion: +https://github.com/logtd/ComfyUI-InstanceDiffusion + +InstanceDiffusion prompt format: +"class_id.class_name": "prompt", +for example: +"1.head": "((head))", +""" - cmap = matplotlib.pyplot.get_cmap('rainbow') - image_batch = [] - canvas = FigureCanvas(fig) - width, height = fig.get_size_inches() * fig.get_dpi() - # Draw a box at each coordinate - for i, ((x, y), size) in enumerate(zip(coordinates, size_multiplier)): - color_index = i / (len(coordinates) - 1) - color = cmap(color_index) - draw_height = bbox_height * size - draw_width = bbox_width * size - rect = matplotlib.patches.Rectangle((x - draw_width/2, y - draw_height/2), draw_width, draw_height, - linewidth=1, edgecolor=color, facecolor='none', alpha=0.5) - ax.add_patch(rect) + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "coordinates": ("STRING", {"forceInput": True}), + "width": ("INT", {"default": 512,"min": 16, "max": 4096, "step": 1}), + "height": ("INT", {"default": 512,"min": 16, "max": 4096, "step": 1}), + "bbox_width": ("INT", {"default": 512,"min": 16, "max": 4096, "step": 1}), + "bbox_height": ("INT", {"default": 512,"min": 16, "max": 4096, "step": 1}), - # Check if there is a next coordinate to draw an arrow to - if i < len(coordinates) - 1: - x1, y1 = coordinates[i] - x2, y2 = coordinates[i + 1] - ax.annotate("", xy=(x2, y2), xytext=(x1, y1), - arrowprops=dict(arrowstyle="->", - linestyle="-", - lw=1, - color=color, - mutation_scale=20)) - canvas.draw() - image_np = np.frombuffer(canvas.tostring_rgb(), dtype='uint8').reshape(int(height), int(width), 3).copy() - image_tensor = torch.from_numpy(image_np).float() / 255.0 - image_tensor = image_tensor.unsqueeze(0) - image_batch.append(image_tensor) - - matplotlib.pyplot.close(fig) - image_batch_tensor = torch.cat(image_batch, dim=0) + "class_name": ("STRING", {"default": "class_id"}), + "class_id": ("INT", {"default": 0,"min": 0, "max": 255, "step": 1}), + }, + "optional": { + "size_multiplier": ("FLOAT", {"default": [1.0], "forceInput": True}), + } + } - return image_batch_tensor \ No newline at end of file + def tracking(self, coordinates, class_name, class_id, width, height, bbox_width, bbox_height, size_multiplier=[1.0]): + # Define the number of images in the batch + coordinates = coordinates.replace("'", '"') + coordinates = json.loads(coordinates) + + tracked = {} + tracked[class_name] = {} + + # Initialize a list to hold the coordinates for the current ID + id_coordinates = [] + + for detection in coordinates: + # Append the 'x' and 'y' coordinates along with bbox width/height and frame width/height to the list for the current ID + id_coordinates.append([detection['x'], detection['y'], bbox_width, bbox_height, width, height]) + + # Assign the list of coordinates to the specified ID within the class_id dictionary + tracked[class_name][class_id] = id_coordinates + + + print(tracked) + return (tracked, ) \ No newline at end of file diff --git a/web/js/spline_editor.js b/web/js/spline_editor.js index d39a43c..57cdac3 100644 --- a/web/js/spline_editor.js +++ b/web/js/spline_editor.js @@ -396,6 +396,7 @@ function createSplineEditor(context, reset=false) { y: this.mouse().y / app.canvas.ds.scale }; i = points.push(scaledMouse) - 1; + updatePath(); return this; } else if (pv.event.ctrlKey) { From 0c1aacac97d0a35b85d87c858eb27a7ddcbbc455 Mon Sep 17 00:00:00 2001 From: kijai <40791699+kijai@users.noreply.github.com> Date: Fri, 3 May 2024 22:30:50 +0300 Subject: [PATCH 83/95] Add InterpolateCoords -node --- __init__.py | 1 + nodes/curve_nodes.py | 67 +++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 67 insertions(+), 1 deletion(-) diff --git a/__init__.py b/__init__.py index 3d31d1c..c39eeb4 100644 --- a/__init__.py +++ b/__init__.py @@ -101,6 +101,7 @@ NODE_CONFIG = { "FloatToMask": {"class": FloatToMask, "name": "Float To Mask"}, "FloatToSigmas": {"class": FloatToSigmas, "name": "Float To Sigmas"}, "PlotCoordinates": {"class": PlotCoordinates, "name": "Plot Coordinates"}, + "InterpolateCoords": {"class": InterpolateCoords, "name": "Interpolate Coords"}, #experimental "StabilityAPI_SD3": {"class": StabilityAPI_SD3, "name": "Stability API SD3"}, "SoundReactive": {"class": SoundReactive, "name": "Sound Reactive"}, diff --git a/nodes/curve_nodes.py b/nodes/curve_nodes.py index 2f52540..4eee557 100644 --- a/nodes/curve_nodes.py +++ b/nodes/curve_nodes.py @@ -739,4 +739,69 @@ for example: print(tracked) - return (tracked, ) \ No newline at end of file + return (tracked, ) + +class InterpolateCoords: + + RETURN_TYPES = ("STRING",) + RETURN_NAMES = ("coordinates",) + FUNCTION = "interpolate" + CATEGORY = "KJNodes/experimental" + DESCRIPTION = """ +Interpolates coordinates based on a curve. +""" + + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "coordinates": ("STRING", {"forceInput": True}), + "interpolation_curve": ("FLOAT", {"forceInput": True}), + + }, + } + + def interpolate(self, coordinates, interpolation_curve): + # Parse the JSON string to get the list of coordinates + coordinates = json.loads(coordinates.replace("'", '"')) + + # Convert the list of dictionaries to a list of (x, y) tuples for easier processing + coordinates = [(coord['x'], coord['y']) for coord in coordinates] + + # Calculate the total length of the original path + path_length = sum(np.linalg.norm(np.array(coordinates[i]) - np.array(coordinates[i-1])) for i in range(1, len(coordinates))) + + # Normalize the interpolation curve + normalized_curve = [x / path_length for x in interpolation_curve] + + # Initialize variables for interpolation + interpolated_coords = [] + current_length = 0 + current_index = 1 + + # Iterate over the normalized curve + for target_length in normalized_curve: + target_length *= path_length # Convert back to the original scale + while current_length < target_length and current_index < len(coordinates): + segment_length = np.linalg.norm(np.array(coordinates[current_index]) - np.array(coordinates[current_index-1])) + current_length += segment_length + current_index += 1 + + # Interpolate between the last two points + if current_index == 1: + interpolated_coords.append(coordinates[0]) + else: + p1, p2 = np.array(coordinates[current_index-2]), np.array(coordinates[current_index-1]) + segment_length = np.linalg.norm(p2 - p1) + if segment_length > 0: + t = (target_length - (current_length - segment_length)) / segment_length + interpolated_point = p1 + t * (p2 - p1) + interpolated_coords.append(interpolated_point.tolist()) + else: + interpolated_coords.append(p1.tolist()) + + # Convert back to string format if necessary + interpolated_coords_str = "[" + ", ".join([f"{{'x': {round(coord[0])}, 'y': {round(coord[1])}}}" for coord in interpolated_coords]) + "]" + + return (interpolated_coords_str, ) + From 11a62b6fc3eb290495576e35b51cd836b291f782 Mon Sep 17 00:00:00 2001 From: kijai <40791699+kijai@users.noreply.github.com> Date: Sat, 4 May 2024 09:46:05 +0300 Subject: [PATCH 84/95] Restructuring fixes --- __init__.py | 12 ++-- nodes/batchcrop_nodes.py | 128 ++++++++++++++++++++++++++++----------- nodes/nodes.py | 76 ----------------------- 3 files changed, 101 insertions(+), 115 deletions(-) diff --git a/__init__.py b/__init__.py index c39eeb4..811716c 100644 --- a/__init__.py +++ b/__init__.py @@ -40,11 +40,14 @@ NODE_CONFIG = { #images "AddLabel": {"class": AddLabel, "name": "Add Label"}, "ColorMatch": {"class": ColorMatch, "name": "Color Match"}, + "CrossFadeImages": {"class": CrossFadeImages, "name": "Cross Fade Images"}, + "GetImageRangeFromBatch": {"class": GetImageRangeFromBatch, "name": "Get Image Range From Batch"}, "GetImageSizeAndCount": {"class": GetImageSizeAndCount, "name": "Get Image Size & Count"}, "ImageAndMaskPreview": {"class": ImageAndMaskPreview}, "ImageBatchMulti": {"class": ImageBatchMulti, "name": "Image Batch Multi"}, + "ImageBatchRepeatInterleaving": {"class": ImageBatchRepeatInterleaving}, "ImageBatchTestPattern": {"class": ImageBatchTestPattern, "name": "Image Batch Test Pattern"}, - "ImageConcanate": {"class": ImageConcanate, "name": "Image Concanate"}, + "ImageConcanate": {"class": ImageConcanate, "name": "Image Concatenate"}, "ImageGrabPIL": {"class": ImageGrabPIL, "name": "Image Grab PIL"}, "ImageGridComposite2x2": {"class": ImageGridComposite2x2, "name": "Image Grid Composite 2x2"}, "ImageGridComposite3x3": {"class": ImageGridComposite3x3, "name": "Image Grid Composite 3x3"}, @@ -54,12 +57,11 @@ NODE_CONFIG = { "ImageUpscaleWithModelBatched": {"class": ImageUpscaleWithModelBatched, "name": "Image Upscale With Model Batched"}, "InsertImagesToBatchIndexed": {"class": InsertImagesToBatchIndexed, "name": "Insert Images To Batch Indexed"}, "MergeImageChannels": {"class": MergeImageChannels, "name": "Merge Image Channels"}, - "ReverseImageBatch": {"class": ReverseImageBatch, "name": "Reverse Image Batch"}, "RemapImageRange": {"class": RemapImageRange, "name": "Remap Image Range"}, + "ReverseImageBatch": {"class": ReverseImageBatch, "name": "Reverse Image Batch"}, + "ReplaceImagesInBatch": {"class": ReplaceImagesInBatch, "name": "Replace Images In Batch"}, "SaveImageWithAlpha": {"class": SaveImageWithAlpha, "name": "Save Image With Alpha"}, - "SplitImageChannels": {"class": SplitImageChannels, "name": "Split Image Channels"}, - "CrossFadeImages": {"class": CrossFadeImages, "name": "Cross Fade Images"}, - "GetImageRangeFromBatch": {"class": GetImageRangeFromBatch, "name": "Get Image Range From Batch"}, + "SplitImageChannels": {"class": SplitImageChannels, "name": "Split Image Channels"}, #batch cropping "BatchCropFromMask": {"class": BatchCropFromMask, "name": "Batch Crop From Mask"}, "BatchCropFromMaskAdvanced": {"class": BatchCropFromMaskAdvanced, "name": "Batch Crop From Mask Advanced"}, diff --git a/nodes/batchcrop_nodes.py b/nodes/batchcrop_nodes.py index 20671bb..282dd93 100644 --- a/nodes/batchcrop_nodes.py +++ b/nodes/batchcrop_nodes.py @@ -6,6 +6,23 @@ from torchvision.transforms import Resize, CenterCrop, InterpolationMode import math #based on nodes from mtb https://github.com/melMass/comfy_mtb + +def bbox_to_region(bbox, target_size=None): + bbox = bbox_check(bbox, target_size) + return (bbox[0], bbox[1], bbox[0] + bbox[2], bbox[1] + bbox[3]) + +def bbox_check(bbox, target_size=None): + if not target_size: + return bbox + + new_bbox = ( + bbox[0], + bbox[1], + min(target_size[0] - bbox[0], bbox[2]), + min(target_size[1] - bbox[1], bbox[3]), + ) + return new_bbox + class BatchCropFromMask: @classmethod @@ -136,23 +153,6 @@ class BatchCropFromMask: return (original_images, cropped_out, bounding_boxes, self.max_bbox_width, self.max_bbox_height, ) - -def bbox_to_region(bbox, target_size=None): - bbox = bbox_check(bbox, target_size) - return (bbox[0], bbox[1], bbox[0] + bbox[2], bbox[1] + bbox[3]) - -def bbox_check(bbox, target_size=None): - if not target_size: - return bbox - - new_bbox = ( - bbox[0], - bbox[1], - min(target_size[0] - bbox[0], bbox[2]), - min(target_size[1] - bbox[1], bbox[3]), - ) - return new_bbox - class BatchUncrop: @classmethod @@ -532,22 +532,6 @@ Returns: return (images_after_insert, ) -def bbox_to_region(bbox, target_size=None): - bbox = bbox_check(bbox, target_size) - return (bbox[0], bbox[1], bbox[0] + bbox[2], bbox[1] + bbox[3]) - -def bbox_check(bbox, target_size=None): - if not target_size: - return bbox - - new_bbox = ( - bbox[0], - bbox[1], - min(target_size[0] - bbox[0], bbox[2]), - min(target_size[1] - bbox[1], bbox[3]), - ) - return new_bbox - class BatchUncropAdvanced: @classmethod @@ -674,4 +658,80 @@ Splits the specified bbox list at the given index into two lists. bboxes_a = bboxes[:index] # Sub-list from the start of bboxes up to (but not including) the index bboxes_b = bboxes[index:] # Sub-list from the index to the end of bboxes - return (bboxes_a, bboxes_b,) \ No newline at end of file + return (bboxes_a, bboxes_b,) + +class BboxToInt: + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "bboxes": ("BBOX",), + "index": ("INT", {"default": 0,"min": 0, "max": 99999999, "step": 1}), + }, + } + + RETURN_TYPES = ("INT","INT","INT","INT","INT","INT",) + RETURN_NAMES = ("x_min","y_min","width","height", "center_x","center_y",) + FUNCTION = "bboxtoint" + CATEGORY = "KJNodes/masking" + DESCRIPTION = """ +Returns selected index from bounding box list as integers. +""" + def bboxtoint(self, bboxes, index): + x_min, y_min, width, height = bboxes[index] + center_x = int(x_min + width / 2) + center_y = int(y_min + height / 2) + + return (x_min, y_min, width, height, center_x, center_y,) + +class BboxVisualize: + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "images": ("IMAGE",), + "bboxes": ("BBOX",), + "line_width": ("INT", {"default": 1,"min": 1, "max": 10, "step": 1}), + }, + } + + RETURN_TYPES = ("IMAGE",) + RETURN_NAMES = ("images",) + FUNCTION = "visualizebbox" + DESCRIPTION = """ +Visualizes the specified bbox on the image. +""" + + CATEGORY = "KJNodes/masking" + + def visualizebbox(self, bboxes, images, line_width): + image_list = [] + for image, bbox in zip(images, bboxes): + x_min, y_min, width, height = bbox + image = image.permute(2, 0, 1) + + img_with_bbox = image.clone() + + # Define the color for the bbox, e.g., red + color = torch.tensor([1, 0, 0], dtype=torch.float32) + + # Draw lines for each side of the bbox with the specified line width + for lw in range(line_width): + # Top horizontal line + img_with_bbox[:, y_min + lw, x_min:x_min + width] = color[:, None] + + # Bottom horizontal line + img_with_bbox[:, y_min + height - lw, x_min:x_min + width] = color[:, None] + + # Left vertical line + img_with_bbox[:, y_min:y_min + height, x_min + lw] = color[:, None] + + # Right vertical line + img_with_bbox[:, y_min:y_min + height, x_min + width - lw] = color[:, None] + + img_with_bbox = img_with_bbox.permute(1, 2, 0).unsqueeze(0) + image_list.append(img_with_bbox) + + return (torch.cat(image_list, dim=0),) \ No newline at end of file diff --git a/nodes/nodes.py b/nodes/nodes.py index 2b60ed8..cfec561 100644 --- a/nodes/nodes.py +++ b/nodes/nodes.py @@ -730,82 +730,6 @@ To see node id's, enable node id display from Manager badge menu. raise NameError(f"Node not found: {id}") return (', '.join(results).strip(', '), ) -class BboxToInt: - - @classmethod - def INPUT_TYPES(cls): - return { - "required": { - "bboxes": ("BBOX",), - "index": ("INT", {"default": 0,"min": 0, "max": 99999999, "step": 1}), - }, - } - - RETURN_TYPES = ("INT","INT","INT","INT","INT","INT",) - RETURN_NAMES = ("x_min","y_min","width","height", "center_x","center_y",) - FUNCTION = "bboxtoint" - CATEGORY = "KJNodes/masking" - DESCRIPTION = """ -Returns selected index from bounding box list as integers. -""" - def bboxtoint(self, bboxes, index): - x_min, y_min, width, height = bboxes[index] - center_x = int(x_min + width / 2) - center_y = int(y_min + height / 2) - - return (x_min, y_min, width, height, center_x, center_y,) - -class BboxVisualize: - - @classmethod - def INPUT_TYPES(cls): - return { - "required": { - "images": ("IMAGE",), - "bboxes": ("BBOX",), - "line_width": ("INT", {"default": 1,"min": 1, "max": 10, "step": 1}), - }, - } - - RETURN_TYPES = ("IMAGE",) - RETURN_NAMES = ("images",) - FUNCTION = "visualizebbox" - DESCRIPTION = """ -Visualizes the specified bbox on the image. -""" - - CATEGORY = "KJNodes/masking" - - def visualizebbox(self, bboxes, images, line_width): - image_list = [] - for image, bbox in zip(images, bboxes): - x_min, y_min, width, height = bbox - image = image.permute(2, 0, 1) - - img_with_bbox = image.clone() - - # Define the color for the bbox, e.g., red - color = torch.tensor([1, 0, 0], dtype=torch.float32) - - # Draw lines for each side of the bbox with the specified line width - for lw in range(line_width): - # Top horizontal line - img_with_bbox[:, y_min + lw, x_min:x_min + width] = color[:, None] - - # Bottom horizontal line - img_with_bbox[:, y_min + height - lw, x_min:x_min + width] = color[:, None] - - # Left vertical line - img_with_bbox[:, y_min:y_min + height, x_min + lw] = color[:, None] - - # Right vertical line - img_with_bbox[:, y_min:y_min + height, x_min + width - lw] = color[:, None] - - img_with_bbox = img_with_bbox.permute(1, 2, 0).unsqueeze(0) - image_list.append(img_with_bbox) - - return (torch.cat(image_list, dim=0),) - class DummyLatentOut: @classmethod From a8c216c7e9c536f7daf3e13a01248a4d3b4911e4 Mon Sep 17 00:00:00 2001 From: kijai <40791699+kijai@users.noreply.github.com> Date: Sat, 4 May 2024 10:09:17 +0300 Subject: [PATCH 85/95] Make VRAM_Debug report the values within the node itself --- nodes/nodes.py | 13 ++++++++----- web/js/jsnodes.js | 17 +++++++++++++++++ 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/nodes/nodes.py b/nodes/nodes.py index cfec561..1472ec8 100644 --- a/nodes/nodes.py +++ b/nodes/nodes.py @@ -558,9 +558,9 @@ and performs comfy model management functions and garbage collection, reports free VRAM before and after the operations. """ - def VRAMdebug(self, gc_collect,empty_cache, unload_all_models, image_pass=None, model_pass=None, any_input=None): + def VRAMdebug(self, gc_collect, empty_cache, unload_all_models, image_pass=None, model_pass=None, any_input=None): freemem_before = model_management.get_free_memory() - print("VRAMdebug: free memory before: ", freemem_before) + print("VRAMdebug: free memory before: ", f"{freemem_before:,.0f}") if empty_cache: model_management.soft_empty_cache() if unload_all_models: @@ -569,9 +569,12 @@ reports free VRAM before and after the operations. import gc gc.collect() freemem_after = model_management.get_free_memory() - print("VRAMdebug: free memory after: ", freemem_after) - print("VRAMdebug: freed memory: ", freemem_after - freemem_before) - return (any_input, image_pass, model_pass, freemem_before, freemem_after) + print("VRAMdebug: free memory after: ", f"{freemem_after:,.0f}") + print("VRAMdebug: freed memory: ", f"{freemem_after - freemem_before:,.0f}") + return {"ui": { + "text": [f"{freemem_before:,.0f}x{freemem_after:,.0f}"]}, + "result": (any_input, image_pass, model_pass, freemem_before, freemem_after) + } class SomethingToString: @classmethod diff --git a/web/js/jsnodes.js b/web/js/jsnodes.js index c09e893..6131289 100644 --- a/web/js/jsnodes.js +++ b/web/js/jsnodes.js @@ -113,6 +113,23 @@ app.registerExtension({ return r } break; + case "VRAM_Debug": + const onVRAM_DebugConnectInput = nodeType.prototype.onConnectInput; + nodeType.prototype.onConnectInput = function (targetSlot, type, output, originNode, originSlot) { + const v = onVRAM_DebugConnectInput?.(this, arguments); + targetSlot.outputs[3]["name"] = "freemem_before" + targetSlot.outputs[4]["name"] = "freemem_after" + return v; + } + const onVRAM_DebugExecuted = nodeType.prototype.onExecuted; + nodeType.prototype.onExecuted = function(message) { + const r = onVRAM_DebugExecuted? onVRAM_DebugExecuted.apply(this,arguments): undefined + let values = message["text"].toString().split('x'); + this.outputs[3]["name"] = values[0] + " freemem_before" + this.outputs[4]["name"] = values[1] + " freemem_after" + return r + } + break; case "JoinStringMulti": nodeType.prototype.onNodeCreated = function () { From 4412c67aa690d14fdb8acf673ac4f7ee1eb4c6b1 Mon Sep 17 00:00:00 2001 From: kijai <40791699+kijai@users.noreply.github.com> Date: Sat, 4 May 2024 15:12:43 +0300 Subject: [PATCH 86/95] Add AppendInstanceDiffusionTracking -node --- __init__.py | 1 + nodes/curve_nodes.py | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/__init__.py b/__init__.py index 811716c..15732f9 100644 --- a/__init__.py +++ b/__init__.py @@ -114,6 +114,7 @@ NODE_CONFIG = { "GLIGENTextBoxApplyBatchCoords": {"class": GLIGENTextBoxApplyBatchCoords}, "Intrinsic_lora_sampling": {"class": Intrinsic_lora_sampling, "name": "Intrinsic Lora Sampling"}, "CreateInstanceDiffusionTracking": {"class": CreateInstanceDiffusionTracking}, + "AppendInstanceDiffusionTracking": {"class": AppendInstanceDiffusionTracking, "name": "Append Diffusion Tracking"}, } def generate_node_mappings(node_config): diff --git a/nodes/curve_nodes.py b/nodes/curve_nodes.py index 4eee557..338a873 100644 --- a/nodes/curve_nodes.py +++ b/nodes/curve_nodes.py @@ -740,7 +740,41 @@ for example: print(tracked) return (tracked, ) + +class AppendInstanceDiffusionTracking: + RETURN_TYPES = ("TRACKING",) + RETURN_NAMES = ("tracking",) + FUNCTION = "append" + CATEGORY = "KJNodes/experimental" + DESCRIPTION = """ +Appends tracking data to be used with InstanceDiffusion: +https://github.com/logtd/ComfyUI-InstanceDiffusion + +""" + + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "tracking_1": ("TRACKING", {"forceInput": True}), + "tracking_2": ("TRACKING", {"forceInput": True}), + }, + } + + def append(self, tracking_1, tracking_2): + tracking_copy = tracking_1.copy() + # Check for existing class names and class IDs, and raise an error if they exist + for class_name, class_data in tracking_2.items(): + if class_name in tracking_copy: + for class_id in class_data.keys(): + if class_id in tracking_copy[class_name]: + raise ValueError(f"Class ID {class_id} already exists for class name {class_name}. Cannot append tracking data.") + # If class name does not exist, add it + tracking_copy[class_name] = class_data + + return (tracking_copy, ) + class InterpolateCoords: RETURN_TYPES = ("STRING",) From 8ccc080bf48c509aeab3987000ce498b8d2b76cb Mon Sep 17 00:00:00 2001 From: kijai <40791699+kijai@users.noreply.github.com> Date: Sat, 4 May 2024 18:12:26 +0300 Subject: [PATCH 87/95] Fix InstanceDiffusion bboxes Seems it uses x1, y2, x2, y2 instead of x, y, width, height --- __init__.py | 2 +- nodes/curve_nodes.py | 39 +++++++++++++++++++++++++-------------- 2 files changed, 26 insertions(+), 15 deletions(-) diff --git a/__init__.py b/__init__.py index 15732f9..af75c4b 100644 --- a/__init__.py +++ b/__init__.py @@ -114,7 +114,7 @@ NODE_CONFIG = { "GLIGENTextBoxApplyBatchCoords": {"class": GLIGENTextBoxApplyBatchCoords}, "Intrinsic_lora_sampling": {"class": Intrinsic_lora_sampling, "name": "Intrinsic Lora Sampling"}, "CreateInstanceDiffusionTracking": {"class": CreateInstanceDiffusionTracking}, - "AppendInstanceDiffusionTracking": {"class": AppendInstanceDiffusionTracking, "name": "Append Diffusion Tracking"}, + "AppendInstanceDiffusionTracking": {"class": AppendInstanceDiffusionTracking}, } def generate_node_mappings(node_config): diff --git a/nodes/curve_nodes.py b/nodes/curve_nodes.py index 338a873..0edf3b2 100644 --- a/nodes/curve_nodes.py +++ b/nodes/curve_nodes.py @@ -76,8 +76,8 @@ class PlotCoordinates: }, "optional": {"size_multiplier": ("FLOAT", {"default": [1.0], "forceInput": True})}, } - RETURN_TYPES = ("IMAGE", ) - RETURN_NAMES = ("images", ) + RETURN_TYPES = ("IMAGE", "INT", "INT", "INT", "INT",) + RETURN_NAMES = ("images", "width", "height", "bbox_width", "bbox_height",) FUNCTION = "append" CATEGORY = "KJNodes/experimental" DESCRIPTION = """ @@ -94,7 +94,7 @@ Plots coordinates to sequence of images using Matplotlib. plot_image_tensor = plot_coordinates_to_tensor(coordinates, height, width, bbox_height, bbox_width, size_multiplier, text) - return (plot_image_tensor,) + return (plot_image_tensor, width, height, bbox_width, bbox_height) class SplineEditor: @@ -665,7 +665,7 @@ bounding boxes. for i in range(batch_size): x_position, y_position = coordinates[i] - position_param = (cond_pooled, int((height // 8) * size_multiplier[i]), int((width // 8) * size_multiplier[i]), (y_position - height / 2) // 8, (x_position - width / 2) // 8) + position_param = (cond_pooled, int((height // 8) * size_multiplier[i]), int((width // 8) * size_multiplier[i]), (y_position - height // 2) // 8, (x_position - width // 2) // 8) position_params_batch[i].append(position_param) # Append position_param to the correct sublist prev = [] @@ -687,8 +687,8 @@ bounding boxes. class CreateInstanceDiffusionTracking: - RETURN_TYPES = ("TRACKING",) - RETURN_NAMES = ("TRACKING",) + RETURN_TYPES = ("TRACKING", "INT", "INT", "INT", "INT",) + RETURN_NAMES = ("tracking", "width", "height", "bbox_width", "bbox_height",) FUNCTION = "tracking" CATEGORY = "KJNodes/experimental" DESCRIPTION = """ @@ -710,8 +710,7 @@ for example: "height": ("INT", {"default": 512,"min": 16, "max": 4096, "step": 1}), "bbox_width": ("INT", {"default": 512,"min": 16, "max": 4096, "step": 1}), "bbox_height": ("INT", {"default": 512,"min": 16, "max": 4096, "step": 1}), - - "class_name": ("STRING", {"default": "class_id"}), + "class_name": ("STRING", {"default": "class_name"}), "class_id": ("INT", {"default": 0,"min": 0, "max": 255, "step": 1}), }, "optional": { @@ -730,16 +729,28 @@ for example: # Initialize a list to hold the coordinates for the current ID id_coordinates = [] - for detection in coordinates: - # Append the 'x' and 'y' coordinates along with bbox width/height and frame width/height to the list for the current ID - id_coordinates.append([detection['x'], detection['y'], bbox_width, bbox_height, width, height]) + for coord in coordinates: + x = coord['x'] + y = coord['y'] + # Calculate the top left and bottom right coordinates + top_left_x = x - bbox_width // 2 + top_left_y = y - bbox_height // 2 + bottom_right_x = x + bbox_width // 2 + bottom_right_y = y + bbox_height // 2 + # Append the top left and bottom right coordinates to the list for the current ID + id_coordinates.append([top_left_x, top_left_y, bottom_right_x, bottom_right_y, width, height]) + + # Append the 'x' and 'y' coordinates along with bbox width/height and frame width/height to the list for the current ID + #id_coordinates.append([coord['x'] - bbox_width // 2 , coord['y'] - bbox_height // 2, bbox_width, bbox_height, width, height]) + + class_id = int(class_id) + print(class_id) # Assign the list of coordinates to the specified ID within the class_id dictionary tracked[class_name][class_id] = id_coordinates - - print(tracked) - return (tracked, ) + + return (tracked, width, height, bbox_width, bbox_height,) class AppendInstanceDiffusionTracking: From 5208980fc975b8ac2925fcb09be81d292badcae8 Mon Sep 17 00:00:00 2001 From: kijai <40791699+kijai@users.noreply.github.com> Date: Sat, 4 May 2024 23:37:19 +0300 Subject: [PATCH 88/95] Instancediffusion updates --- nodes/curve_nodes.py | 69 +++++++++++++++++++++++++---------------- web/js/spline_editor.js | 9 +++++- 2 files changed, 50 insertions(+), 28 deletions(-) diff --git a/nodes/curve_nodes.py b/nodes/curve_nodes.py index 0edf3b2..ab90589 100644 --- a/nodes/curve_nodes.py +++ b/nodes/curve_nodes.py @@ -687,8 +687,8 @@ bounding boxes. class CreateInstanceDiffusionTracking: - RETURN_TYPES = ("TRACKING", "INT", "INT", "INT", "INT",) - RETURN_NAMES = ("tracking", "width", "height", "bbox_width", "bbox_height",) + RETURN_TYPES = ("TRACKING", "STRING", "INT", "INT", "INT", "INT",) + RETURN_NAMES = ("tracking", "prompt", "width", "height", "bbox_width", "bbox_height",) FUNCTION = "tracking" CATEGORY = "KJNodes/experimental" DESCRIPTION = """ @@ -712,50 +712,61 @@ for example: "bbox_height": ("INT", {"default": 512,"min": 16, "max": 4096, "step": 1}), "class_name": ("STRING", {"default": "class_name"}), "class_id": ("INT", {"default": 0,"min": 0, "max": 255, "step": 1}), + "prompt": ("STRING", {"default": "prompt", "multiline": True}), }, "optional": { "size_multiplier": ("FLOAT", {"default": [1.0], "forceInput": True}), } } - def tracking(self, coordinates, class_name, class_id, width, height, bbox_width, bbox_height, size_multiplier=[1.0]): + def tracking(self, coordinates, class_name, class_id, width, height, bbox_width, bbox_height, prompt, size_multiplier=[1.0]): # Define the number of images in the batch coordinates = coordinates.replace("'", '"') coordinates = json.loads(coordinates) tracked = {} tracked[class_name] = {} - + batch_size = len(coordinates) # Initialize a list to hold the coordinates for the current ID id_coordinates = [] - - for coord in coordinates: + if len(size_multiplier) != batch_size: + size_multiplier = size_multiplier * (batch_size // len(size_multiplier)) + size_multiplier[:batch_size % len(size_multiplier)] + for i, coord in enumerate(coordinates): x = coord['x'] y = coord['y'] + adjusted_bbox_width = bbox_width * size_multiplier[i] + adjusted_bbox_height = bbox_height * size_multiplier[i] # Calculate the top left and bottom right coordinates - top_left_x = x - bbox_width // 2 - top_left_y = y - bbox_height // 2 - bottom_right_x = x + bbox_width // 2 - bottom_right_y = y + bbox_height // 2 + top_left_x = x - adjusted_bbox_width // 2 + top_left_y = y - adjusted_bbox_height // 2 + bottom_right_x = x + adjusted_bbox_width // 2 + bottom_right_y = y + adjusted_bbox_height // 2 # Append the top left and bottom right coordinates to the list for the current ID id_coordinates.append([top_left_x, top_left_y, bottom_right_x, bottom_right_y, width, height]) - - # Append the 'x' and 'y' coordinates along with bbox width/height and frame width/height to the list for the current ID - #id_coordinates.append([coord['x'] - bbox_width // 2 , coord['y'] - bbox_height // 2, bbox_width, bbox_height, width, height]) class_id = int(class_id) - print(class_id) # Assign the list of coordinates to the specified ID within the class_id dictionary tracked[class_name][class_id] = id_coordinates - print(tracked) - return (tracked, width, height, bbox_width, bbox_height,) + prompt_string = "" + for class_name, class_data in tracked.items(): + for class_id in class_data.keys(): + class_id_str = str(class_id) + # Use the incoming prompt for each class name and ID + prompt_string += f'"{class_id_str}.{class_name}": "({prompt})",\n' + + # Remove the last comma and newline + prompt_string = prompt_string.rstrip(",\n") + + print(prompt_string) + + return (tracked, prompt_string, width, height, bbox_width, bbox_height) class AppendInstanceDiffusionTracking: - RETURN_TYPES = ("TRACKING",) - RETURN_NAMES = ("tracking",) + RETURN_TYPES = ("TRACKING", "STRING",) + RETURN_NAMES = ("tracking", "prompt",) FUNCTION = "append" CATEGORY = "KJNodes/experimental" DESCRIPTION = """ @@ -771,20 +782,24 @@ https://github.com/logtd/ComfyUI-InstanceDiffusion "tracking_1": ("TRACKING", {"forceInput": True}), "tracking_2": ("TRACKING", {"forceInput": True}), }, + "optional": { + "prompt_1": ("STRING", {"default": "", "forceInput": True}), + "prompt_2": ("STRING", {"default": "", "forceInput": True}), + } } - def append(self, tracking_1, tracking_2): + def append(self, tracking_1, tracking_2, prompt_1="", prompt_2=""): tracking_copy = tracking_1.copy() # Check for existing class names and class IDs, and raise an error if they exist for class_name, class_data in tracking_2.items(): - if class_name in tracking_copy: - for class_id in class_data.keys(): - if class_id in tracking_copy[class_name]: - raise ValueError(f"Class ID {class_id} already exists for class name {class_name}. Cannot append tracking data.") - # If class name does not exist, add it - tracking_copy[class_name] = class_data - - return (tracking_copy, ) + if class_name not in tracking_copy: + tracking_copy[class_name] = class_data + else: + # If the class name exists, merge the class data from tracking_2 into tracking_copy + # This will add new class IDs under the same class name without raising an error + tracking_copy[class_name].update(class_data) + prompt_string = prompt_1 + "," + prompt_2 + return (tracking_copy, prompt_string) class InterpolateCoords: diff --git a/web/js/spline_editor.js b/web/js/spline_editor.js index 57cdac3..ed63ce6 100644 --- a/web/js/spline_editor.js +++ b/web/js/spline_editor.js @@ -314,12 +314,19 @@ function createSplineEditor(context, reset=false) { var pointsLayer = null; var samplingMethod = samplingMethodWidget.value + if (samplingMethod == "path") { + dotShape = "triangle" + } + interpolationWidget.callback = () => { interpolation = interpolationWidget.value updatePath(); } samplingMethodWidget.callback = () => { samplingMethod = samplingMethodWidget.value + if (samplingMethod == "path") { + dotShape = "triangle" + } updatePath(); } tensionWidget.callback = () => { @@ -359,7 +366,7 @@ function createSplineEditor(context, reset=false) { var h = heightWidget.value; var i = 3; let points = []; - + if (!reset && pointsStoreWidget.value != "") { points = JSON.parse(pointsStoreWidget.value); } else { From a976bac7015df63af8eba8a212be6df8c2918f8a Mon Sep 17 00:00:00 2001 From: kijai <40791699+kijai@users.noreply.github.com> Date: Sun, 5 May 2024 12:20:52 +0300 Subject: [PATCH 89/95] Add DrawInstanceDiffusionTracking -node --- __init__.py | 2 + nodes/curve_nodes.py | 87 ++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 86 insertions(+), 3 deletions(-) diff --git a/__init__.py b/__init__.py index af75c4b..70eb417 100644 --- a/__init__.py +++ b/__init__.py @@ -113,8 +113,10 @@ NODE_CONFIG = { "Superprompt": {"class": Superprompt, "name": "Superprompt"}, "GLIGENTextBoxApplyBatchCoords": {"class": GLIGENTextBoxApplyBatchCoords}, "Intrinsic_lora_sampling": {"class": Intrinsic_lora_sampling, "name": "Intrinsic Lora Sampling"}, + #instance diffusion "CreateInstanceDiffusionTracking": {"class": CreateInstanceDiffusionTracking}, "AppendInstanceDiffusionTracking": {"class": AppendInstanceDiffusionTracking}, + "DrawInstanceDiffusionTracking": {"class": DrawInstanceDiffusionTracking}, } def generate_node_mappings(node_config): diff --git a/nodes/curve_nodes.py b/nodes/curve_nodes.py index ab90589..8c8e9b2 100644 --- a/nodes/curve_nodes.py +++ b/nodes/curve_nodes.py @@ -1,8 +1,10 @@ import torch +from torchvision import transforms import json -from PIL import Image, ImageDraw +from PIL import Image, ImageDraw, ImageFont import numpy as np from ..utility.utility import pil2tensor +import folder_paths def plot_coordinates_to_tensor(coordinates, height, width, bbox_height, bbox_width, size_multiplier, prompt): import matplotlib @@ -690,7 +692,7 @@ class CreateInstanceDiffusionTracking: RETURN_TYPES = ("TRACKING", "STRING", "INT", "INT", "INT", "INT",) RETURN_NAMES = ("tracking", "prompt", "width", "height", "bbox_width", "bbox_height",) FUNCTION = "tracking" - CATEGORY = "KJNodes/experimental" + CATEGORY = "KJNodes/InstanceDiffusion" DESCRIPTION = """ Creates tracking data to be used with InstanceDiffusion: https://github.com/logtd/ComfyUI-InstanceDiffusion @@ -768,7 +770,7 @@ class AppendInstanceDiffusionTracking: RETURN_TYPES = ("TRACKING", "STRING",) RETURN_NAMES = ("tracking", "prompt",) FUNCTION = "append" - CATEGORY = "KJNodes/experimental" + CATEGORY = "KJNodes/InstanceDiffusion" DESCRIPTION = """ Appends tracking data to be used with InstanceDiffusion: https://github.com/logtd/ComfyUI-InstanceDiffusion @@ -865,3 +867,82 @@ Interpolates coordinates based on a curve. return (interpolated_coords_str, ) +class DrawInstanceDiffusionTracking: + + RETURN_TYPES = ("IMAGE",) + RETURN_NAMES = ("image", ) + FUNCTION = "draw" + CATEGORY = "KJNodes/InstanceDiffusion" + DESCRIPTION = """ +Draws the tracking data from +CreateInstanceDiffusionTracking -node. + +""" + + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "image": ("IMAGE", ), + "tracking": ("TRACKING", {"forceInput": True}), + "box_line_width": ("INT", {"default": 2, "min": 1, "max": 10, "step": 1}), + "draw_text": ("BOOLEAN", {"default": True}), + "font": (folder_paths.get_filename_list("kjnodes_fonts"), ), + "font_size": ("INT", {"default": 20}), + }, + } + + def draw(self, image, tracking, box_line_width, draw_text, font, font_size): + import matplotlib.cm as cm + print(image.shape) + + modified_images = [] + + colormap = cm.get_cmap('rainbow', len(tracking)) + if draw_text: + #font = ImageFont.load_default() + font = ImageFont.truetype("arial.ttf", font_size) + + # Iterate over each image in the batch + for i in range(image.shape[0]): + # Extract the current image and convert it to a PIL image + # Adjust the tensor to (C, H, W) for ToPILImage + current_image = image[i, :, :, :].permute(2, 0, 1) + pil_image = transforms.ToPILImage()(current_image) + + draw = ImageDraw.Draw(pil_image) + + # Iterate over the bounding boxes for the current image + for j, (class_name, class_data) in enumerate(tracking.items()): + for class_id, bbox_list in class_data.items(): + # Check if the current index is within the bounds of the bbox_list + if i < len(bbox_list): + bbox = bbox_list[i] + # Ensure bbox is a list or tuple before unpacking + if isinstance(bbox, (list, tuple)): + x1, y1, x2, y2, _, _ = bbox + # Convert coordinates to integers + x1, y1, x2, y2 = int(x1), int(y1), int(x2), int(y2) + # Generate a color from the rainbow colormap + color = tuple(int(255 * x) for x in colormap(j / len(tracking)))[:3] + # Draw the bounding box on the image with the generated color + draw.rectangle([x1, y1, x2, y2], outline=color, width=box_line_width) + if draw_text: + # Draw the class name and ID as text above the box with the generated color + text = f"{class_id}.{class_name}" + # Calculate the width and height of the text + _, _, text_width, text_height = draw.textbbox((0, 0), text=text, font=font) + # Position the text above the top-left corner of the box + text_position = (x1, y1 - text_height) + draw.text(text_position, text, fill=color, font=font) + else: + print(f"Unexpected data type for bbox: {type(bbox)}") + + # Convert the drawn image back to a torch tensor and adjust back to (H, W, C) + modified_image_tensor = transforms.ToTensor()(pil_image).permute(1, 2, 0) + modified_images.append(modified_image_tensor) + + # Stack the modified images back into a batch + image_tensor_batch = torch.stack(modified_images).cpu().float() + + return image_tensor_batch, \ No newline at end of file From e65a035693ff92fff8a5f5d8fd0a6b84169ead99 Mon Sep 17 00:00:00 2001 From: kijai <40791699+kijai@users.noreply.github.com> Date: Sun, 5 May 2024 12:37:43 +0300 Subject: [PATCH 90/95] AddLabel auto newline --- nodes/image_nodes.py | 44 +++++++++++++++++++++++++++++++------------- 1 file changed, 31 insertions(+), 13 deletions(-) diff --git a/nodes/image_nodes.py b/nodes/image_nodes.py index 898673d..e061b60 100644 --- a/nodes/image_nodes.py +++ b/nodes/image_nodes.py @@ -415,22 +415,40 @@ ComfyUI/custom_nodes/ComfyUI-KJNodes/fonts def process_image(input_image, caption_text): if direction == 'overlay': pil_image = Image.fromarray((input_image.cpu().numpy() * 255).astype(np.uint8)) - draw = ImageDraw.Draw(pil_image) - font = ImageFont.truetype(font_path, font_size) - try: - draw.text((text_x, text_y), caption_text, font=font, fill=font_color, features=['-liga']) - except: - draw.text((text_x, text_y), caption_text, font=font, fill=font_color) - processed_image = torch.from_numpy(np.array(pil_image).astype(np.float32) / 255.0).unsqueeze(0) else: label_image = Image.new("RGB", (width, height), label_color) - draw = ImageDraw.Draw(label_image) + pil_image = label_image + + draw = ImageDraw.Draw(pil_image) font = ImageFont.truetype(font_path, font_size) - try: - draw.text((text_x, text_y), caption_text, font=font, fill=font_color, features=['-liga']) - except: - draw.text((text_x, text_y), caption_text, font=font, fill=font_color) - processed_image = torch.from_numpy(np.array(label_image).astype(np.float32) / 255.0)[None, :, :, :] + + words = caption_text.split() + + lines = [] + current_line = [] + current_line_width = 0 + for word in words: + word_width = font.getbbox(word)[2] + if current_line_width + word_width <= width - 2 * text_x: + current_line.append(word) + current_line_width += word_width + font.getbbox(" ")[2] # Add space width + else: + lines.append(" ".join(current_line)) + current_line = [word] + current_line_width = word_width + + if current_line: + lines.append(" ".join(current_line)) + + y_offset = text_y + for line in lines: + try: + draw.text((text_x, y_offset), line, font=font, fill=font_color, features=['-liga']) + except: + draw.text((text_x, y_offset), line, font=font, fill=font_color) + y_offset += font_size # Move to the next line + + processed_image = torch.from_numpy(np.array(pil_image).astype(np.float32) / 255.0).unsqueeze(0) return processed_image if caption == "": From 38f1d0c08ab9b748d51ceebe1dcea791173f1697 Mon Sep 17 00:00:00 2001 From: kijai <40791699+kijai@users.noreply.github.com> Date: Sun, 5 May 2024 13:20:45 +0300 Subject: [PATCH 91/95] Show coords on mouseover in path -mode --- web/js/spline_editor.js | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/web/js/spline_editor.js b/web/js/spline_editor.js index ed63ce6..ef43e85 100644 --- a/web/js/spline_editor.js +++ b/web/js/spline_editor.js @@ -493,7 +493,7 @@ function createSplineEditor(context, reset=false) { }) .cursor("move") .strokeStyle(function() { return i == this.index ? "#ff7f0e" : "#1f77b4"; }) - .fillStyle(function() { return "rgba(100, 100, 100, 0.2)"; }) + .fillStyle(function() { return "rgba(100, 100, 100, 0.3)"; }) .event("mousedown", pv.Behavior.drag()) .event("dragstart", function() { i = this.index; @@ -539,13 +539,16 @@ function createSplineEditor(context, reset=false) { }) .left(d => d.x < w / 2 ? d.x + 80 : d.x - 70) // Shift label to right if on left half, otherwise shift to left .top(d => d.y < h / 2 ? d.y + 20 : d.y - 20) // Shift label down if on top half, otherwise shift up - - .font(12 + "px sans-serif") - .text(d => { + .font(12 + "px sans-serif") + .text(d => { + if (samplingMethod == "path") { + return `X: ${Math.round(d.x)}, Y: ${Math.round(d.y)}`; + } else { + let frame = Math.round((d.x / w) * points_to_sample); let normalizedY = (1.0 - (d.y / h) - 0.0) * (rangeMax - rangeMin) + rangeMin; let normalizedX = (d.x / w); - let frame = Math.round((d.x / w) * points_to_sample); return `F: ${frame}, X: ${normalizedX.toFixed(2)}, Y: ${normalizedY.toFixed(2)}`; + } }) .textStyle("orange") From 84185b35f664ce8120e50e7e01a828403b016929 Mon Sep 17 00:00:00 2001 From: kijai <40791699+kijai@users.noreply.github.com> Date: Sun, 5 May 2024 15:18:08 +0300 Subject: [PATCH 92/95] Update spline_editor.js --- web/js/spline_editor.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/js/spline_editor.js b/web/js/spline_editor.js index ef43e85..523f085 100644 --- a/web/js/spline_editor.js +++ b/web/js/spline_editor.js @@ -391,7 +391,7 @@ function createSplineEditor(context, reset=false) { var vis = new pv.Panel() .width(w) .height(h) - .fillStyle("var(--comfy-menu-bg)") + .fillStyle("var(--comfy-input-bg)") .strokeStyle("gray") .lineWidth(2) .antialias(false) From 8e720289487735c8888040c9c4fc54dbcad1de32 Mon Sep 17 00:00:00 2001 From: kijai <40791699+kijai@users.noreply.github.com> Date: Sun, 5 May 2024 15:40:06 +0300 Subject: [PATCH 93/95] Update spline_editor.js --- web/js/spline_editor.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/js/spline_editor.js b/web/js/spline_editor.js index 523f085..8f03f66 100644 --- a/web/js/spline_editor.js +++ b/web/js/spline_editor.js @@ -391,7 +391,7 @@ function createSplineEditor(context, reset=false) { var vis = new pv.Panel() .width(w) .height(h) - .fillStyle("var(--comfy-input-bg)") + .fillStyle("#222") .strokeStyle("gray") .lineWidth(2) .antialias(false) From 11459bb550a90ed6e328f1e6f5caa01ac348aca9 Mon Sep 17 00:00:00 2001 From: kijai <40791699+kijai@users.noreply.github.com> Date: Sun, 5 May 2024 18:18:56 +0300 Subject: [PATCH 94/95] import error fix --- nodes/nodes.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nodes/nodes.py b/nodes/nodes.py index 1472ec8..f327045 100644 --- a/nodes/nodes.py +++ b/nodes/nodes.py @@ -9,6 +9,7 @@ import json, re, os, io, time import model_management import folder_paths from nodes import MAX_RESOLUTION +from comfy.utils import common_upscale script_directory = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) folder_paths.add_model_folder_path("kjnodes_fonts", os.path.join(script_directory, "fonts")) @@ -1009,7 +1010,6 @@ class StableZero123_BatchSchedule: CATEGORY = "KJNodes/experimental" def encode(self, clip_vision, init_image, vae, width, height, batch_size, azimuth_points_string, elevation_points_string, interpolation): - from comfy.utils import common_upscale output = clip_vision.encode_image(init_image) pooled = output.image_embeds.unsqueeze(0) pixels = common_upscale(init_image.movedim(-1,1), width, height, "bilinear", "center").movedim(1,-1) @@ -1150,7 +1150,7 @@ https://huggingface.co/stabilityai/sv3d def encode(self, clip_vision, init_image, vae, width, height, batch_size, azimuth_points_string, elevation_points_string, interpolation): output = clip_vision.encode_image(init_image) pooled = output.image_embeds.unsqueeze(0) - pixels = comfy.utils.common_upscale(init_image.movedim(-1,1), width, height, "bilinear", "center").movedim(1,-1) + pixels = common_upscale(init_image.movedim(-1,1), width, height, "bilinear", "center").movedim(1,-1) encode_pixels = pixels[:,:,:,:3] t = vae.encode(encode_pixels) From 9acc603b3ee35202b92665c3aba3015f2e011d2e Mon Sep 17 00:00:00 2001 From: kijai <40791699+kijai@users.noreply.github.com> Date: Sun, 5 May 2024 18:22:33 +0300 Subject: [PATCH 95/95] cleanup prints --- nodes/curve_nodes.py | 3 --- nodes/image_nodes.py | 1 - nodes/nodes.py | 14 ++++---------- 3 files changed, 4 insertions(+), 14 deletions(-) diff --git a/nodes/curve_nodes.py b/nodes/curve_nodes.py index 8c8e9b2..47086af 100644 --- a/nodes/curve_nodes.py +++ b/nodes/curve_nodes.py @@ -761,8 +761,6 @@ for example: # Remove the last comma and newline prompt_string = prompt_string.rstrip(",\n") - print(prompt_string) - return (tracked, prompt_string, width, height, bbox_width, bbox_height) class AppendInstanceDiffusionTracking: @@ -894,7 +892,6 @@ CreateInstanceDiffusionTracking -node. def draw(self, image, tracking, box_line_width, draw_text, font, font_size): import matplotlib.cm as cm - print(image.shape) modified_images = [] diff --git a/nodes/image_nodes.py b/nodes/image_nodes.py index e061b60..4dec4d3 100644 --- a/nodes/image_nodes.py +++ b/nodes/image_nodes.py @@ -794,7 +794,6 @@ nodes for example. mask_image = mask.reshape((-1, 1, mask.shape[-2], mask.shape[-1])).movedim(1, -1).expand(-1, -1, -1, 3).clone() color_list = list(map(int, mask_color.split(', '))) - print(color_list[0]) mask_image[:, :, :, 0] = color_list[0] // 255 # Red channel mask_image[:, :, :, 1] = color_list[1] // 255 # Green channel mask_image[:, :, :, 2] = color_list[2] // 255 # Blue channel diff --git a/nodes/nodes.py b/nodes/nodes.py index f327045..5d87e5b 100644 --- a/nodes/nodes.py +++ b/nodes/nodes.py @@ -114,7 +114,6 @@ to a different frame count. } def scaleschedule(self, old_frame_count, input_str, new_frame_count): - print("input_str:", input_str) pattern = r'"(\d+)"\s*:\s*"(.*?)"(?:,|\Z)' frame_strings = dict(re.findall(pattern, input_str)) @@ -134,7 +133,6 @@ to a different frame count. # Format the output string output_str = ', '.join([f'"{k}":"{v}"' for k, v in sorted(new_frame_strings.items())]) - print(output_str) return (output_str,) @@ -601,7 +599,6 @@ Converts any type to a string. if isinstance(input, (int, float, bool)): stringified = str(input) elif isinstance(input, list): - print("input is a list") stringified = ', '.join(str(item) for item in input) else: return @@ -712,10 +709,8 @@ To see node id's, enable node id display from Manager badge menu. def get_widget_value(self, id, widget_name, extra_pnginfo, prompt, return_all=False): workflow = extra_pnginfo["workflow"] - print(workflow) results = [] for node in workflow["nodes"]: - print(node) node_id = node["id"] if node_id != id: @@ -1236,8 +1231,8 @@ https://huggingface.co/stabilityai/sv3d azimuths.append(interpolated_azimuth) elevations.append(interpolated_elevation) - print("azimuths", azimuths) - print("elevations", elevations) + #print("azimuths", azimuths) + #print("elevations", elevations) # Structure the final output final_positive = [[pooled, {"concat_latent_image": t, "elevation": elevations, "azimuth": azimuths}]] @@ -1320,13 +1315,12 @@ https://huggingface.co/roborovski/superprompt-v1 model = T5ForConditionalGeneration.from_pretrained(checkpoint_path, device_map=device) model.to(device) input_text = instruction_prompt + ": " + prompt - print(input_text) + input_ids = tokenizer(input_text, return_tensors="pt").input_ids.to(device) outputs = model.generate(input_ids, max_new_tokens=max_new_tokens) out = (tokenizer.decode(outputs[0])) out = out.replace('', '') out = out.replace('', '') - print(out) return (out, ) @@ -1391,7 +1385,7 @@ or a .txt file with RealEstate camera intrinsics and coordinates, in a 3D plot. poses = f.readlines() w2cs = [np.asarray([float(p) for p in pose.strip().split(' ')[7:]]).reshape(3, 4) for pose in poses[1:]] fxs = [float(pose.strip().split(' ')[1]) for pose in poses[1:]] - print(poses) + #print(poses) elif cameractrl_poses is not None: poses = cameractrl_poses w2cs = [np.array(pose[7:]).reshape(3, 4) for pose in cameractrl_poses]

_(=Rxa}gtY7>U}w051p+DD72CllxR2OD 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 z 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 z%0A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%0AApUGqqZHz;R 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