Repository: Haidra-Org/hordelib (language: python)

Issue:
feat: adds stable_cascade_2pass
New feature where it add [2-pass Stable Cascade](https://comfyworkflows.com/workflows/2e72682e-dc58-4ceb-8dab-49ddfd074292)


feat: Removes the requirement for a design pipeline
* Uses the exported "_meta" field to get the pipeline node name, instead of requiring a design_pipeline.
* The design pipelines are left there for future reimport into comfy UI and adjustments, but have no other functional use.


Discussion / hints:

@tazlin I had to skip the pre-commit for the last commit, as it keeps complaining about test_comfy.py and I can't figure it out. I just removed the requirement for the design pipeline from it, but them mypy started complaining

![image](https://github.com/Haidra-Org/hordelib/assets/296513/200422ff-b012-4c9d-bf89-ddfc52115a61)

OK to merge then? This PR is finised

Fix patch:
diff --git a/hordelib/comfy_horde.py b/hordelib/comfy_horde.py
index c9a1759f..9a59d4e0 100644
--- a/hordelib/comfy_horde.py
+++ b/hordelib/comfy_horde.py
@@ -458,7 +458,7 @@ def _fix_pipeline_types(self, data: dict) -> dict:
 
         return data
 
-    def _fix_node_names(self, data: dict, design: dict) -> dict:
+    def _fix_node_names(self, data: dict) -> dict:
         """Rename nodes to the "title" set in the design file.
 
         Args:
@@ -472,15 +472,12 @@ def _fix_node_names(self, data: dict, design: dict) -> dict:
         # in the design file. These must be unique names.
         newnodes = {}
         renames = {}
-        nodes = design["nodes"]
-        for nodename, oldnode in data.items():
+        for nodename, nodedata in data.items():
             newname = nodename
-            for node in nodes:
-                if str(node["id"]) == str(nodename) and "title" in node:
-                    newname = node["title"]
-                    break
+            if nodedata.get("_meta", {}).get("title"):
+                newname = nodedata["_meta"]["title"]
             renames[nodename] = newname
-            newnodes[newname] = oldnode
+            newnodes[newname] = nodedata
 
         # Now we've renamed the node names, change any references to them also
         for node in newnodes.values():
@@ -504,13 +501,13 @@ def _fix_node_names(self, data: dict, design: dict) -> dict:
     #
     # Note also that the format of the design files from web app is expected to change at a fast
     # pace. This is why the only thing that partially relies on that format, is in fact, optional.
-    def _patch_pipeline(self, data: dict, design: dict) -> dict:
+    def _patch_pipeline(self, data: dict) -> dict:
         """Patch the pipeline data with the design data."""
         # FIXME: This can now be done through the _meta.title key included with each API export.
         # First replace comfyui standard types with hordelib node types
         data = self._fix_pipeline_types(data)
         # Now try to find better parameter names
-        return self._fix_node_names(data, design)
+        return self._fix_node_names(data)
 
     def _load_pipeline(self, filename: str) -> bool | None:
         """
@@ -540,17 +537,8 @@ def _load_pipeline(self, filename: str) -> bool | None:
                 # Load the pipeline data from the file
                 pipeline_data = json.loads(jsonfile.read())
                 # Check if there is a design file for this pipeline
-                pipeline_design = os.path.join(
-                    os.path.dirname(os.path.dirname(filename)),
-                    "pipeline_designs",
-                    os.path.basename(filename),
-                )
-                # If there is a design file, patch the pipeline data with it
-                if os.path.exists(pipeline_design):
-                    logger.debug(f"Patching pipeline {pipeline_name}")
-                    with open(pipeline_design) as design_file:
-                        design_data = json.loads(design_file.read())
-                    pipeline_data = self._patch_pipeline(pipeline_data, design_data)
+                logger.debug(f"Patching pipeline {pipeline_name}")
+                pipeline_data = self._patch_pipeline(pipeline_data)
                 # Add the pipeline data to the pipelines dictionary
                 self.pipelines[pipeline_name] = pipeline_data
                 logger.debug(f"Loaded inference pipeline: {pipeline_name}")
@@ -690,10 +678,10 @@ def _run_pipeline(
         # This is useful for dumping the entire pipeline to the terminal when
         # developing and debugging new pipelines. A badly structured pipeline
         # file just results in a cryptic error from comfy
-        # if False:  # This isn't here, Tazlin :)
-        # with open("pipeline_debug.json", "w") as outfile:
-        #     default = lambda o: f"<<non-serializable: {type(o).__qualname__}>>"
-        #     outfile.write(json.dumps(pipeline, indent=4, default=default))
+        # if True:  # This isn't here, Tazlin :)
+        #     with open("pipeline_debug.json", "w") as outfile:
+        #         default = lambda o: f"<<non-serializable: {type(o).__qualname__}>>"
+        #         outfile.write(json.dumps(pipeline, indent=4, default=default))
         # pretty_pipeline = pformat(pipeline)
         # logger.warning(pretty_pipeline)
 
diff --git a/hordelib/horde.py b/hordelib/horde.py
index 2002bc09..f5c1a232 100644
--- a/hordelib/horde.py
+++ b/hordelib/horde.py
@@ -239,6 +239,10 @@ class HordeLib:
         "model_loader_stage_b.ckpt_name": "stable_cascade_stage_b",
         "model_loader_stage_b.model_name": "stable_cascade_stage_b",
         "model_loader_stage_b.horde_model_name": "model_name",
+        # Stable Cascade 2pass
+        "2pass_sampler_stage_c.sampler_name": "sampler_name",
+        "2pass_sampler_stage_c.denoise": "hires_fix_denoising_strength",
+        "2pass_sampler_stage_b.sampler_name": "sampler_name",
     }
 
     _comfyui_callback: Callable[[str, dict, str], None] | None = None
@@ -771,14 +775,44 @@ def _final_pipeline_adjustments(self, payload, pipeline_data) -> tuple[dict, lis
 
         # For hires fix, change the image sizes as we create an intermediate image first
         if payload.get("hires_fix", False):
-            width = pipeline_params.get("empty_latent_image.width", 0)
-            height = pipeline_params.get("empty_latent_image.height", 0)
-            if width > 512 and height > 512:
-                newwidth, newheight = ImageUtils.calculate_source_image_size(width, height)
-                pipeline_params["latent_upscale.width"] = width
-                pipeline_params["latent_upscale.height"] = height
-                pipeline_params["empty_latent_image.width"] = newwidth
-                pipeline_params["empty_latent_image.height"] = newheight
+            model_details = (
+                SharedModelManager.manager.compvis.get_model_reference_info(payload["model_name"])
+                if SharedModelManager.manager.compvis
+                else None
+            )
+
+            original_width = pipeline_params.get("empty_latent_image.width")
+            original_height = pipeline_params.get("empty_latent_image.height")
+
+            if original_width is None or original_height is None:
+                logger.error("empty_latent_image.width or empty_latent_image.height not found. Using 512x512.")
+                original_width, original_height = (512, 512)
+
+            new_width, new_height = (None, None)
+
+            if model_details and model_details.get("baseline") == "stable_cascade":
+                new_width, new_height = ImageUtils.get_first_pass_image_resolution_max(
+                    original_width,
+                    original_height,
+                )
+            else:
+                new_width, new_height = ImageUtils.get_first_pass_image_resolution_min(
+                    original_width,
+                    original_height,
+                )
+
+            # This is the *target* resolution
+            pipeline_params["latent_upscale.width"] = original_width
+            pipeline_params["latent_upscale.height"] = original_height
+
+            if new_width and new_height:
+                # This is the *first pass* resolution
+                pipeline_params["empty_latent_image.width"] = new_width
+                pipeline_params["empty_latent_image.height"] = new_height
+            else:
+                logger.error("Could not determine new image size for hires fix. Using 1024x1024.")
+                pipeline_params["empty_latent_image.width"] = 1024
+                pipeline_params["empty_latent_image.height"] = 1024
 
         if payload.get("control_type"):
             # Inject control net model manager
@@ -886,6 +920,7 @@ def _get_appropriate_pipeline(self, params):
         #     image_upscale
         #     stable_cascade
         #       stable_cascade_remix
+        #       stable_cascade_2pass
 
         # controlnet, controlnet_hires_fix controlnet_annotator
         if params.get("model_name"):
@@ -893,6 +928,8 @@ def _get_appropriate_pipeline(self, params):
             if model_details.get("baseline") == "stable_cascade":
                 if params.get("source_processing") == "remix":
                     return "stable_cascade_remix"
+                if params.get("hires_fix", False):
+                    return "stable_cascade_2pass"
                 return "stable_cascade"
         if params.get("control_type"):
             if params.get("return_control_map", False):
diff --git a/hordelib/nodes/node_lora_loader.py b/hordelib/nodes/node_lora_loader.py
index 09dd21f7..2c0fa1c1 100644
--- a/hordelib/nodes/node_lora_loader.py
+++ b/hordelib/nodes/node_lora_loader.py
@@ -1,7 +1,7 @@
 import os
 
 import comfy.utils
-import folder_paths
+import folder_paths  # type: ignore
 from loguru import logger
 
 
diff --git a/hordelib/pipeline_designs/pipeline_stable_cascade.json b/hordelib/pipeline_designs/pipeline_stable_cascade.json
index 6331dcb3..9995e9d2 100644
--- a/hordelib/pipeline_designs/pipeline_stable_cascade.json
+++ b/hordelib/pipeline_designs/pipeline_stable_cascade.json
@@ -96,7 +96,7 @@
         "1": 26
       },
       "flags": {},
-      "order": 7,
+      "order": 8,
       "mode": 0,
       "inputs": [
         {
@@ -230,51 +230,6 @@
         1
       ]
     },
-    {
-      "id": 34,
-      "type": "StableCascade_EmptyLatentImage",
-      "pos": [
-        210,
-        725
-      ],
-      "size": {
-        "0": 315,
-        "1": 150
-      },
-      "flags": {},
-      "order": 1,
-      "mode": 0,
-      "outputs": [
-        {
-          "name": "stage_c",
-          "type": "LATENT",
-          "links": [
-            82
-          ],
-          "shape": 3,
-          "slot_index": 0
-        },
-        {
-          "name": "stage_b",
-          "type": "LATENT",
-          "links": [
-            83
-          ],
-          "shape": 3,
-          "slot_index": 1
-        }
-      ],
-      "title": "stable_cascade_empty_latent_image",
-      "properties": {
-        "Node name for S&R": "StableCascade_EmptyLatentImage"
-      },
-      "widgets_values": [
-        1024,
-        1024,
-        42,
-        5
-      ]
-    },
     {
       "id": 3,
       "type": "KSampler",
@@ -287,7 +242,7 @@
         "1": 262
       },
       "flags": {},
-      "order": 8,
+      "order": 9,
       "mode": 0,
       "inputs": [
         {
@@ -348,7 +303,7 @@
         "1": 192
       },
       "flags": {},
-      "order": 4,
+      "order": 5,
       "mode": 0,
       "inputs": [
         {
@@ -390,7 +345,7 @@
         "1": 96
       },
       "flags": {},
-      "order": 5,
+      "order": 6,
       "mode": 0,
       "inputs": [
         {
@@ -446,12 +401,52 @@
         "ComfyUI"
       ]
     },
+    {
+      "id": 48,
+      "type": "LoadImage",
+      "pos": [
+        -263,
+        724
+      ],
+      "size": {
+        "0": 315,
+        "1": 314
+      },
+      "flags": {},
+      "order": 1,
+      "mode": 0,
+      "outputs": [
+        {
+          "name": "IMAGE",
+          "type": "IMAGE",
+          "links": [
+            117
+          ],
+          "shape": 3,
+          "slot_index": 0
+        },
+        {
+          "name": "MASK",
+          "type": "MASK",
+          "links": null,
+          "shape": 3
+        }
+      ],
+      "title": "sc_image_loader",
+      "properties": {
+        "Node name for S&R": "LoadImage"
+      },
+      "widgets_values": [
+        "example.png",
+        "image"
+      ]
+    },
     {
       "id": 41,
       "type": "CheckpointLoaderSimple",
       "pos": [
-        -271.29601025390616,
-        353.6759821777344
+        -271,
+        354
       ],
       "size": {
         "0": 336,
@@ -498,6 +493,46 @@
         "stable_cascade_stage_c.safetensors"
       ]
     },
+    {
+      "id": 50,
+      "type": "RepeatImageBatch",
+      "pos": [
+        99,
+        936
+      ],
+      "size": {
+        "0": 315,
+        "1": 58
+      },
+      "flags": {},
+      "order": 4,
+      "mode": 0,
+      "inputs": [
+        {
+          "name": "image",
+          "type": "IMAGE",
+          "link": 117
+        }
+      ],
+      "outputs": [
+        {
+          "name": "IMAGE",
+          "type": "IMAGE",
+          "links": [
+            118
+          ],
+          "shape": 3,
+          "slot_index": 0
+        }
+      ],
+      "title": "repeat_image_batch",
+      "properties": {
+        "Node name for S&R": "RepeatImageBatch"
+      },
+      "widgets_values": [
+        1
+      ]
+    },
     {
       "id": 47,
       "type": "StableCascade_StageC_VAEEncode",
@@ -510,7 +545,7 @@
         "1": 78
       },
       "flags": {},
-      "order": 9,
+      "order": 7,
       "mode": 0,
       "inputs": [
         {
@@ -547,83 +582,48 @@
       ]
     },
     {
-      "id": 48,
-      "type": "LoadImage",
+      "id": 34,
+      "type": "StableCascade_EmptyLatentImage",
       "pos": [
-        -263,
-        724
-      ],
-      "size": [
-        315,
-        314
+        210,
+        725
       ],
+      "size": {
+        "0": 315,
+        "1": 150
+      },
       "flags": {},
       "order": 3,
       "mode": 0,
       "outputs": [
         {
-          "name": "IMAGE",
-          "type": "IMAGE",
+          "name": "stage_c",
+          "type": "LATENT",
           "links": [
-            117
+            82
           ],
           "shape": 3,
           "slot_index": 0
         },
         {
-          "name": "MASK",
-          "type": "MASK",
-          "links": null,
-          "shape": 3
-        }
-      ],
-      "title": "sc_image_loader",
-      "properties": {
-        "Node name for S&R": "LoadImage"
-      },
-      "widgets_values": [
-        "example.png",
-        "image"
-      ]
-    },
-    {
-      "id": 50,
-      "type": "RepeatImageBatch",
-      "pos": [
-        99,
-        936
-      ],
-      "size": {
-        "0": 315,
-        "1": 58
-      },
-      "flags": {},
-      "order": 6,
-      "mode": 0,
-      "inputs": [
-        {
-          "name": "image",
-          "type": "IMAGE",
-          "link": 117
-        }
-      ],
-      "outputs": [
-        {
-          "name": "IMAGE",
-          "type": "IMAGE",
+          "name": "stage_b",
+          "type": "LATENT",
           "links": [
-            118
+            83
           ],
           "shape": 3,
-          "slot_index": 0
+          "slot_index": 1
         }
       ],
-      "title": "repeat_image_batch",
+      "title": "empty_latent_image",
       "properties": {
-        "Node name for S&R": "RepeatImageBatch"
+        "Node name for S&R": "StableCascade_EmptyLatentImage"
       },
       "widgets_values": [
-        1
+        1024,
+        1024,
+        42,
+        5
       ]
     }
   ],
diff --git a/hordelib/pipeline_designs/pipeline_stable_cascade_2pass.json b/hordelib/pipeline_designs/pipeline_stable_cascade_2pass.json
new file mode 100644
index 00000000..dbc38511
--- /dev/null
+++ b/hordelib/pipeline_designs/pipeline_stable_cascade_2pass.json
@@ -0,0 +1,1349 @@
+{
+  "last_node_id": 92,
+  "last_link_id": 197,
+  "nodes": [
+    {
+      "id": 24,
+      "type": "StableCascade_StageB_Conditioning",
+      "pos": [
+        1000,
+        190
+      ],
+      "size": {
+        "0": 277.20001220703125,
+        "1": 46
+      },
+      "flags": {},
+      "order": 11,
+      "mode": 0,
+      "inputs": [
+        {
+          "name": "conditioning",
+          "type": "CONDITIONING",
+          "link": 39
+        },
+        {
+          "name": "stage_c",
+          "type": "LATENT",
+          "link": 40
+        }
+      ],
+      "outputs": [
+        {
+          "name": "CONDITIONING",
+          "type": "CONDITIONING",
+          "links": [
+            36
+          ],
+          "shape": 3,
+          "slot_index": 0
+        }
+      ],
+      "properties": {
+        "Node name for S&R": "StableCascade_StageB_Conditioning"
+      }
+    },
+    {
+      "id": 25,
+      "type": "ConditioningZeroOut",
+      "pos": [
+        1000,
+        280
+      ],
+      "size": {
+        "0": 211.60000610351562,
+        "1": 26
+      },
+      "flags": {},
+      "order": 9,
+      "mode": 0,
+      "inputs": [
+        {
+          "name": "conditioning",
+          "type": "CONDITIONING",
+          "link": 41
+        }
+      ],
+      "outputs": [
+        {
+          "name": "CONDITIONING",
+          "type": "CONDITIONING",
+          "links": [
+            37,
+            39
+          ],
+          "shape": 3,
+          "slot_index": 0
+        }
+      ],
+      "properties": {
+        "Node name for S&R": "ConditioningZeroOut"
+      }
+    },
+    {
+      "id": 22,
+      "type": "VAEDecode",
+      "pos": [
+        1100,
+        620
+      ],
+      "size": {
+        "0": 210,
+        "1": 46
+      },
+      "flags": {},
+      "order": 13,
+      "mode": 0,
+      "inputs": [
+        {
+          "name": "samples",
+          "type": "LATENT",
+          "link": 33
+        },
+        {
+          "name": "vae",
+          "type": "VAE",
+          "link": 34,
+          "slot_index": 1
+        }
+      ],
+      "outputs": [
+        {
+          "name": "IMAGE",
+          "type": "IMAGE",
+          "links": [
+            197
+          ],
+          "slot_index": 0
+        }
+      ],
+      "properties": {
+        "Node name for S&R": "VAEDecode"
+      }
+    },
+    {
+      "id": 33,
+      "type": "PrimitiveNode",
+      "pos": [
+        0,
+        650
+      ],
+      "size": {
+        "0": 390,
+        "1": 150
+      },
+      "flags": {},
+      "order": 0,
+      "mode": 0,
+      "outputs": [
+        {
+          "name": "STRING",
+          "type": "STRING",
+          "links": [
+            48
+          ],
+          "widget": {
+            "name": "text"
+          },
+          "slot_index": 0
+        }
+      ],
+      "title": "negative_prompt_primitive",
+      "properties": {
+        "Run widget replace on values": false
+      },
+      "widgets_values": [
+        ""
+      ],
+      "color": "#322",
+      "bgcolor": "#533"
+    },
+    {
+      "id": 82,
+      "type": "ConditioningZeroOut",
+      "pos": [
+        1020.2292389637469,
+        1013.9929825913365
+      ],
+      "size": {
+        "0": 211.60000610351562,
+        "1": 26
+      },
+      "flags": {},
+      "order": 10,
+      "mode": 0,
+      "inputs": [
+        {
+          "name": "conditioning",
+          "type": "CONDITIONING",
+          "link": 176
+        }
+      ],
+      "outputs": [
+        {
+          "name": "CONDITIONING",
+          "type": "CONDITIONING",
+          "links": [
+            171,
+            174
+          ],
+          "shape": 3,
+          "slot_index": 0
+        }
+      ],
+      "title": "2pass_ConditioningZeroOut",
+      "properties": {
+        "Node name for S&R": "ConditioningZeroOut"
+      }
+    },
+    {
+      "id": 27,
+      "type": "CheckpointLoaderSimple",
+      "pos": [
+        1000,
+        50
+      ],
+      "size": {
+        "0": 349.90911865234375,
+        "1": 98
+      },
+      "flags": {},
+      "order": 1,
+      "mode": 0,
+      "outputs": [
+        {
+          "name": "MODEL",
+          "type": "MODEL",
+          "links": [
+            35,
+            169
+          ],
+          "shape": 3,
+          "slot_index": 0
+        },
+        {
+          "name": "CLIP",
+          "type": "CLIP",
+          "links": null,
+          "shape": 3
+        },
+        {
+          "name": "VAE",
+          "type": "VAE",
+          "links": [
+            34,
+            168
+          ],
+          "shape": 3,
+          "slot_index": 2
+        }
+      ],
+      "title": "model_loader_stage_b",
+      "properties": {
+        "Node name for S&R": "CheckpointLoaderSimple"
+      },
+      "widgets_values": [
+        "stable_cascade_stage_b.safetensors"
+      ]
+    },
+    {
+      "id": 20,
+      "type": "CLIPTextEncode",
+      "pos": [
+        500,
+        190
+      ],
+      "size": {
+        "0": 300,
+        "1": 54
+      },
+      "flags": {},
+      "order": 7,
+      "mode": 0,
+      "inputs": [
+        {
+          "name": "clip",
+          "type": "CLIP",
+          "link": 31
+        },
+        {
+          "name": "text",
+          "type": "STRING",
+          "link": 46,
+          "widget": {
+            "name": "text"
+          },
+          "slot_index": 1
+        }
+      ],
+      "outputs": [
+        {
+          "name": "CONDITIONING",
+          "type": "CONDITIONING",
+          "links": [
+            28,
+            41,
+            176,
+            188
+          ],
+          "slot_index": 0
+        }
+      ],
+      "title": "prompt",
+      "properties": {
+        "Node name for S&R": "CLIPTextEncode"
+      },
+      "widgets_values": [
+        "In the dimly lit depths of the cave, a seasoned miner, equipped with his trusty oil lamp, carefully navigates through the shadows. His steady footsteps echo softly against the rocky walls as he ventures deeper into the unknown. colonizer geologist dress code, gold nuggets, deep cold shadows, With each flicker of the lamp's flame, the miner's keen eyes scan the rugged terrain, searching for any glimmer of hope amidst the darkness. Suddenly, a spark of light catches his attention, and his heart quickens with anticipation. Drawing nearer, the miner's breath catches in his throat as he discovers a vein of gold, glistening like a beacon of possibility against the rough stone. With steady hands, he extracts the precious ore, his movements deliberate and sure. Stretching out his hand to see in the dark, lit part of the face, with obvious expression of surprise As the light from his lamp dances across the newfound treasure, a sense of accomplishment washes over the miner. With each passing moment, the once-hidden riches become a tangible symbol of his resilience and determination in the face of adversity. In the quiet solitude of the cave, surrounded by the warmth of his oil lamp and the promise of newfound wealth, the miner stands as a testament to the power of perseverance and the rewards of unwavering dedication."
+      ],
+      "color": "#232",
+      "bgcolor": "#353"
+    },
+    {
+      "id": 21,
+      "type": "CLIPTextEncode",
+      "pos": [
+        500,
+        280
+      ],
+      "size": {
+        "0": 300,
+        "1": 54
+      },
+      "flags": {},
+      "order": 6,
+      "mode": 0,
+      "inputs": [
+        {
+          "name": "clip",
+          "type": "CLIP",
+          "link": 32
+        },
+        {
+          "name": "text",
+          "type": "STRING",
+          "link": 48,
+          "widget": {
+            "name": "text"
+          },
+          "slot_index": 1
+        }
+      ],
+      "outputs": [
+        {
+          "name": "CONDITIONING",
+          "type": "CONDITIONING",
+          "links": [
+            29,
+            189
+          ],
+          "slot_index": 0
+        }
+      ],
+      "title": "negative_prompt",
+      "properties": {
+        "Node name for S&R": "CLIPTextEncode"
+      },
+      "widgets_values": [
+        ""
+      ],
+      "color": "#322",
+      "bgcolor": "#533"
+    },
+    {
+      "id": 79,
+      "type": "VAEDecode",
+      "pos": [
+        1120.2292389637473,
+        1353.9929825913357
+      ],
+      "size": {
+        "0": 210,
+        "1": 46
+      },
+      "flags": {},
+      "order": 19,
+      "mode": 0,
+      "inputs": [
+        {
+          "name": "samples",
+          "type": "LATENT",
+          "link": 167
+        },
+        {
+          "name": "vae",
+          "type": "VAE",
+          "link": 168,
+          "slot_index": 1
+        }
+      ],
+      "outputs": [
+        {
+          "name": "IMAGE",
+          "type": "IMAGE",
+          "links": [
+            195
+          ],
+          "slot_index": 0
+        }
+      ],
+      "title": "2pass_vae_decode",
+      "properties": {
+        "Node name for S&R": "VAEDecode"
+      }
+    },
+    {
+      "id": 81,
+      "type": "StableCascade_StageB_Conditioning",
+      "pos": [
+        1020,
+        924
+      ],
+      "size": {
+        "0": 277.20001220703125,
+        "1": 46
+      },
+      "flags": {},
+      "order": 17,
+      "mode": 0,
+      "inputs": [
+        {
+          "name": "conditioning",
+          "type": "CONDITIONING",
+          "link": 174
+        },
+        {
+          "name": "stage_c",
+          "type": "LATENT",
+          "link": 175
+        }
+      ],
+      "outputs": [
+        {
+          "name": "CONDITIONING",
+          "type": "CONDITIONING",
+          "links": [
+            170
+          ],
+          "shape": 3,
+          "slot_index": 0
+        }
+      ],
+      "title": "2pass_StableCascade_StageB_Conditioning",
+      "properties": {
+        "Node name for S&R": "StableCascade_StageB_Conditioning"
+      }
+    },
+    {
+      "id": 80,
+      "type": "KSampler",
+      "pos": [
+        1020,
+        1085
+      ],
+      "size": {
+        "0": 310,
+        "1": 234
+      },
+      "flags": {},
+      "order": 18,
+      "mode": 0,
+      "inputs": [
+        {
+          "name": "model",
+          "type": "MODEL",
+          "link": 169,
+          "slot_index": 0
+        },
+        {
+          "name": "positive",
+          "type": "CONDITIONING",
+          "link": 170
+        },
+        {
+          "name": "negative",
+          "type": "CONDITIONING",
+          "link": 171
+        },
+        {
+          "name": "latent_image",
+          "type": "LATENT",
+          "link": 180
+        },
+        {
+          "name": "seed",
+          "type": "INT",
+          "link": 173,
+          "widget": {
+            "name": "seed"
+          }
+        }
+      ],
+      "outputs": [
+        {
+          "name": "LATENT",
+          "type": "LATENT",
+          "links": [
+            167
+          ],
+          "slot_index": 0
+        }
+      ],
+      "title": "2pass_sampler_stage_b",
+      "properties": {
+        "Node name for S&R": "KSampler"
+      },
+      "widgets_values": [
+        256522606127804,
+        "fixed",
+        10,
+        1.1,
+        "euler_ancestral",
+        "simple",
+        1
+      ]
+    },
+    {
+      "id": 50,
+      "type": "PrimitiveNode",
+      "pos": [
+        42,
+        290
+      ],
+      "size": {
+        "0": 210,
+        "1": 82
+      },
+      "flags": {
+        "collapsed": false
+      },
+      "order": 2,
+      "mode": 0,
+      "outputs": [
+        {
+          "name": "INT",
+          "type": "INT",
+          "links": [
+            91,
+            92,
+            166,
+            173
+          ],
+          "slot_index": 0,
+          "widget": {
+            "name": "seed"
+          }
+        }
+      ],
+      "title": "seed_primitive",
+      "properties": {
+        "Run widget replace on values": false
+      },
+      "widgets_values": [
+        256522606127804,
+        "randomize"
+      ],
+      "color": "#233",
+      "bgcolor": "#355"
+    },
+    {
+      "id": 19,
+      "type": "KSampler",
+      "pos": [
+        500,
+        370
+      ],
+      "size": {
+        "0": 300,
+        "1": 234
+      },
+      "flags": {},
+      "order": 8,
+      "mode": 0,
+      "inputs": [
+        {
+          "name": "model",
+          "type": "MODEL",
+          "link": 27,
+          "slot_index": 0
+        },
+        {
+          "name": "positive",
+          "type": "CONDITIONING",
+          "link": 28
+        },
+        {
+          "name": "negative",
+          "type": "CONDITIONING",
+          "link": 29
+        },
+        {
+          "name": "latent_image",
+          "type": "LATENT",
+          "link": 60
+        },
+        {
+          "name": "seed",
+          "type": "INT",
+          "link": 91,
+          "widget": {
+            "name": "seed"
+          }
+        }
+      ],
+      "outputs": [
+        {
+          "name": "LATENT",
+          "type": "LATENT",
+          "links": [
+            40
+          ],
+          "slot_index": 0
+        }
+      ],
+      "title": "sampler_stage_c",
+      "properties": {
+        "Node name for S&R": "KSampler"
+      },
+      "widgets_values": [
+        256522606127804,
+        "fixed",
+        20,
+        4,
+        "euler_ancestral",
+        "simple",
+        1
+      ]
+    },
+    {
+      "id": 26,
+      "type": "CheckpointLoaderSimple",
+      "pos": [
+        498,
+        49
+      ],
+      "size": {
+        "0": 336,
+        "1": 98
+      },
+      "flags": {},
+      "order": 3,
+      "mode": 0,
+      "outputs": [
+        {
+          "name": "MODEL",
+          "type": "MODEL",
+          "links": [
+            27,
+            162
+          ],
+          "shape": 3,
+          "slot_index": 0
+        },
+        {
+          "name": "CLIP",
+          "type": "CLIP",
+          "links": [
+            31,
+            32
+          ],
+          "shape": 3,
+          "slot_index": 1
+        },
+        {
+          "name": "VAE",
+          "type": "VAE",
+          "links": [
+            182
+          ],
+          "shape": 3,
+          "slot_index": 2
+        }
+      ],
+      "title": "model_loader_stage_c",
+      "properties": {
+        "Node name for S&R": "CheckpointLoaderSimple"
+      },
+      "widgets_values": [
+        "stable_cascade_stage_c.safetensors"
+      ]
+    },
+    {
+      "id": 23,
+      "type": "KSampler",
+      "pos": [
+        1000,
+        350
+      ],
+      "size": {
+        "0": 310,
+        "1": 234
+      },
+      "flags": {},
+      "order": 12,
+      "mode": 0,
+      "inputs": [
+        {
+          "name": "model",
+          "type": "MODEL",
+          "link": 35,
+          "slot_index": 0
+        },
+        {
+          "name": "positive",
+          "type": "CONDITIONING",
+          "link": 36
+        },
+        {
+          "name": "negative",
+          "type": "CONDITIONING",
+          "link": 37
+        },
+        {
+          "name": "latent_image",
+          "type": "LATENT",
+          "link": 61
+        },
+        {
+          "name": "seed",
+          "type": "INT",
+          "link": 92,
+          "widget": {
+            "name": "seed"
+          }
+        }
+      ],
+      "outputs": [
+        {
+          "name": "LATENT",
+          "type": "LATENT",
+          "links": [
+            33
+          ],
+          "slot_index": 0
+        }
+      ],
+      "title": "sampler_stage_b",
+      "properties": {
+        "Node name for S&R": "KSampler"
+      },
+      "widgets_values": [
+        256522606127804,
+        "fixed",
+        10,
+        1.1,
+        "euler_ancestral",
+        "simple",
+        1
+      ]
+    },
+    {
+      "id": 78,
+      "type": "KSampler",
+      "pos": [
+        524,
+        1204
+      ],
+      "size": {
+        "0": 300,
+        "1": 234
+      },
+      "flags": {},
+      "order": 16,
+      "mode": 0,
+      "inputs": [
+        {
+          "name": "model",
+          "type": "MODEL",
+          "link": 162,
+          "slot_index": 0
+        },
+        {
+          "name": "positive",
+          "type": "CONDITIONING",
+          "link": 188
+        },
+        {
+          "name": "negative",
+          "type": "CONDITIONING",
+          "link": 189
+        },
+        {
+          "name": "latent_image",
+          "type": "LATENT",
+          "link": 181
+        },
+        {
+          "name": "seed",
+          "type": "INT",
+          "link": 166,
+          "widget": {
+            "name": "seed"
+          }
+        }
+      ],
+      "outputs": [
+        {
+          "name": "LATENT",
+          "type": "LATENT",
+          "links": [
+            175
+          ],
+          "slot_index": 0
+        }
+      ],
+      "title": "2pass_sampler_stage_c",
+      "properties": {
+        "Node name for S&R": "KSampler"
+      },
+      "widgets_values": [
+        256522606127804,
+        "fixed",
+        20,
+        4,
+        "euler_ancestral",
+        "simple",
+        0.5
+      ]
+    },
+    {
+      "id": 85,
+      "type": "StableCascade_StageC_VAEEncode",
+      "pos": [
+        522,
+        1094
+      ],
+      "size": {
+        "0": 315,
+        "1": 78
+      },
+      "flags": {},
+      "order": 15,
+      "mode": 0,
+      "inputs": [
+        {
+          "name": "image",
+          "type": "IMAGE",
+          "link": 196
+        },
+        {
+          "name": "vae",
+          "type": "VAE",
+          "link": 182
+        }
+      ],
+      "outputs": [
+        {
+          "name": "stage_c",
+          "type": "LATENT",
+          "links": [
+            181
+          ],
+          "shape": 3,
+          "slot_index": 0
+        },
+        {
+          "name": "stage_b",
+          "type": "LATENT",
+          "links": [
+            180
+          ],
+          "shape": 3,
+          "slot_index": 1
+        }
+      ],
+      "title": "2pass_StableCascade_StageC_VAEEncode",
+      "properties": {
+        "Node name for S&R": "StableCascade_StageC_VAEEncode"
+      },
+      "widgets_values": [
+        32
+      ]
+    },
+    {
+      "id": 92,
+      "type": "ImageScale",
+      "pos": [
+        532,
+        917
+      ],
+      "size": {
+        "0": 315,
+        "1": 130
+      },
+      "flags": {},
+      "order": 14,
+      "mode": 0,
+      "inputs": [
+        {
+          "name": "image",
+          "type": "IMAGE",
+          "link": 197
+        }
+      ],
+      "outputs": [
+        {
+          "name": "IMAGE",
+          "type": "IMAGE",
+          "links": [
+            196
+          ],
+          "shape": 3,
+          "slot_index": 0
+        }
+      ],
+      "title": "latent_upscale",
+      "properties": {
+        "Node name for S&R": "ImageScale"
+      },
+      "widgets_values": [
+        "nearest-exact",
+        1024,
+        1024,
+        "disabled"
+      ]
+    },
+    {
+      "id": 36,
+      "type": "StableCascade_EmptyLatentImage",
+      "pos": [
+        500,
+        650
+      ],
+      "size": {
+        "0": 315,
+        "1": 150
+      },
+      "flags": {},
+      "order": 4,
+      "mode": 0,
+      "outputs": [
+        {
+          "name": "stage_c",
+          "type": "LATENT",
+          "links": [
+            60
+          ],
+          "shape": 3,
+          "slot_index": 0
+        },
+        {
+          "name": "stage_b",
+          "type": "LATENT",
+          "links": [
+            61
+          ],
+          "shape": 3,
+          "slot_index": 1
+        }
+      ],
+      "title": "empty_latent_image",
+      "properties": {
+        "Node name for S&R": "StableCascade_EmptyLatentImage"
+      },
+      "widgets_values": [
+        1024,
+        1024,
+        32,
+        1
+      ]
+    },
+    {
+      "id": 91,
+      "type": "SaveImage",
+      "pos": [
+        1397,
+        946
+      ],
+      "size": {
+        "0": 315,
+        "1": 270
+      },
+      "flags": {},
+      "order": 20,
+      "mode": 0,
+      "inputs": [
+        {
+          "name": "images",
+          "type": "IMAGE",
+          "link": 195
+        }
+      ],
+      "title": "output_image",
+      "properties": {},
+      "widgets_values": [
+        "ComfyUI"
+      ]
+    },
+    {
+      "id": 32,
+      "type": "PrimitiveNode",
+      "pos": [
+        0,
+        420
+      ],
+      "size": {
+        "0": 387.80291748046875,
+        "1": 186.3981170654297
+      },
+      "flags": {},
+      "order": 5,
+      "mode": 0,
+      "outputs": [
+        {
+          "name": "STRING",
+          "type": "STRING",
+          "links": [
+            46
+          ],
+          "widget": {
+            "name": "text"
+          },
+          "slot_index": 0
+        }
+      ],
+      "title": "prompt_primitive",
+      "properties": {
+        "Run widget replace on values": false
+      },
+      "widgets_values": [
+        "In the dimly lit depths of the cave, a seasoned miner, equipped with his trusty oil lamp, carefully navigates through the shadows. His steady footsteps echo softly against the rocky walls as he ventures deeper into the unknown. colonizer geologist dress code, gold nuggets, deep cold shadows, With each flicker of the lamp's flame, the miner's keen eyes scan the rugged terrain, searching for any glimmer of hope amidst the darkness. Suddenly, a spark of light catches his attention, and his heart quickens with anticipation. Drawing nearer, the miner's breath catches in his throat as he discovers a vein of gold, glistening like a beacon of possibility against the rough stone. With steady hands, he extracts the precious ore, his movements deliberate and sure. Stretching out his hand to see in the dark, lit part of the face, with obvious expression of surprise As the light from his lamp dances across the newfound treasure, a sense of accomplishment washes over the miner. With each passing moment, the once-hidden riches become a tangible symbol of his resilience and determination in the face of adversity. In the quiet solitude of the cave, surrounded by the warmth of his oil lamp and the promise of newfound wealth, the miner stands as a testament to the power of perseverance and the rewards of unwavering dedication."
+      ],
+      "color": "#232",
+      "bgcolor": "#353"
+    }
+  ],
+  "links": [
+    [
+      27,
+      26,
+      0,
+      19,
+      0,
+      "MODEL"
+    ],
+    [
+      28,
+      20,
+      0,
+      19,
+      1,
+      "CONDITIONING"
+    ],
+    [
+      29,
+      21,
+      0,
+      19,
+      2,
+      "CONDITIONING"
+    ],
+    [
+      31,
+      26,
+      1,
+      20,
+      0,
+      "CLIP"
+    ],
+    [
+      32,
+      26,
+      1,
+      21,
+      0,
+      "CLIP"
+    ],
+    [
+      33,
+      23,
+      0,
+      22,
+      0,
+      "LATENT"
+    ],
+    [
+      34,
+      27,
+      2,
+      22,
+      1,
+      "VAE"
+    ],
+    [
+      35,
+      27,
+      0,
+      23,
+      0,
+      "MODEL"
+    ],
+    [
+      36,
+      24,
+      0,
+      23,
+      1,
+      "CONDITIONING"
+    ],
+    [
+      37,
+      25,
+      0,
+      23,
+      2,
+      "CONDITIONING"
+    ],
+    [
+      39,
+      25,
+      0,
+      24,
+      0,
+      "CONDITIONING"
+    ],
+    [
+      40,
+      19,
+      0,
+      24,
+      1,
+      "LATENT"
+    ],
+    [
+      41,
+      20,
+      0,
+      25,
+      0,
+      "CONDITIONING"
+    ],
+    [
+      46,
+      32,
+      0,
+      20,
+      1,
+      "STRING"
+    ],
+    [
+      48,
+      33,
+      0,
+      21,
+      1,
+      "STRING"
+    ],
+    [
+      60,
+      36,
+      0,
+      19,
+      3,
+      "LATENT"
+    ],
+    [
+      61,
+      36,
+      1,
+      23,
+      3,
+      "LATENT"
+    ],
+    [
+      91,
+      50,
+      0,
+      19,
+      4,
+      "INT"
+    ],
+    [
+      92,
+      50,
+      0,
+      23,
+      4,
+      "INT"
+    ],
+    [
+      162,
+      26,
+      0,
+      78,
+      0,
+      "MODEL"
+    ],
+    [
+      166,
+      50,
+      0,
+      78,
+      4,
+      "INT"
+    ],
+    [
+      167,
+      80,
+      0,
+      79,
+      0,
+      "LATENT"
+    ],
+    [
+      168,
+      27,
+      2,
+      79,
+      1,
+      "VAE"
+    ],
+    [
+      169,
+      27,
+      0,
+      80,
+      0,
+      "MODEL"
+    ],
+    [
+      170,
+      81,
+      0,
+      80,
+      1,
+      "CONDITIONING"
+    ],
+    [
+      171,
+      82,
+      0,
+      80,
+      2,
+      "CONDITIONING"
+    ],
+    [
+      173,
+      50,
+      0,
+      80,
+      4,
+      "INT"
+    ],
+    [
+      174,
+      82,
+      0,
+      81,
+      0,
+      "CONDITIONING"
+    ],
+    [
+      175,
+      78,
+      0,
+      81,
+      1,
+      "LATENT"
+    ],
+    [
+      176,
+      20,
+      0,
+      82,
+      0,
+      "CONDITIONING"
+    ],
+    [
+      180,
+      85,
+      1,
+      80,
+      3,
+      "LATENT"
+    ],
+    [
+      181,
+      85,
+      0,
+      78,
+      3,
+      "LATENT"
+    ],
+    [
+      182,
+      26,
+      2,
+      85,
+      1,
+      "VAE"
+    ],
+    [
+      188,
+      20,
+      0,
+      78,
+      1,
+      "CONDITIONING"
+    ],
+    [
+      189,
+      21,
+      0,
+      78,
+      2,
+      "CONDITIONING"
+    ],
+    [
+      195,
+      79,
+      0,
+      91,
+      0,
+      "IMAGE"
+    ],
+    [
+      196,
+      92,
+      0,
+      85,
+      0,
+      "IMAGE"
+    ],
+    [
+      197,
+      22,
+      0,
+      92,
+      0,
+      "IMAGE"
+    ]
+  ],
+  "groups": [
+    {
+      "title": "Stable Cascade",
+      "bounding": [
+        490,
+        -20,
+        1257,
+        820
+      ],
+      "color": "#3f789e",
+      "font_size": 24
+    },
+    {
+      "title": "Stable Cascade (HiRes Pass)",
+      "bounding": [
+        494,
+        830,
+        1269,
+        600
+      ],
+      "color": "#3f789e",
+      "font_size": 24
+    }
+  ],
+  "config": {},
+  "extra": {},
+  "version": 0.4
+}
diff --git a/hordelib/pipelines/pipeline_controlnet.json b/hordelib/pipelines/pipeline_controlnet.json
index 7c8388a8..a5656428 100644
--- a/hordelib/pipelines/pipeline_controlnet.json
+++ b/hordelib/pipelines/pipeline_controlnet.json
@@ -1,212 +1,269 @@
 {
-    "3": {
-        "inputs": {
-            "seed": 123456789,
-            "steps": 25,
-            "cfg": 7.5,
-            "sampler_name": "dpmpp_2m",
-            "scheduler": "karras",
-            "denoise": 1.0,
-            "model": [
-                "47",
-                0
-            ],
-            "positive": [
-                "23",
-                0
-            ],
-            "negative": [
-                "7",
-                0
-            ],
-            "latent_image": [
-                "5",
-                0
-            ]
-        },
-        "class_type": "KSampler"
-    },
-    "5": {
-        "inputs": {
-            "width": 512,
-            "height": 512,
-            "batch_size": 1
-        },
-        "class_type": "EmptyLatentImage"
-    },
-    "7": {
-        "inputs": {
-            "text": "",
-            "clip": [
-                "47",
-                1
-            ]
-        },
-        "class_type": "CLIPTextEncode"
-    },
-    "9": {
-        "inputs": {
-            "filename_prefix": "ComfyUI",
-            "images": [
-                "49",
-                0
-            ]
-        },
-        "class_type": "SaveImage"
-    },
-    "20": {
-        "inputs": {
-            "image": "test_db0 (1).jpg",
-            "choose file to upload": "image"
-        },
-        "class_type": "LoadImage"
-    },
-    "23": {
-        "inputs": {
-            "strength": 0.8500000000000003,
-            "conditioning": [
-                "24",
-                0
-            ],
-            "control_net": [
-                "33",
-                0
-            ],
-            "image": [
-                "34",
-                0
-            ]
-        },
-        "class_type": "ControlNetApply"
-    },
-    "24": {
-        "inputs": {
-            "text": "a man walking in the snow\n\n\n",
-            "clip": [
-                "47",
-                1
-            ]
-        },
-        "class_type": "CLIPTextEncode"
-    },
-    "33": {
-        "inputs": {
-            "control_net_name": "control_scribble_fp16.safetensors",
-            "model": [
-                "47",
-                0
-            ]
-        },
-        "class_type": "DiffControlNetLoader"
-    },
-    "34": {
-        "inputs": {
-            "low_threshold": 100,
-            "high_threshold": 200,
-            "l2gradient": "disable",
-            "image": [
-                "20",
-                0
-            ]
-        },
-        "class_type": "CannyEdgePreprocessor"
-    },
-    "37": {
-        "inputs": {
-            "rm_nearest": 0,
-            "rm_background": 0,
-            "image": [
-                "20",
-                0
-            ]
-        },
-        "class_type": "LeReS-DepthMapPreprocessor"
-    },
-    "39": {
-        "inputs": {
-            "image": [
-                "20",
-                0
-            ]
-        },
-        "class_type": "HEDPreprocessor"
-    },
-    "40": {
-        "inputs": {
-            "a": 6.283185307179586,
-            "bg_threshold": 0.05,
-            "image": [
-                "20",
-                0
-            ]
-        },
-        "class_type": "MiDaS-NormalMapPreprocessor"
-    },
-    "42": {
-        "inputs": {
-            "detect_hand": "disable",
-            "image": [
-                "20",
-                0
-            ]
-        },
-        "class_type": "OpenposePreprocessor"
-    },
-    "43": {
-        "inputs": {
-            "image": [
-                "20",
-                0
-            ]
-        },
-        "class_type": "SemSegPreprocessor"
-    },
-    "44": {
-        "inputs": {
-            "image": [
-                "20",
-                0
-            ]
-        },
-        "class_type": "ScribblePreprocessor"
-    },
-    "45": {
-        "inputs": {
-            "image": [
-                "20",
-                0
-            ]
-        },
-        "class_type": "FakeScribblePreprocessor"
-    },
-    "46": {
-        "inputs": {
-            "score_threshold": 0.15,
-            "dist_threshold": 1,
-            "image": [
-                "20",
-                0
-            ]
-        },
-        "class_type": "M-LSDPreprocessor"
-    },
-    "47": {
-        "inputs": {
-            "ckpt_name": "Deliberate.ckpt"
-        },
-        "class_type": "CheckpointLoaderSimple"
-    },
-    "49": {
-        "inputs": {
-            "samples": [
-                "3",
-                0
-            ],
-            "vae": [
-                "47",
-                2
-            ]
-        },
-        "class_type": "VAEDecode"
+  "3": {
+    "inputs": {
+      "seed": 123456789,
+      "steps": 25,
+      "cfg": 7.5,
+      "sampler_name": "dpmpp_2m",
+      "scheduler": "karras",
+      "denoise": 1,
+      "model": [
+        "47",
+        0
+      ],
+      "positive": [
+        "23",
+        0
+      ],
+      "negative": [
+        "7",
+        0
+      ],
+      "latent_image": [
+        "5",
+        0
+      ]
+    },
+    "class_type": "KSampler",
+    "_meta": {
+      "title": "sampler"
+    }
+  },
+  "5": {
+    "inputs": {
+      "width": 512,
+      "height": 512,
+      "batch_size": 1
+    },
+    "class_type": "EmptyLatentImage",
+    "_meta": {
+      "title": "empty_latent_image"
+    }
+  },
+  "7": {
+    "inputs": {
+      "text": "",
+      "clip": [
+        "47",
+        1
+      ]
+    },
+    "class_type": "CLIPTextEncode",
+    "_meta": {
+      "title": "negative_prompt"
+    }
+  },
+  "9": {
+    "inputs": {
+      "filename_prefix": "ComfyUI",
+      "images": [
+        "49",
+        0
+      ]
+    },
+    "class_type": "SaveImage",
+    "_meta": {
+      "title": "output_image"
+    }
+  },
+  "20": {
+    "inputs": {
+      "image": "test_db0 (1).jpg",
+      "upload": "image"
+    },
+    "class_type": "LoadImage",
+    "_meta": {
+      "title": "image_loader"
+    }
+  },
+  "23": {
+    "inputs": {
+      "strength": 0.8500000000000003,
+      "conditioning": [
+        "24",
+        0
+      ],
+      "control_net": [
+        "33",
+        0
+      ],
+      "image": [
+        "34",
+        0
+      ]
+    },
+    "class_type": "ControlNetApply",
+    "_meta": {
+      "title": "controlnet_apply"
+    }
+  },
+  "24": {
+    "inputs": {
+      "text": "a man walking in the snow\n\n\n",
+      "clip": [
+        "47",
+        1
+      ]
+    },
+    "class_type": "CLIPTextEncode",
+    "_meta": {
+      "title": "prompt"
+    }
+  },
+  "33": {
+    "inputs": {
+      "control_net_name": "control_scribble_fp16.safetensors",
+      "model": [
+        "47",
+        0
+      ]
+    },
+    "class_type": "DiffControlNetLoader",
+    "_meta": {
+      "title": "controlnet_model_loader"
+    }
+  },
+  "34": {
+    "inputs": {
+      "low_threshold": 100,
+      "high_threshold": 200,
+      "l2gradient": "disable",
+      "image": [
+        "20",
+        0
+      ]
+    },
+    "class_type": "CannyEdgePreprocessor",
+    "_meta": {
+      "title": "canny"
+    }
+  },
+  "37": {
+    "inputs": {
+      "rm_nearest": 0,
+      "rm_background": 0,
+      "image": [
+        "20",
+        0
+      ]
+    },
+    "class_type": "LeReS-DepthMapPreprocessor",
+    "_meta": {
+      "title": "depth"
+    }
+  },
+  "39": {
+    "inputs": {
+      "image": [
+        "20",
+        0
+      ]
+    },
+    "class_type": "HEDPreprocessor",
+    "_meta": {
+      "title": "hed"
+    }
+  },
+  "40": {
+    "inputs": {
+      "a": 6.283185307179586,
+      "bg_threshold": 0.05,
+      "image": [
+        "20",
+        0
+      ]
+    },
+    "class_type": "MiDaS-NormalMapPreprocessor",
+    "_meta": {
+      "title": "normal"
+    }
+  },
+  "42": {
+    "inputs": {
+      "detect_hand": "disable",
+      "image": [
+        "20",
+        0
+      ]
+    },
+    "class_type": "OpenposePreprocessor",
+    "_meta": {
+      "title": "openpose"
+    }
+  },
+  "43": {
+    "inputs": {
+      "image": [
+        "20",
+        0
+      ]
+    },
+    "class_type": "SemSegPreprocessor",
+    "_meta": {
+      "title": "seg"
+    }
+  },
+  "44": {
+    "inputs": {
+      "image": [
+        "20",
+        0
+      ]
+    },
+    "class_type": "ScribblePreprocessor",
+    "_meta": {
+      "title": "scribble"
+    }
+  },
+  "45": {
+    "inputs": {
+      "image": [
+        "20",
+        0
+      ]
+    },
+    "class_type": "FakeScribblePreprocessor",
+    "_meta": {
+      "title": "fakescribble"
+    }
+  },
+  "46": {
+    "inputs": {
+      "score_threshold": 0.15,
+      "dist_threshold": 1,
+      "image": [
+        "20",
+        0
+      ]
+    },
+    "class_type": "M-LSDPreprocessor",
+    "_meta": {
+      "title": "mlsd"
+    }
+  },
+  "47": {
+    "inputs": {
+      "ckpt_name": "Deliberate.ckpt"
+    },
+    "class_type": "CheckpointLoaderSimple",
+    "_meta": {
+      "title": "model_loader"
+    }
+  },
+  "49": {
+    "inputs": {
+      "samples": [
+        "3",
+        0
+      ],
+      "vae": [
+        "47",
+        2
+      ]
+    },
+    "class_type": "VAEDecode",
+    "_meta": {
+      "title": "vae_decode"
     }
+  }
 }
diff --git a/hordelib/pipelines/pipeline_controlnet_annotator.json b/hordelib/pipelines/pipeline_controlnet_annotator.json
index 6f83190d..1fe7e531 100644
--- a/hordelib/pipelines/pipeline_controlnet_annotator.json
+++ b/hordelib/pipelines/pipeline_controlnet_annotator.json
@@ -1,110 +1,144 @@
 {
-    "9": {
-        "inputs": {
-            "filename_prefix": "ComfyUI",
-            "images": [
-                "45",
-                0
-            ]
-        },
-        "class_type": "SaveImage"
+  "9": {
+    "inputs": {
+      "filename_prefix": "ComfyUI",
+      "images": [
+        "45",
+        0
+      ]
     },
-    "20": {
-        "inputs": {
-            "image": "horde_image_facefix_codeformers.webp",
-            "choose file to upload": "image"
-        },
-        "class_type": "LoadImage"
+    "class_type": "SaveImage",
+    "_meta": {
+      "title": "output_image"
+    }
+  },
+  "20": {
+    "inputs": {
+      "image": "horde_image_facefix_codeformers.webp",
+      "upload": "image"
     },
-    "34": {
-        "inputs": {
-            "low_threshold": 100,
-            "high_threshold": 200,
-            "l2gradient": "disable",
-            "image": [
-                "20",
-                0
-            ]
-        },
-        "class_type": "CannyEdgePreprocessor"
+    "class_type": "LoadImage",
+    "_meta": {
+      "title": "image_loader"
+    }
+  },
+  "34": {
+    "inputs": {
+      "low_threshold": 100,
+      "high_threshold": 200,
+      "l2gradient": "disable",
+      "image": [
+        "20",
+        0
+      ]
     },
-    "37": {
-        "inputs": {
-            "rm_nearest": 0,
-            "rm_background": 0,
-            "image": [
-                "20",
-                0
-            ]
-        },
-        "class_type": "LeReS-DepthMapPreprocessor"
+    "class_type": "CannyEdgePreprocessor",
+    "_meta": {
+      "title": "canny"
+    }
+  },
+  "37": {
+    "inputs": {
+      "rm_nearest": 0,
+      "rm_background": 0,
+      "image": [
+        "20",
+        0
+      ]
     },
-    "39": {
-        "inputs": {
-            "image": [
-                "20",
-                0
-            ]
-        },
-        "class_type": "HEDPreprocessor"
+    "class_type": "LeReS-DepthMapPreprocessor",
+    "_meta": {
+      "title": "depth"
+    }
+  },
+  "39": {
+    "inputs": {
+      "image": [
+        "20",
+        0
+      ]
     },
-    "40": {
-        "inputs": {
-            "a": 6.283185307179586,
-            "bg_threshold": 0.05,
-            "image": [
-                "20",
-                0
-            ]
-        },
-        "class_type": "MiDaS-NormalMapPreprocessor"
+    "class_type": "HEDPreprocessor",
+    "_meta": {
+      "title": "hed"
+    }
+  },
+  "40": {
+    "inputs": {
+      "a": 6.283185307179586,
+      "bg_threshold": 0.05,
+      "image": [
+        "20",
+        0
+      ]
     },
-    "42": {
-        "inputs": {
-            "detect_hand": "disable",
-            "image": [
-                "20",
-                0
-            ]
-        },
-        "class_type": "OpenposePreprocessor"
+    "class_type": "MiDaS-NormalMapPreprocessor",
+    "_meta": {
+      "title": "normal"
+    }
+  },
+  "42": {
+    "inputs": {
+      "detect_hand": "disable",
+      "image": [
+        "20",
+        0
+      ]
     },
-    "43": {
-        "inputs": {
-            "image": [
-                "20",
-                0
-            ]
-        },
-        "class_type": "SemSegPreprocessor"
+    "class_type": "OpenposePreprocessor",
+    "_meta": {
+      "title": "openpose"
+    }
+  },
+  "43": {
+    "inputs": {
+      "image": [
+        "20",
+        0
+      ]
     },
-    "44": {
-        "inputs": {
-            "image": [
-                "20",
-                0
-            ]
-        },
-        "class_type": "ScribblePreprocessor"
+    "class_type": "SemSegPreprocessor",
+    "_meta": {
+      "title": "seg"
+    }
+  },
+  "44": {
+    "inputs": {
+      "image": [
+        "20",
+        0
+      ]
     },
-    "45": {
-        "inputs": {
-            "image": [
-                "20",
-                0
-            ]
-        },
-        "class_type": "FakeScribblePreprocessor"
+    "class_type": "ScribblePreprocessor",
+    "_meta": {
+      "title": "scribble"
+    }
+  },
+  "45": {
+    "inputs": {
+      "safe": "enable",
+      "image": [
+        "20",
+        0
+      ]
+    },
+    "class_type": "FakeScribblePreprocessor",
+    "_meta": {
+      "title": "fakescribble"
+    }
+  },
+  "46": {
+    "inputs": {
+      "score_threshold": 0.15,
+      "dist_threshold": 1,
+      "image": [
+        "20",
+        0
+      ]
     },
-    "46": {
-        "inputs": {
-            "score_threshold": 0.150,
-            "dist_threshold": 1.0,
-            "image": [
-                "20",
-                0
-            ]
-        },
-        "class_type": "M-LSDPreprocessor"
+    "class_type": "M-LSDPreprocessor",
+    "_meta": {
+      "title": "mlsd"
     }
+  }
 }
diff --git a/hordelib/pipelines/pipeline_controlnet_hires_fix.json b/hordelib/pipelines/pipeline_controlnet_hires_fix.json
index 40185234..2dce6386 100644
--- a/hordelib/pipelines/pipeline_controlnet_hires_fix.json
+++ b/hordelib/pipelines/pipeline_controlnet_hires_fix.json
@@ -1,252 +1,315 @@
 {
-    "3": {
-        "inputs": {
-            "seed": 123456789,
-            "steps": 25,
-            "cfg": 7.5,
-            "sampler_name": "dpmpp_2m",
-            "scheduler": "karras",
-            "denoise": 1.0,
-            "model": [
-                "47",
-                0
-            ],
-            "positive": [
-                "23",
-                0
-            ],
-            "negative": [
-                "7",
-                0
-            ],
-            "latent_image": [
-                "5",
-                0
-            ]
-        },
-        "class_type": "KSampler"
-    },
-    "5": {
-        "inputs": {
-            "width": 512,
-            "height": 512,
-            "batch_size": 1
-        },
-        "class_type": "EmptyLatentImage"
-    },
-    "7": {
-        "inputs": {
-            "text": "",
-            "clip": [
-                "47",
-                1
-            ]
-        },
-        "class_type": "CLIPTextEncode"
-    },
-    "9": {
-        "inputs": {
-            "filename_prefix": "ComfyUI",
-            "images": [
-                "52",
-                0
-            ]
-        },
-        "class_type": "SaveImage"
-    },
-    "20": {
-        "inputs": {
-            "image": "test_db0 (2).jpg",
-            "choose file to upload": "image"
-        },
-        "class_type": "LoadImage"
-    },
-    "23": {
-        "inputs": {
-            "strength": 0.8500000000000003,
-            "conditioning": [
-                "24",
-                0
-            ],
-            "control_net": [
-                "33",
-                0
-            ],
-            "image": [
-                "34",
-                0
-            ]
-        },
-        "class_type": "ControlNetApply"
-    },
-    "24": {
-        "inputs": {
-            "text": "a man walking in the jungle\n\n\n",
-            "clip": [
-                "47",
-                1
-            ]
-        },
-        "class_type": "CLIPTextEncode"
-    },
-    "33": {
-        "inputs": {
-            "control_net_name": "control_scribble_fp16.safetensors",
-            "model": [
-                "47",
-                0
-            ]
-        },
-        "class_type": "DiffControlNetLoader"
-    },
-    "34": {
-        "inputs": {
-            "low_threshold": 100,
-            "high_threshold": 200,
-            "l2gradient": "disable",
-            "image": [
-                "20",
-                0
-            ]
-        },
-        "class_type": "CannyEdgePreprocessor"
-    },
-    "37": {
-        "inputs": {
-            "rm_nearest": 0,
-            "rm_background": 0,
-            "image": [
-                "20",
-                0
-            ]
-        },
-        "class_type": "LeReS-DepthMapPreprocessor"
-    },
-    "39": {
-        "inputs": {
-            "image": [
-                "20",
-                0
-            ]
-        },
-        "class_type": "HEDPreprocessor"
-    },
-    "40": {
-        "inputs": {
-            "a": 6.283185307179586,
-            "bg_threshold": 0.05,
-            "image": [
-                "20",
-                0
-            ]
-        },
-        "class_type": "MiDaS-NormalMapPreprocessor"
-    },
-    "42": {
-        "inputs": {
-            "detect_hand": "disable",
-            "image": [
-                "20",
-                0
-            ]
-        },
-        "class_type": "OpenposePreprocessor"
-    },
-    "43": {
-        "inputs": {
-            "image": [
-                "20",
-                0
-            ]
-        },
-        "class_type": "SemSegPreprocessor"
-    },
-    "44": {
-        "inputs": {
-            "image": [
-                "20",
-                0
-            ]
-        },
-        "class_type": "ScribblePreprocessor"
-    },
-    "45": {
-        "inputs": {
-            "image": [
-                "20",
-                0
-            ]
-        },
-        "class_type": "FakeScribblePreprocessor"
-    },
-    "46": {
-        "inputs": {
-            "score_threshold": 0.15,
-            "dist_threshold": 1,
-            "image": [
-                "20",
-                0
-            ]
-        },
-        "class_type": "M-LSDPreprocessor"
-    },
-    "47": {
-        "inputs": {
-            "ckpt_name": "Deliberate.ckpt"
-        },
-        "class_type": "CheckpointLoaderSimple"
-    },
-    "49": {
-        "inputs": {
-            "upscale_method": "nearest-exact",
-            "width": 768,
-            "height": 768,
-            "crop": "disabled",
-            "samples": [
-                "3",
-                0
-            ]
-        },
-        "class_type": "LatentUpscale"
-    },
-    "50": {
-        "inputs": {
-            "seed": 287046928977701,
-            "steps": 15,
-            "cfg": 8.0,
-            "sampler_name": "dpmpp_2m",
-            "scheduler": "simple",
-            "denoise": 0.6,
-            "model": [
-                "47",
-                0
-            ],
-            "positive": [
-                "24",
-                0
-            ],
-            "negative": [
-                "7",
-                0
-            ],
-            "latent_image": [
-                "49",
-                0
-            ]
-        },
-        "class_type": "KSampler"
-    },
-    "52": {
-        "inputs": {
-            "samples": [
-                "50",
-                0
-            ],
-            "vae": [
-                "47",
-                2
-            ]
-        },
-        "class_type": "VAEDecode"
+  "3": {
+    "inputs": {
+      "seed": 123456789,
+      "steps": 25,
+      "cfg": 7.5,
+      "sampler_name": "dpmpp_2m",
+      "scheduler": "karras",
+      "denoise": 1,
+      "model": [
+        "47",
+        0
+      ],
+      "positive": [
+        "23",
+        0
+      ],
+      "negative": [
+        "7",
+        0
+      ],
+      "latent_image": [
+        "5",
+        0
+      ]
+    },
+    "class_type": "KSampler",
+    "_meta": {
+      "title": "sampler"
+    }
+  },
+  "5": {
+    "inputs": {
+      "width": 512,
+      "height": 512,
+      "batch_size": 1
+    },
+    "class_type": "EmptyLatentImage",
+    "_meta": {
+      "title": "empty_latent_image"
+    }
+  },
+  "7": {
+    "inputs": {
+      "text": "",
+      "clip": [
+        "47",
+        1
+      ]
+    },
+    "class_type": "CLIPTextEncode",
+    "_meta": {
+      "title": "negative_prompt"
+    }
+  },
+  "9": {
+    "inputs": {
+      "filename_prefix": "ComfyUI",
+      "images": [
+        "52",
+        0
+      ]
+    },
+    "class_type": "SaveImage",
+    "_meta": {
+      "title": "output_image"
+    }
+  },
+  "20": {
+    "inputs": {
+      "image": "example.png",
+      "upload": "image"
+    },
+    "class_type": "LoadImage",
+    "_meta": {
+      "title": "image_loader"
+    }
+  },
+  "23": {
+    "inputs": {
+      "strength": 0.8500000000000003,
+      "conditioning": [
+        "24",
+        0
+      ],
+      "control_net": [
+        "33",
+        0
+      ],
+      "image": [
+        "34",
+        0
+      ]
+    },
+    "class_type": "ControlNetApply",
+    "_meta": {
+      "title": "controlnet_apply"
+    }
+  },
+  "24": {
+    "inputs": {
+      "text": "a man walking in the jungle\n\n\n",
+      "clip": [
+        "47",
+        1
+      ]
+    },
+    "class_type": "CLIPTextEncode",
+    "_meta": {
+      "title": "prompt"
+    }
+  },
+  "33": {
+    "inputs": {
+      "control_net_name": "control_scribble_fp16.safetensors",
+      "model": [
+        "47",
+        0
+      ]
+    },
+    "class_type": "DiffControlNetLoader",
+    "_meta": {
+      "title": "controlnet_model_loader"
+    }
+  },
+  "34": {
+    "inputs": {
+      "low_threshold": 100,
+      "high_threshold": 200,
+      "l2gradient": "disable",
+      "image": [
+        "20",
+        0
+      ]
+    },
+    "class_type": "CannyEdgePreprocessor",
+    "_meta": {
+      "title": "canny"
+    }
+  },
+  "37": {
+    "inputs": {
+      "rm_nearest": 0,
+      "rm_background": 0,
+      "image": [
+        "20",
+        0
+      ]
+    },
+    "class_type": "LeReS-DepthMapPreprocessor",
+    "_meta": {
+      "title": "depth"
+    }
+  },
+  "39": {
+    "inputs": {
+      "image": [
+        "20",
+        0
+      ]
+    },
+    "class_type": "HEDPreprocessor",
+    "_meta": {
+      "title": "hed"
+    }
+  },
+  "40": {
+    "inputs": {
+      "a": 6.283185307179586,
+      "bg_threshold": 0.05,
+      "image": [
+        "20",
+        0
+      ]
+    },
+    "class_type": "MiDaS-NormalMapPreprocessor",
+    "_meta": {
+      "title": "normal"
+    }
+  },
+  "42": {
+    "inputs": {
+      "detect_hand": "disable",
+      "image": [
+        "20",
+        0
+      ]
+    },
+    "class_type": "OpenposePreprocessor",
+    "_meta": {
+      "title": "openpose"
+    }
+  },
+  "43": {
+    "inputs": {
+      "image": [
+        "20",
+        0
+      ]
+    },
+    "class_type": "SemSegPreprocessor",
+    "_meta": {
+      "title": "seg"
+    }
+  },
+  "44": {
+    "inputs": {
+      "image": [
+        "20",
+        0
+      ]
+    },
+    "class_type": "ScribblePreprocessor",
+    "_meta": {
+      "title": "scribble"
+    }
+  },
+  "45": {
+    "inputs": {
+      "image": [
+        "20",
+        0
+      ]
+    },
+    "class_type": "FakeScribblePreprocessor",
+    "_meta": {
+      "title": "fakescribble"
+    }
+  },
+  "46": {
+    "inputs": {
+      "score_threshold": 0.15,
+      "dist_threshold": 1,
+      "image": [
+        "20",
+        0
+      ]
+    },
+    "class_type": "M-LSDPreprocessor",
+    "_meta": {
+      "title": "mlsd"
+    }
+  },
+  "47": {
+    "inputs": {
+      "ckpt_name": "Deliberate.ckpt"
+    },
+    "class_type": "CheckpointLoaderSimple",
+    "_meta": {
+      "title": "model_loader"
+    }
+  },
+  "49": {
+    "inputs": {
+      "upscale_method": "nearest-exact",
+      "width": 768,
+      "height": 768,
+      "crop": "disabled",
+      "samples": [
+        "3",
+        0
+      ]
+    },
+    "class_type": "LatentUpscale",
+    "_meta": {
+      "title": "latent_upscale"
+    }
+  },
+  "50": {
+    "inputs": {
+      "seed": 287046928977701,
+      "steps": 15,
+      "cfg": 8,
+      "sampler_name": "dpmpp_2m",
+      "scheduler": "simple",
+      "denoise": 0.6,
+      "model": [
+        "47",
+        0
+      ],
+      "positive": [
+        "24",
+        0
+      ],
+      "negative": [
+        "7",
+        0
+      ],
+      "latent_image": [
+        "49",
+        0
+      ]
+    },
+    "class_type": "KSampler",
+    "_meta": {
+      "title": "upscale_sampler"
+    }
+  },
+  "52": {
+    "inputs": {
+      "samples": [
+        "50",
+        0
+      ],
+      "vae": [
+        "47",
+        2
+      ]
+    },
+    "class_type": "VAEDecode",
+    "_meta": {
+      "title": "vae_decode"
     }
+  }
 }
diff --git a/hordelib/pipelines/pipeline_image_facefix.json b/hordelib/pipelines/pipeline_image_facefix.json
index 1d19e90d..a00f479a 100644
--- a/hordelib/pipelines/pipeline_image_facefix.json
+++ b/hordelib/pipelines/pipeline_image_facefix.json
@@ -1,39 +1,51 @@
 {
-    "1": {
-        "inputs": {
-            "image": "test_facefix.png",
-            "choose file to upload": "image"
-        },
-        "class_type": "LoadImage"
+  "1": {
+    "inputs": {
+      "image": "test_facefix.png",
+      "upload": "image"
     },
-    "4": {
-        "inputs": {
-            "model_name": "CodeFormers.pth"
-        },
-        "class_type": "UpscaleModelLoader"
+    "class_type": "LoadImage",
+    "_meta": {
+      "title": "image_loader"
+    }
+  },
+  "4": {
+    "inputs": {
+      "model_name": "CodeFormers.pth"
     },
-    "6": {
-        "inputs": {
-            "filename_prefix": "ComfyUI",
-            "images": [
-                "8",
-                0
-            ]
-        },
-        "class_type": "SaveImage"
+    "class_type": "UpscaleModelLoader",
+    "_meta": {
+      "title": "model_loader"
+    }
+  },
+  "6": {
+    "inputs": {
+      "filename_prefix": "ComfyUI",
+      "images": [
+        "8",
+        0
+      ]
     },
-    "8": {
-        "inputs": {
-            "facedetection": "retinaface_resnet50",
-            "upscale_model": [
-                "4",
-                0
-            ],
-            "image": [
-                "1",
-                0
-            ]
-        },
-        "class_type": "FaceRestoreWithModel"
+    "class_type": "SaveImage",
+    "_meta": {
+      "title": "output_image"
     }
+  },
+  "8": {
+    "inputs": {
+      "facedetection": "retinaface_resnet50",
+      "upscale_model": [
+        "4",
+        0
+      ],
+      "image": [
+        "1",
+        0
+      ]
+    },
+    "_meta": {
+      "title": "face_restore_with_model"
+    },
+    "class_type": "FaceRestoreWithModel"
+  }
 }
diff --git a/hordelib/pipelines/pipeline_image_upscale.json b/hordelib/pipelines/pipeline_image_upscale.json
index d449def1..13196f8c 100644
--- a/hordelib/pipelines/pipeline_image_upscale.json
+++ b/hordelib/pipelines/pipeline_image_upscale.json
@@ -1,38 +1,50 @@
 {
-    "1": {
-        "inputs": {
-            "image": "ComfyUI_00010_.png",
-            "choose file to upload": "image"
-        },
-        "class_type": "LoadImage"
+  "1": {
+    "inputs": {
+      "image": "ComfyUI_00010_.png",
+      "upload": "image"
     },
-    "3": {
-        "inputs": {
-            "upscale_model": [
-                "4",
-                0
-            ],
-            "image": [
-                "1",
-                0
-            ]
-        },
-        "class_type": "ImageUpscaleWithModel"
+    "class_type": "LoadImage",
+    "_meta": {
+      "title": "image_loader"
+    }
+  },
+  "3": {
+    "inputs": {
+      "upscale_model": [
+        "4",
+        0
+      ],
+      "image": [
+        "1",
+        0
+      ]
+    },
+    "class_type": "ImageUpscaleWithModel",
+    "_meta": {
+      "title": "Upscale Image (using Model)"
+    }
+  },
+  "4": {
+    "inputs": {
+      "model_name": "NMKD_Siax.pth"
     },
-    "4": {
-        "inputs": {
-            "model_name": "NMKD_Siax.pth"
-        },
-        "class_type": "UpscaleModelLoader"
+    "class_type": "UpscaleModelLoader",
+    "_meta": {
+      "title": "model_loader"
+    }
+  },
+  "6": {
+    "inputs": {
+      "filename_prefix": "ComfyUI",
+      "images": [
+        "3",
+        0
+      ]
     },
-    "6": {
-        "inputs": {
-            "filename_prefix": "ComfyUI",
-            "images": [
-                "3",
-                0
-            ]
-        },
-        "class_type": "SaveImage"
+    "class_type": "SaveImage",
+    "_meta": {
+      "title": "output_image"
     }
+  }
 }
diff --git a/hordelib/pipelines/pipeline_stable_cascade.json b/hordelib/pipelines/pipeline_stable_cascade.json
index 846c885d..8ab8b829 100644
--- a/hordelib/pipelines/pipeline_stable_cascade.json
+++ b/hordelib/pipelines/pipeline_stable_cascade.json
@@ -123,7 +123,7 @@
     },
     "class_type": "StableCascade_EmptyLatentImage",
     "_meta": {
-      "title": "stable_cascade_empty_latent_image"
+      "title": "empty_latent_image"
     }
   },
   "36": {
diff --git a/hordelib/pipelines/pipeline_stable_cascade_2pass.json b/hordelib/pipelines/pipeline_stable_cascade_2pass.json
new file mode 100644
index 00000000..95e363f7
--- /dev/null
+++ b/hordelib/pipelines/pipeline_stable_cascade_2pass.json
@@ -0,0 +1,312 @@
+{
+  "19": {
+    "inputs": {
+      "seed": 256522606127804,
+      "steps": 20,
+      "cfg": 4,
+      "sampler_name": "euler_ancestral",
+      "scheduler": "simple",
+      "denoise": 1,
+      "model": [
+        "26",
+        0
+      ],
+      "positive": [
+        "20",
+        0
+      ],
+      "negative": [
+        "21",
+        0
+      ],
+      "latent_image": [
+        "36",
+        0
+      ]
+    },
+    "class_type": "KSampler",
+    "_meta": {
+      "title": "sampler_stage_c"
+    }
+  },
+  "20": {
+    "inputs": {
+      "text": "In the dimly lit depths of the cave, a seasoned miner, equipped with his trusty oil lamp, carefully navigates through the shadows. His steady footsteps echo softly against the rocky walls as he ventures deeper into the unknown. colonizer geologist dress code, gold nuggets, deep cold shadows, With each flicker of the lamp's flame, the miner's keen eyes scan the rugged terrain, searching for any glimmer of hope amidst the darkness. Suddenly, a spark of light catches his attention, and his heart quickens with anticipation. Drawing nearer, the miner's breath catches in his throat as he discovers a vein of gold, glistening like a beacon of possibility against the rough stone. With steady hands, he extracts the precious ore, his movements deliberate and sure. Stretching out his hand to see in the dark, lit part of the face, with obvious expression of surprise As the light from his lamp dances across the newfound treasure, a sense of accomplishment washes over the miner. With each passing moment, the once-hidden riches become a tangible symbol of his resilience and determination in the face of adversity. In the quiet solitude of the cave, surrounded by the warmth of his oil lamp and the promise of newfound wealth, the miner stands as a testament to the power of perseverance and the rewards of unwavering dedication.",
+      "clip": [
+        "26",
+        1
+      ]
+    },
+    "class_type": "CLIPTextEncode",
+    "_meta": {
+      "title": "prompt"
+    }
+  },
+  "21": {
+    "inputs": {
+      "text": "",
+      "clip": [
+        "26",
+        1
+      ]
+    },
+    "class_type": "CLIPTextEncode",
+    "_meta": {
+      "title": "negative_prompt"
+    }
+  },
+  "22": {
+    "inputs": {
+      "samples": [
+        "23",
+        0
+      ],
+      "vae": [
+        "27",
+        2
+      ]
+    },
+    "class_type": "VAEDecode",
+    "_meta": {
+      "title": "VAE Decode"
+    }
+  },
+  "23": {
+    "inputs": {
+      "seed": 256522606127804,
+      "steps": 10,
+      "cfg": 1.1,
+      "sampler_name": "euler_ancestral",
+      "scheduler": "simple",
+      "denoise": 1,
+      "model": [
+        "27",
+        0
+      ],
+      "positive": [
+        "24",
+        0
+      ],
+      "negative": [
+        "25",
+        0
+      ],
+      "latent_image": [
+        "36",
+        1
+      ]
+    },
+    "class_type": "KSampler",
+    "_meta": {
+      "title": "sampler_stage_b"
+    }
+  },
+  "24": {
+    "inputs": {
+      "conditioning": [
+        "25",
+        0
+      ],
+      "stage_c": [
+        "19",
+        0
+      ]
+    },
+    "class_type": "StableCascade_StageB_Conditioning",
+    "_meta": {
+      "title": "StableCascade_StageB_Conditioning"
+    }
+  },
+  "25": {
+    "inputs": {
+      "conditioning": [
+        "20",
+        0
+      ]
+    },
+    "class_type": "ConditioningZeroOut",
+    "_meta": {
+      "title": "ConditioningZeroOut"
+    }
+  },
+  "26": {
+    "inputs": {
+      "ckpt_name": "stable_cascade_stage_c.safetensors"
+    },
+    "class_type": "CheckpointLoaderSimple",
+    "_meta": {
+      "title": "model_loader_stage_c"
+    }
+  },
+  "27": {
+    "inputs": {
+      "ckpt_name": "stable_cascade_stage_b.safetensors"
+    },
+    "class_type": "CheckpointLoaderSimple",
+    "_meta": {
+      "title": "model_loader_stage_b"
+    }
+  },
+  "36": {
+    "inputs": {
+      "width": 1024,
+      "height": 1024,
+      "compression": 32,
+      "batch_size": 1
+    },
+    "class_type": "StableCascade_EmptyLatentImage",
+    "_meta": {
+      "title": "empty_latent_image"
+    }
+  },
+  "78": {
+    "inputs": {
+      "seed": 256522606127804,
+      "steps": 20,
+      "cfg": 4,
+      "sampler_name": "euler_ancestral",
+      "scheduler": "simple",
+      "denoise": 0.5,
+      "model": [
+        "26",
+        0
+      ],
+      "positive": [
+        "20",
+        0
+      ],
+      "negative": [
+        "21",
+        0
+      ],
+      "latent_image": [
+        "85",
+        0
+      ]
+    },
+    "class_type": "KSampler",
+    "_meta": {
+      "title": "2pass_sampler_stage_c"
+    }
+  },
+  "79": {
+    "inputs": {
+      "samples": [
+        "80",
+        0
+      ],
+      "vae": [
+        "27",
+        2
+      ]
+    },
+    "class_type": "VAEDecode",
+    "_meta": {
+      "title": "2pass_vae_decode"
+    }
+  },
+  "80": {
+    "inputs": {
+      "seed": 256522606127804,
+      "steps": 10,
+      "cfg": 1.1,
+      "sampler_name": "euler_ancestral",
+      "scheduler": "simple",
+      "denoise": 1,
+      "model": [
+        "27",
+        0
+      ],
+      "positive": [
+        "81",
+        0
+      ],
+      "negative": [
+        "82",
+        0
+      ],
+      "latent_image": [
+        "85",
+        1
+      ]
+    },
+    "class_type": "KSampler",
+    "_meta": {
+      "title": "2pass_sampler_stage_b"
+    }
+  },
+  "81": {
+    "inputs": {
+      "conditioning": [
+        "82",
+        0
+      ],
+      "stage_c": [
+        "78",
+        0
+      ]
+    },
+    "class_type": "StableCascade_StageB_Conditioning",
+    "_meta": {
+      "title": "2pass_StableCascade_StageB_Conditioning"
+    }
+  },
+  "82": {
+    "inputs": {
+      "conditioning": [
+        "20",
+        0
+      ]
+    },
+    "class_type": "ConditioningZeroOut",
+    "_meta": {
+      "title": "2pass_ConditioningZeroOut"
+    }
+  },
+  "85": {
+    "inputs": {
+      "compression": 32,
+      "image": [
+        "92",
+        0
+      ],
+      "vae": [
+        "26",
+        2
+      ]
+    },
+    "class_type": "StableCascade_StageC_VAEEncode",
+    "_meta": {
+      "title": "2pass_StableCascade_StageC_VAEEncode"
+    }
+  },
+  "91": {
+    "inputs": {
+      "filename_prefix": "ComfyUI",
+      "images": [
+        "79",
+        0
+      ]
+    },
+    "class_type": "SaveImage",
+    "_meta": {
+      "title": "output_image"
+    }
+  },
+  "92": {
+    "inputs": {
+      "upscale_method": "nearest-exact",
+      "width": 1024,
+      "height": 1024,
+      "crop": "disabled",
+      "image": [
+        "22",
+        0
+      ]
+    },
+    "class_type": "ImageScale",
+    "_meta": {
+      "title": "latent_upscale"
+    }
+  }
+}
diff --git a/hordelib/pipelines/pipeline_stable_diffusion.json b/hordelib/pipelines/pipeline_stable_diffusion.json
index 063793b3..983dd28d 100644
--- a/hordelib/pipelines/pipeline_stable_diffusion.json
+++ b/hordelib/pipelines/pipeline_stable_diffusion.json
@@ -1,127 +1,159 @@
 {
-    "3": {
-        "inputs": {
-            "seed": 843159458049729,
-            "steps": 20,
-            "cfg": 8.0,
-            "sampler_name": "euler",
-            "scheduler": "normal",
-            "denoise": 1.0,
-            "model": [
-                "4",
-                0
-            ],
-            "positive": [
-                "6",
-                0
-            ],
-            "negative": [
-                "7",
-                0
-            ],
-            "latent_image": [
-                "5",
-                0
-            ]
-        },
-        "class_type": "KSampler"
+  "3": {
+    "inputs": {
+      "seed": 62706718437716,
+      "steps": 20,
+      "cfg": 8,
+      "sampler_name": "euler",
+      "scheduler": "normal",
+      "denoise": 1,
+      "model": [
+        "4",
+        0
+      ],
+      "positive": [
+        "6",
+        0
+      ],
+      "negative": [
+        "7",
+        0
+      ],
+      "latent_image": [
+        "5",
+        0
+      ]
     },
-    "4": {
-        "inputs": {
-            "ckpt_name": "Deliberate.ckpt"
-        },
-        "class_type": "CheckpointLoaderSimple"
+    "class_type": "KSampler",
+    "_meta": {
+      "title": "sampler"
+    }
+  },
+  "4": {
+    "inputs": {
+      "ckpt_name": "Deliberate.ckpt"
     },
-    "5": {
-        "inputs": {
-            "width": 512,
-            "height": 512,
-            "batch_size": 1
-        },
-        "class_type": "EmptyLatentImage"
+    "class_type": "CheckpointLoaderSimple",
+    "_meta": {
+      "title": "model_loader"
+    }
+  },
+  "5": {
+    "inputs": {
+      "width": 512,
+      "height": 512,
+      "batch_size": 1
     },
-    "6": {
-        "inputs": {
-            "text": "dinosaur ",
-            "clip": [
-                "10",
-                0
-            ]
-        },
-        "class_type": "CLIPTextEncode"
+    "class_type": "EmptyLatentImage",
+    "_meta": {
+      "title": "empty_latent_image"
+    }
+  },
+  "6": {
+    "inputs": {
+      "text": "dinosaur ",
+      "clip": [
+        "10",
+        0
+      ]
     },
-    "7": {
-        "inputs": {
-            "text": "painting, drawing, artwork",
-            "clip": [
-                "10",
-                0
-            ]
-        },
-        "class_type": "CLIPTextEncode"
+    "class_type": "CLIPTextEncode",
+    "_meta": {
+      "title": "prompt"
+    }
+  },
+  "7": {
+    "inputs": {
+      "text": "painting, drawing, artwork",
+      "clip": [
+        "10",
+        0
+      ]
     },
-    "9": {
-        "inputs": {
-            "filename_prefix": "ComfyUI",
-            "images": [
-                "14",
-                0
-            ]
-        },
-        "class_type": "SaveImage"
+    "class_type": "CLIPTextEncode",
+    "_meta": {
+      "title": "negative_prompt"
+    }
+  },
+  "9": {
+    "inputs": {
+      "filename_prefix": "ComfyUI",
+      "images": [
+        "14",
+        0
+      ]
     },
-    "10": {
-        "inputs": {
-            "stop_at_clip_layer": -1,
-            "clip": [
-                "4",
-                1
-            ]
-        },
-        "class_type": "CLIPSetLastLayer"
+    "class_type": "SaveImage",
+    "_meta": {
+      "title": "output_image"
+    }
+  },
+  "10": {
+    "inputs": {
+      "stop_at_clip_layer": -1,
+      "clip": [
+        "4",
+        1
+      ]
     },
-    "11": {
-        "inputs": {
-            "image": "example.png",
-            "choose file to upload": "image"
-        },
-        "class_type": "LoadImage"
+    "class_type": "CLIPSetLastLayer",
+    "_meta": {
+      "title": "clip_skip"
+    }
+  },
+  "11": {
+    "inputs": {
+      "image": "example.png",
+      "upload": "image"
     },
-    "12": {
-        "inputs": {
-            "pixels": [
-                "15",
-                0
-            ],
-            "vae": [
-                "4",
-                2
-            ]
-        },
-        "class_type": "VAEEncode"
+    "class_type": "LoadImage",
+    "_meta": {
+      "title": "image_loader"
+    }
+  },
+  "12": {
+    "inputs": {
+      "pixels": [
+        "15",
+        0
+      ],
+      "vae": [
+        "4",
+        2
+      ]
     },
-    "14": {
-        "inputs": {
-            "samples": [
-                "3",
-                0
-            ],
-            "vae": [
-                "4",
-                2
-            ]
-        },
-        "class_type": "VAEDecode"
+    "class_type": "VAEEncode",
+    "_meta": {
+      "title": "vae_encode"
+    }
+  },
+  "14": {
+    "inputs": {
+      "samples": [
+        "3",
+        0
+      ],
+      "vae": [
+        "4",
+        2
+      ]
+    },
+    "class_type": "VAEDecode",
+    "_meta": {
+      "title": "vae_decode"
+    }
+  },
+  "15": {
+    "inputs": {
+      "amount": 1,
+      "image": [
+        "11",
+        0
+      ]
     },
-    "15": {
-        "id": 15,
-        "inputs": {
-            "amount": 1,
-            "image": [
-                "11",
-                0
-            ]
-        },
-        "class_type": "RepeatImageBatch"
+    "class_type": "RepeatImageBatch",
+    "_meta": {
+      "title": "repeat_image_batch"
     }
+  }
 }
diff --git a/hordelib/pipelines/pipeline_stable_diffusion_hires_fix.json b/hordelib/pipelines/pipeline_stable_diffusion_hires_fix.json
index efa9aeba..fed95de1 100644
--- a/hordelib/pipelines/pipeline_stable_diffusion_hires_fix.json
+++ b/hordelib/pipelines/pipeline_stable_diffusion_hires_fix.json
@@ -1,167 +1,205 @@
 {
-    "3": {
-        "inputs": {
-            "seed": 1096039238624618,
-            "steps": 12,
-            "cfg": 8.0,
-            "sampler_name": "dpmpp_sde",
-            "scheduler": "normal",
-            "denoise": 1.0,
-            "model": [
-                "16",
-                0
-            ],
-            "positive": [
-                "6",
-                0
-            ],
-            "negative": [
-                "7",
-                0
-            ],
-            "latent_image": [
-                "5",
-                0
-            ]
-        },
-        "class_type": "KSampler"
+  "3": {
+    "inputs": {
+      "seed": 458841867575267,
+      "steps": 12,
+      "cfg": 8,
+      "sampler_name": "dpmpp_sde",
+      "scheduler": "normal",
+      "denoise": 1,
+      "model": [
+        "16",
+        0
+      ],
+      "positive": [
+        "6",
+        0
+      ],
+      "negative": [
+        "7",
+        0
+      ],
+      "latent_image": [
+        "5",
+        0
+      ]
     },
-    "5": {
-        "inputs": {
-            "width": 256,
-            "height": 256,
-            "batch_size": 1
-        },
-        "class_type": "EmptyLatentImage"
+    "class_type": "KSampler",
+    "_meta": {
+      "title": "sampler"
+    }
+  },
+  "5": {
+    "inputs": {
+      "width": 256,
+      "height": 256,
+      "batch_size": 1
     },
-    "6": {
-        "inputs": {
-            "text": "(masterpiece) HDR victorian portrait painting of (girl), blonde hair, mountain nature, blue sky\n",
-            "clip": [
-                "17",
-                0
-            ]
-        },
-        "class_type": "CLIPTextEncode"
+    "class_type": "EmptyLatentImage",
+    "_meta": {
+      "title": "empty_latent_image"
+    }
+  },
+  "6": {
+    "inputs": {
+      "text": "(masterpiece) HDR victorian portrait painting of (girl), blonde hair, mountain nature, blue sky\n",
+      "clip": [
+        "17",
+        0
+      ]
     },
-    "7": {
-        "inputs": {
-            "text": "bad hands, text, watermark\n",
-            "clip": [
-                "17",
-                0
-            ]
-        },
-        "class_type": "CLIPTextEncode"
+    "class_type": "CLIPTextEncode",
+    "_meta": {
+      "title": "prompt"
+    }
+  },
+  "7": {
+    "inputs": {
+      "text": "bad hands, text, watermark\n",
+      "clip": [
+        "17",
+        0
+      ]
     },
-    "10": {
-        "inputs": {
-            "upscale_method": "nearest-exact",
-            "width": 512,
-            "height": 512,
-            "crop": "disabled",
-            "samples": [
-                "3",
-                0
-            ]
-        },
-        "class_type": "LatentUpscale"
+    "class_type": "CLIPTextEncode",
+    "_meta": {
+      "title": "negative_prompt"
+    }
+  },
+  "10": {
+    "inputs": {
+      "upscale_method": "nearest-exact",
+      "width": 512,
+      "height": 512,
+      "crop": "disabled",
+      "samples": [
+        "3",
+        0
+      ]
     },
-    "11": {
-        "inputs": {
-            "seed": 347776641452924,
-            "steps": 14,
-            "cfg": 8.0,
-            "sampler_name": "dpmpp_2m",
-            "scheduler": "simple",
-            "denoise": 0.5,
-            "model": [
-                "16",
-                0
-            ],
-            "positive": [
-                "6",
-                0
-            ],
-            "negative": [
-                "7",
-                0
-            ],
-            "latent_image": [
-                "10",
-                0
-            ]
-        },
-        "class_type": "KSampler"
+    "class_type": "LatentUpscale",
+    "_meta": {
+      "title": "latent_upscale"
+    }
+  },
+  "11": {
+    "inputs": {
+      "seed": 65484213431324,
+      "steps": 14,
+      "cfg": 8,
+      "sampler_name": "dpmpp_2m",
+      "scheduler": "simple",
+      "denoise": 0.5,
+      "model": [
+        "16",
+        0
+      ],
+      "positive": [
+        "6",
+        0
+      ],
+      "negative": [
+        "7",
+        0
+      ],
+      "latent_image": [
+        "10",
+        0
+      ]
     },
-    "12": {
-        "inputs": {
-            "filename_prefix": "ComfyUI",
-            "images": [
-                "21",
-                0
-            ]
-        },
-        "class_type": "SaveImage"
+    "class_type": "KSampler",
+    "_meta": {
+      "title": "upscale_sampler"
+    }
+  },
+  "12": {
+    "inputs": {
+      "filename_prefix": "ComfyUI",
+      "images": [
+        "21",
+        0
+      ]
     },
-    "16": {
-        "inputs": {
-            "ckpt_name": "Deliberate.ckpt"
-        },
-        "class_type": "CheckpointLoaderSimple"
+    "class_type": "SaveImage",
+    "_meta": {
+      "title": "output_image"
+    }
+  },
+  "16": {
+    "inputs": {
+      "ckpt_name": "Deliberate.ckpt"
     },
-    "17": {
-        "inputs": {
-            "stop_at_clip_layer": -1,
-            "clip": [
-                "16",
-                1
-            ]
-        },
-        "class_type": "CLIPSetLastLayer"
+    "class_type": "CheckpointLoaderSimple",
+    "_meta": {
+      "title": "model_loader"
+    }
+  },
+  "17": {
+    "inputs": {
+      "stop_at_clip_layer": -1,
+      "clip": [
+        "16",
+        1
+      ]
+    },
+    "class_type": "CLIPSetLastLayer",
+    "_meta": {
+      "title": "clip_skip"
+    }
+  },
+  "18": {
+    "inputs": {
+      "image": "example.png",
+      "upload": "image"
     },
-    "18": {
-        "inputs": {
-            "image": "example.png",
-            "choose file to upload": "image"
-        },
-        "class_type": "LoadImage"
+    "class_type": "LoadImage",
+    "_meta": {
+      "title": "image_loader"
+    }
+  },
+  "19": {
+    "inputs": {
+      "pixels": [
+        "22",
+        0
+      ],
+      "vae": [
+        "16",
+        2
+      ]
     },
-    "19": {
-        "inputs": {
-            "pixels": [
-                "22",
-                0
-            ],
-            "vae": [
-                "16",
-                2
-            ]
-        },
-        "class_type": "VAEEncode"
+    "class_type": "VAEEncode",
+    "_meta": {
+      "title": "vae_encode"
+    }
+  },
+  "21": {
+    "inputs": {
+      "samples": [
+        "11",
+        0
+      ],
+      "vae": [
+        "16",
+        2
+      ]
     },
-    "21": {
-        "inputs": {
-            "samples": [
-                "11",
-                0
-            ],
-            "vae": [
-                "16",
-                2
-            ]
-        },
-        "class_type": "VAEDecode"
+    "class_type": "VAEDecode",
+    "_meta": {
+      "title": "vae_decode"
+    }
+  },
+  "22": {
+    "inputs": {
+      "amount": 1,
+      "image": [
+        "18",
+        0
+      ]
     },
-    "22": {
-        "id": 22,
-        "inputs": {
-            "amount": 1,
-            "image": [
-                "18",
-                0
-            ]
-        },
-        "class_type": "RepeatImageBatch"
+    "class_type": "RepeatImageBatch",
+    "_meta": {
+      "title": "repeat_image_batch"
     }
+  }
 }
diff --git a/hordelib/pipelines/pipeline_stable_diffusion_img2img_mask.json b/hordelib/pipelines/pipeline_stable_diffusion_img2img_mask.json
index 81209f56..abee2050 100644
--- a/hordelib/pipelines/pipeline_stable_diffusion_img2img_mask.json
+++ b/hordelib/pipelines/pipeline_stable_diffusion_img2img_mask.json
@@ -1,140 +1,175 @@
 {
-    "3": {
-        "inputs": {
-            "seed": 681890209860356,
-            "steps": 20,
-            "cfg": 8.0,
-            "sampler_name": "euler",
-            "scheduler": "normal",
-            "denoise": 0.46999999999999953,
-            "model": [
-                "4",
-                0
-            ],
-            "positive": [
-                "6",
-                0
-            ],
-            "negative": [
-                "7",
-                0
-            ],
-            "latent_image": [
-                "16",
-                0
-            ]
-        },
-        "class_type": "KSampler"
+  "3": {
+    "inputs": {
+      "seed": 501193305970493,
+      "steps": 20,
+      "cfg": 8,
+      "sampler_name": "euler",
+      "scheduler": "normal",
+      "denoise": 0.46999999999999953,
+      "model": [
+        "4",
+        0
+      ],
+      "positive": [
+        "6",
+        0
+      ],
+      "negative": [
+        "7",
+        0
+      ],
+      "latent_image": [
+        "16",
+        0
+      ]
     },
-    "4": {
-        "inputs": {
-            "ckpt_name": "Deliberate.ckpt"
-        },
-        "class_type": "CheckpointLoaderSimple"
+    "class_type": "KSampler",
+    "_meta": {
+      "title": "sampler"
+    }
+  },
+  "4": {
+    "inputs": {
+      "ckpt_name": "Deliberate.ckpt"
+    },
+    "class_type": "CheckpointLoaderSimple",
+    "_meta": {
+      "title": "model_loader"
+    }
+  },
+  "5": {
+    "inputs": {
+      "width": 512,
+      "height": 512,
+      "batch_size": 1
     },
-    "5": {
-        "inputs": {
-            "width": 512,
-            "height": 512,
-            "batch_size": 1
-        },
-        "class_type": "EmptyLatentImage"
+    "class_type": "EmptyLatentImage",
+    "_meta": {
+      "title": "empty_latent_image"
+    }
+  },
+  "6": {
+    "inputs": {
+      "text": "dinosaur ",
+      "clip": [
+        "10",
+        0
+      ]
     },
-    "6": {
-        "inputs": {
-            "text": "dinosaur ",
-            "clip": [
-                "10",
-                0
-            ]
-        },
-        "class_type": "CLIPTextEncode"
+    "class_type": "CLIPTextEncode",
+    "_meta": {
+      "title": "prompt"
+    }
+  },
+  "7": {
+    "inputs": {
+      "text": "painting, drawing, artwork",
+      "clip": [
+        "10",
+        0
+      ]
     },
-    "7": {
-        "inputs": {
-            "text": "painting, drawing, artwork",
-            "clip": [
-                "10",
-                0
-            ]
-        },
-        "class_type": "CLIPTextEncode"
+    "class_type": "CLIPTextEncode",
+    "_meta": {
+      "title": "negative_prompt"
+    }
+  },
+  "9": {
+    "inputs": {
+      "filename_prefix": "ComfyUI",
+      "images": [
+        "15",
+        0
+      ]
     },
-    "9": {
-        "inputs": {
-            "filename_prefix": "ComfyUI",
-            "images": [
-                "15",
-                0
-            ]
-        },
-        "class_type": "SaveImage"
+    "class_type": "SaveImage",
+    "_meta": {
+      "title": "output_image"
+    }
+  },
+  "10": {
+    "inputs": {
+      "stop_at_clip_layer": -1,
+      "clip": [
+        "4",
+        1
+      ]
     },
-    "10": {
-        "inputs": {
-            "stop_at_clip_layer": -1,
-            "clip": [
-                "4",
-                1
-            ]
-        },
-        "class_type": "CLIPSetLastLayer"
+    "class_type": "CLIPSetLastLayer",
+    "_meta": {
+      "title": "clip_skip"
+    }
+  },
+  "11": {
+    "inputs": {
+      "image": "test_inpaint_alpha.png",
+      "upload": "image"
     },
-    "11": {
-        "inputs": {
-            "image": "test_inpaint_alpha.png",
-            "choose file to upload": "image"
-        },
-        "class_type": "LoadImage"
+    "class_type": "LoadImage",
+    "_meta": {
+      "title": "image_loader"
+    }
+  },
+  "15": {
+    "inputs": {
+      "samples": [
+        "3",
+        0
+      ],
+      "vae": [
+        "4",
+        2
+      ]
     },
-    "15": {
-        "inputs": {
-            "samples": [
-                "3",
-                0
-            ],
-            "vae": [
-                "4",
-                2
-            ]
-        },
-        "class_type": "VAEDecode"
+    "class_type": "VAEDecode",
+    "_meta": {
+      "title": "VAE Decode"
+    }
+  },
+  "16": {
+    "inputs": {
+      "samples": [
+        "17",
+        0
+      ],
+      "mask": [
+        "11",
+        1
+      ]
     },
-    "16": {
-        "inputs": {
-            "samples": [
-                "17",
-                0
-            ],
-            "mask": [
-                "11",
-                1
-            ]
-        },
-        "class_type": "SetLatentNoiseMask"
+    "class_type": "SetLatentNoiseMask",
+    "_meta": {
+      "title": "set_latent_noise_mask"
+    }
+  },
+  "17": {
+    "inputs": {
+      "pixels": [
+        "20",
+        0
+      ],
+      "vae": [
+        "4",
+        2
+      ]
     },
-    "17": {
-        "inputs": {
-            "pixels": [
-                "20",
-                0
-            ],
-            "vae": [
-                "4",
-                2
-            ]
-        },
-        "class_type": "VAEEncode"
+    "class_type": "VAEEncode",
+    "_meta": {
+      "title": "vae_encode_for_mask"
+    }
+  },
+  "20": {
+    "inputs": {
+      "amount": 1,
+      "image": [
+        "11",
+        0
+      ]
     },
-    "20": {
-        "id": 20,
-        "inputs": {
-            "amount": 1,
-            "image": [
-                "11",
-                0
-            ]
-        },
-        "class_type": "RepeatImageBatch"
+    "class_type": "RepeatImageBatch",
+    "_meta": {
+      "title": "repeat_image_batch"
     }
+  }
 }
diff --git a/hordelib/pipelines/pipeline_stable_diffusion_paint.json b/hordelib/pipelines/pipeline_stable_diffusion_paint.json
index 7849197c..5b07ca51 100644
--- a/hordelib/pipelines/pipeline_stable_diffusion_paint.json
+++ b/hordelib/pipelines/pipeline_stable_diffusion_paint.json
@@ -1,132 +1,164 @@
 {
-    "3": {
-        "inputs": {
-            "seed": 77570063643350,
-            "steps": 20,
-            "cfg": 8.0,
-            "sampler_name": "euler",
-            "scheduler": "normal",
-            "denoise": 1.0,
-            "model": [
-                "4",
-                0
-            ],
-            "positive": [
-                "6",
-                0
-            ],
-            "negative": [
-                "7",
-                0
-            ],
-            "latent_image": [
-                "14",
-                0
-            ]
-        },
-        "class_type": "KSampler"
+  "3": {
+    "inputs": {
+      "seed": 845153640392169,
+      "steps": 20,
+      "cfg": 8,
+      "sampler_name": "euler",
+      "scheduler": "normal",
+      "denoise": 1,
+      "model": [
+        "4",
+        0
+      ],
+      "positive": [
+        "6",
+        0
+      ],
+      "negative": [
+        "7",
+        0
+      ],
+      "latent_image": [
+        "14",
+        0
+      ]
     },
-    "4": {
-        "inputs": {
-            "ckpt_name": "Deliberate.ckpt"
-        },
-        "class_type": "CheckpointLoaderSimple"
+    "class_type": "KSampler",
+    "_meta": {
+      "title": "sampler"
+    }
+  },
+  "4": {
+    "inputs": {
+      "ckpt_name": "Deliberate.ckpt"
     },
-    "5": {
-        "inputs": {
-            "width": 512,
-            "height": 512,
-            "batch_size": 1
-        },
-        "class_type": "EmptyLatentImage"
+    "class_type": "CheckpointLoaderSimple",
+    "_meta": {
+      "title": "model_loader"
+    }
+  },
+  "5": {
+    "inputs": {
+      "width": 512,
+      "height": 512,
+      "batch_size": 1
     },
-    "6": {
-        "inputs": {
-            "text": "dinosaur ",
-            "clip": [
-                "10",
-                0
-            ]
-        },
-        "class_type": "CLIPTextEncode"
+    "class_type": "EmptyLatentImage",
+    "_meta": {
+      "title": "empty_latent_image"
+    }
+  },
+  "6": {
+    "inputs": {
+      "text": "dinosaur ",
+      "clip": [
+        "10",
+        0
+      ]
     },
-    "7": {
-        "inputs": {
-            "text": "painting, drawing, artwork",
-            "clip": [
-                "10",
-                0
-            ]
-        },
-        "class_type": "CLIPTextEncode"
+    "class_type": "CLIPTextEncode",
+    "_meta": {
+      "title": "prompt"
+    }
+  },
+  "7": {
+    "inputs": {
+      "text": "painting, drawing, artwork",
+      "clip": [
+        "10",
+        0
+      ]
     },
-    "9": {
-        "inputs": {
-            "filename_prefix": "ComfyUI",
-            "images": [
-                "15",
-                0
-            ]
-        },
-        "class_type": "SaveImage"
+    "class_type": "CLIPTextEncode",
+    "_meta": {
+      "title": "negative_prompt"
+    }
+  },
+  "9": {
+    "inputs": {
+      "filename_prefix": "ComfyUI",
+      "images": [
+        "15",
+        0
+      ]
     },
-    "10": {
-        "inputs": {
-            "stop_at_clip_layer": -1,
-            "clip": [
-                "4",
-                1
-            ]
-        },
-        "class_type": "CLIPSetLastLayer"
+    "class_type": "SaveImage",
+    "_meta": {
+      "title": "output_image"
+    }
+  },
+  "10": {
+    "inputs": {
+      "stop_at_clip_layer": -1,
+      "clip": [
+        "4",
+        1
+      ]
     },
-    "11": {
-        "inputs": {
-            "image": "test_inpaint_alpha.png",
-            "choose file to upload": "image"
-        },
-        "class_type": "LoadImage"
+    "class_type": "CLIPSetLastLayer",
+    "_meta": {
+      "title": "clip_skip"
+    }
+  },
+  "11": {
+    "inputs": {
+      "image": "test_inpaint_alpha.png",
+      "upload": "image"
     },
-    "14": {
-        "inputs": {
-            "grow_mask_by": 6,
-            "pixels": [
-                "16",
-                0
-            ],
-            "vae": [
-                "4",
-                2
-            ],
-            "mask": [
-                "11",
-                1
-            ]
-        },
-        "class_type": "VAEEncodeForInpaint"
+    "class_type": "LoadImage",
+    "_meta": {
+      "title": "image_loader"
+    }
+  },
+  "14": {
+    "inputs": {
+      "grow_mask_by": 6,
+      "pixels": [
+        "16",
+        0
+      ],
+      "vae": [
+        "4",
+        2
+      ],
+      "mask": [
+        "11",
+        1
+      ]
     },
-    "15": {
-        "inputs": {
-            "samples": [
-                "3",
-                0
-            ],
-            "vae": [
-                "4",
-                2
-            ]
-        },
-        "class_type": "VAEDecode"
+    "class_type": "VAEEncodeForInpaint",
+    "_meta": {
+      "title": "vae_encode"
+    }
+  },
+  "15": {
+    "inputs": {
+      "samples": [
+        "3",
+        0
+      ],
+      "vae": [
+        "4",
+        2
+      ]
+    },
+    "class_type": "VAEDecode",
+    "_meta": {
+      "title": "VAE Decode"
+    }
+  },
+  "16": {
+    "inputs": {
+      "amount": 1,
+      "image": [
+        "11",
+        0
+      ]
     },
-    "16": {
-        "id": 16,
-        "inputs": {
-            "amount": 1,
-            "image": [
-                "11",
-                0
-            ]
-        },
-        "class_type": "RepeatImageBatch"
+    "class_type": "RepeatImageBatch",
+    "_meta": {
+      "title": "repeat_image_batch"
     }
+  }
 }
diff --git a/hordelib/utils/image_utils.py b/hordelib/utils/image_utils.py
index 7479ab47..947af7d0 100644
--- a/hordelib/utils/image_utils.py
+++ b/hordelib/utils/image_utils.py
@@ -5,18 +5,111 @@
 from loguru import logger
 from PIL import Image, ImageOps, PngImagePlugin, UnidentifiedImageError
 
+IMAGE_CHUNK_SIZE = 64
+
+DEFAULT_IMAGE_MIN_RESOLUTION = 512
+DEFAULT_HIGHER_RES_MAX_RESOLUTION = 1024
+
 
 class ImageUtils:
+
     @classmethod
-    def calculate_source_image_size(cls, width, height):
-        if width > 512 and height > 512:
-            final_width = width
-            final_height = height
-            first_pass_ratio = min(final_height / 512, final_width / 512)
-            width = (int(final_width / first_pass_ratio) // 64) * 64
-            height = (int(final_height / first_pass_ratio) // 64) * 64
-            return (width, height)
-        return (width, height)
+    def resize_image_dimensions(
+        cls,
+        width: int,
+        height: int,
+        desired_dimension: int,
+        use_min: bool,
+    ) -> tuple[int, int]:
+        """Resize the image dimensions to have one side equal to the desired resolution, keeping the aspect ratio.
+
+        - If use_min is True, the side with the minimum length will be resized to the desired resolution.
+          - For example, if the image is 1024x2048 and the desired resolution is 512, the image will be
+            resized to 512x1024. (As desired for 512x trained models)
+        - If use_min is False, the side with the maximum length will be resized to the desired resolution.
+          - For example, if the image is 1024x2048 and the desired resolution is 1024, the image will be
+            resized to 512x1024. (As desired for 1024x trained models)
+        - If the image is smaller than the desired resolution, the image will not be resized.
+
+        Args:
+            width (int): The width of the image.
+            height (int): The height of the image.
+            desired_dimension (int): The desired single side resolution.
+            use_min (bool): Whether to use the minimum or maximum side.
+
+        Returns:
+            tuple[int, int]: The target first pass width and height of the image.
+        """
+        if desired_dimension is None or desired_dimension <= 0:
+            raise ValueError("desired_resolution must be a positive integer.")
+
+        if width <= 0 or height <= 0:
+            raise ValueError("width and height must be positive integers.")
+
+        if width < desired_dimension and height < desired_dimension:
+            return width, height
+
+        if use_min:
+            ratio = min(
+                height / desired_dimension,
+                width / desired_dimension,
+            )
+        else:
+            ratio = max(
+                height / desired_dimension,
+                width / desired_dimension,
+            )
+
+        new_width = int(width // (ratio * IMAGE_CHUNK_SIZE)) * IMAGE_CHUNK_SIZE
+        new_height = int(height // (ratio * IMAGE_CHUNK_SIZE)) * IMAGE_CHUNK_SIZE
+
+        return new_width, new_height
+
+    @classmethod
+    def get_first_pass_image_resolution_min(
+        cls,
+        width: int,
+        height: int,
+        min_dimension: int = DEFAULT_IMAGE_MIN_RESOLUTION,
+    ):
+        """Resize the image dimensions to have one side equal to the desired resolution, keeping the aspect ratio.
+
+        - If the image is larger than the desired resolution, the side with the minimum length will be resized to the
+          desired resolution.
+        - If the image is smaller than the desired resolution, the image will not be resized.
+
+        """
+        if width > min_dimension and height > min_dimension:
+            return cls.resize_image_dimensions(
+                width,
+                height,
+                desired_dimension=min_dimension,
+                use_min=True,
+            )
+        return width, height
+
+    @classmethod
+    def get_first_pass_image_resolution_max(
+        cls,
+        width: int,
+        height: int,
+        max_dimension: int = DEFAULT_HIGHER_RES_MAX_RESOLUTION,
+    ):
+        """Resize the image dimensions to have one side equal to the desired resolution, keeping the aspect ratio.
+
+        - If the image is larger than the desired resolution, the side with the maximum length will be resized to the
+          desired resolution.
+        - If the image is smaller than the desired resolution, the image will not be resized.
+        """
+
+        if max(width, height) > max_dimension:
+            return cls.resize_image_dimensions(
+                width,
+                height,
+                desired_dimension=max_dimension,
+                use_min=False,
+            )
+        return width, height
 
     @classmethod
     def add_image_alpha_channel(cls, source_image, alpha_image):
@@ -45,7 +138,7 @@ def resize_sources_to_request(cls, payload):
             newwidth = payload["width"]
             newheight = payload["height"]
             if payload.get("hires_fix") or payload.get("control_type"):
-                newwidth, newheight = cls.calculate_source_image_size(payload["width"], payload["height"])
+                newwidth, newheight = cls.get_first_pass_image_resolution_min(payload["width"], payload["height"])
             if source_image.size != (newwidth, newheight):
                 payload["source_image"] = source_image.resize(
                     (newwidth, newheight),
diff --git a/images_expected/stable_cascade_text_to_image_2pass.png b/images_expected/stable_cascade_text_to_image_2pass.png
new file mode 100644
index 00000000..587ff31d
Binary files /dev/null and b/images_expected/stable_cascade_text_to_image_2pass.png differ
diff --git a/images_expected/stable_cascade_text_to_image_2pass_denoise_0.png b/images_expected/stable_cascade_text_to_image_2pass_denoise_0.png
new file mode 100644
index 00000000..c1fd8a7d
Binary files /dev/null and b/images_expected/stable_cascade_text_to_image_2pass_denoise_0.png differ


Test patch:
diff --git a/.github/workflows/maintests.yml b/.github/workflows/maintests.yml
index 550d7365..00bfe59c 100644
--- a/.github/workflows/maintests.yml
+++ b/.github/workflows/maintests.yml
@@ -18,6 +18,8 @@ jobs:
       - uses: actions/checkout@v3
       - name: Run pre-commit
         uses: pre-commit/action@v3.0.0
+        with:
+          extra_args: --all-files
 
   build:
     runs-on: self-hosted
diff --git a/.github/workflows/prtests.yml b/.github/workflows/prtests.yml
index 6abbeb7a..84f53298 100644
--- a/.github/workflows/prtests.yml
+++ b/.github/workflows/prtests.yml
@@ -21,6 +21,8 @@ jobs:
       - uses: actions/checkout@v3
       - name: Run pre-commit
         uses: pre-commit/action@v3.0.0
+        with:
+          extra_args: --all-files
 
   build:
     runs-on: self-hosted
diff --git a/tests/conftest.py b/tests/conftest.py
index a82491f9..bd7a27ad 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -49,7 +49,6 @@ def init_horde(
         if not os.path.exists(default_custom_model_json_path):
             os.makedirs(os.path.dirname(default_custom_model_json_path), exist_ok=True)
             with open(default_custom_model_json_path, "w") as f:
-
                 json.dump(default_custom_model_json, f, indent=4)
 
         os.environ["HORDELIB_CUSTOM_MODELS"] = default_custom_model_json_path
diff --git a/tests/test_comfy.py b/tests/test_comfy.py
index d75889c7..91a3f151 100644
--- a/tests/test_comfy.py
+++ b/tests/test_comfy.py
@@ -74,7 +74,7 @@ def test_fix_pipeline_types(self, hordelib_instance: HordeLib):
 
     def test_fix_node_names(self, hordelib_instance: HordeLib):
         # basically we are expecting a search and replace of "1" with the "title" of id 1, etc.
-        data = {
+        data: dict = {
             "1": {
                 "inputs": {
                     "input1": ["2", 0],
@@ -83,6 +83,7 @@ def test_fix_node_names(self, hordelib_instance: HordeLib):
                     "input4": 33,
                     "input5": None,
                 },
+                "_meta": {"title": "Node1"},
             },
             "2": {
                 "inputs": {
@@ -92,6 +93,7 @@ def test_fix_node_names(self, hordelib_instance: HordeLib):
                     "input4": 33,
                     "input5": None,
                 },
+                "_meta": {"title": "Node2"},
             },
             "3": {
                 "inputs": {
@@ -103,14 +105,7 @@ def test_fix_node_names(self, hordelib_instance: HordeLib):
                 },
             },
         }
-        design = {
-            "nodes": [
-                {"id": 1, "title": "Node1"},
-                {"id": 2, "title": "Node2"},
-                {"id": 3, "no_title": "Node3"},
-            ],
-        }
-        data = hordelib_instance.generator._fix_node_names(data, design)
+        data = hordelib_instance.generator._fix_node_names(data)
 
         assert data
         assert isinstance(data, dict)
diff --git a/tests/test_horde_inference_cascade.py b/tests/test_horde_inference_cascade.py
index 9f8cf7bf..bc6c1e55 100644
--- a/tests/test_horde_inference_cascade.py
+++ b/tests/test_horde_inference_cascade.py
@@ -1,5 +1,6 @@
 # test_horde.py
 
+import pytest
 from PIL import Image
 
 from hordelib.horde import HordeLib
@@ -319,3 +320,65 @@ def test_cascade_image_remix_triple(
             f"images_expected/{img_filename}",
             pil_image,
         )
+
+    def test_cascade_text_to_image_hires_2pass(
+        self,
+        hordelib_instance: HordeLib,
+        stable_cascade_base_model_name: str,
+    ):
+        data = {
+            "sampler_name": "k_euler",
+            "cfg_scale": 4,
+            "denoising_strength": 1.0,
+            "hires_fix_denoising_strength": 0.5,
+            "seed": 1312,
+            "height": 1536,
+            "width": 1024,
+            "karras": False,
+            "tiling": False,
+            "hires_fix": True,
+            "clip_skip": 1,
+            "control_type": None,
+            "image_is_control": False,
+            "return_control_map": False,
+            "prompt": (
+                "Lucid Creations, Deep Forest, Moss, ethereal, dreamlike, surreal, "
+                "beautiful, illustration, incredible detail, 8k, abstract"
+            ),
+            "ddim_steps": 20,
+            "n_iter": 1,
+            "model": stable_cascade_base_model_name,
+        }
+
+        pil_image = hordelib_instance.basic_inference_single_image(data).image
+        assert pil_image is not None
+        assert isinstance(pil_image, Image.Image)
+
+        img_filename = "stable_cascade_text_to_image_2pass.png"
+        pil_image.save(f"images/{img_filename}", quality=100)
+
+        # Check that denoising strength works
+        data["hires_fix_denoising_strength"] = 0
+        pil_image2 = hordelib_instance.basic_inference_single_image(data).image
+        assert pil_image2 is not None
+        assert isinstance(pil_image2, Image.Image)
+
+        img_filename_denoise_0 = "stable_cascade_text_to_image_2pass_denoise_0.png"
+        pil_image2.save(f"images/{img_filename_denoise_0}", quality=100)
+
+        assert pil_image2 is not None
+        assert isinstance(pil_image2, Image.Image)
+        with pytest.raises(AssertionError):
+            check_single_inference_image_similarity(
+                pil_image2,
+                pil_image,
+                exception_on_fail=True,
+            )
+        assert check_single_inference_image_similarity(
+            f"images_expected/{img_filename}",
+            pil_image,
+        )
+        assert check_single_inference_image_similarity(
+            f"images_expected/{img_filename_denoise_0}",
+            pil_image2,
+        )
diff --git a/tests/test_utils.py b/tests/test_utils.py
index 745fe508..1bb0cabe 100644
--- a/tests/test_utils.py
+++ b/tests/test_utils.py
@@ -6,6 +6,7 @@
     HistogramDistanceResultCode,
 )
 from hordelib.utils.gpuinfo import GPUInfo
+from hordelib.utils.image_utils import ImageUtils
 
 
 def test_worker_settings_singleton():
@@ -64,3 +65,85 @@ def test_gpuinfo_init(self):
         assert info.vram_total[0] > 0
         assert info.vram_free[0] > 0
         assert info.vram_used[0] > 0
+
+
+class TestImageUtils:
+    def test_get_first_pass_image_resolution_min(self):
+        expected = (512, 512)
+        calculated = ImageUtils.get_first_pass_image_resolution_min(512, 512)
+
+        assert calculated == expected
+
+    def test_under_sized_both_dimensions_min(self):
+        expected = (256, 256)
+        calculated = ImageUtils.get_first_pass_image_resolution_min(256, 256)
+
+        assert calculated == expected
+
+    def test_under_sized_one_dimension_min(self):
+        expected = (512, 256)
+        calculated = ImageUtils.get_first_pass_image_resolution_min(512, 256)
+
+        assert calculated == expected
+
+    def test_oversized_one_dimension_min(self):
+        expected = (1024, 512)
+        calculated = ImageUtils.get_first_pass_image_resolution_min(1024, 512)
+
+        assert calculated == expected
+
+    def test_oversized_other_dimension_min(self):
+        expected = (512, 1024)
+        calculated = ImageUtils.get_first_pass_image_resolution_min(512, 1024)
+
+        assert calculated == expected
+
+    def test_both_dimensions_oversized_evenly_min(self):
+        expected = (512, 512)
+        calculated = ImageUtils.get_first_pass_image_resolution_min(1024, 1024)
+
+        assert calculated == expected
+
+    def test_both_dimensions_oversized_unevenly_min(self):
+        expected = (512, 768)
+        calculated = ImageUtils.get_first_pass_image_resolution_min(1024, 1536)
+
+        assert calculated == expected
+
+    def test_get_first_pass_image_resolution_max(self):
+        expected = (1024, 1024)
+        calculated = ImageUtils.get_first_pass_image_resolution_max(1024, 1024)
+
+        assert calculated == expected
+
+    def test_under_sized_both_dimensions_max(self):
+        expected = (512, 512)
+        calculated = ImageUtils.get_first_pass_image_resolution_max(512, 512)
+
+        assert calculated == expected
+
+    def test_oversized_one_dimension_max(self):
+        expected = (1024, 512)
+        calculated = ImageUtils.get_first_pass_image_resolution_max(2048, 1024)
+
+        assert calculated == expected
+
+    def test_oversized_other_dimension_max(self):
+        expected = (512, 1024)
+        calculated = ImageUtils.get_first_pass_image_resolution_max(1024, 2048)
+
+        assert calculated == expected
+
+    def test_both_dimensions_oversized_evenly_max(self):
+        expected = (1024, 1024)
+        calculated = ImageUtils.get_first_pass_image_resolution_max(2048, 2048)
+
+        assert calculated == expected
+
+    def test_both_dimensions_oversized_unevenly_max(self):
+        expected = (640, 1024)
+        calculated_cascade = ImageUtils.get_first_pass_image_resolution_max(2048, 3072)
+        calculated_default = ImageUtils.get_first_pass_image_resolution_min(2048, 3072)
+
+        assert calculated_cascade != calculated_default
+        assert calculated_cascade == expected
diff --git a/tests/testing_shared_functions.py b/tests/testing_shared_functions.py
index aa3312bc..23c0332d 100644
--- a/tests/testing_shared_functions.py
+++ b/tests/testing_shared_functions.py
@@ -153,36 +153,48 @@ def do_pytest_action(result: ImageSimilarityResult) -> bool:
 def check_single_inference_image_similarity(
     img1: FilePathOrPILImage,
     img2: FilePathOrPILImage,
+    *,
+    exception_on_fail: bool = False,
 ) -> bool:
     return check_list_inference_images_similarity(
         [(img1, img2)],
+        exception_on_fail=exception_on_fail,
     )
 
 
 def check_single_lora_image_similarity(
     img1: FilePathOrPILImage,
     img2: FilePathOrPILImage,
+    *,
+    exception_on_fail: bool = False,
 ) -> bool:
     return check_list_lora_images_similarity(
         [(img1, img2)],
+        exception_on_fail=exception_on_fail,
     )
 
 
 def check_list_lora_images_similarity(
     list_of_image_pairs: Iterable[tuple[FilePathOrPILImage, FilePathOrPILImage]],
+    *,
+    exception_on_fail: bool = False,
 ) -> bool:
     return check_list_images_similarity(
         list_of_image_pairs,
         similarity_constraints=LORA_SIMILARITY_DEFAULTS,
+        exception_on_fail=exception_on_fail,
     )
 
 
 def check_list_inference_images_similarity(
     list_of_image_pairs: Iterable[tuple[FilePathOrPILImage, FilePathOrPILImage]],
+    *,
+    exception_on_fail: bool = False,
 ) -> bool:
     return check_list_images_similarity(
         list_of_image_pairs,
         similarity_constraints=INFERENCE_SIMILARITY_DEFAULTS,
+        exception_on_fail=exception_on_fail,
     )
 
 
@@ -190,6 +202,7 @@ def check_list_images_similarity(
     list_of_image_pairs: Iterable[tuple[FilePathOrPILImage, FilePathOrPILImage]],
     *,
     similarity_constraints: ImageSimilarityConstraints,
+    exception_on_fail: bool = False,
 ) -> bool:
     all_results: list[tuple[tuple[FilePathOrPILImage, FilePathOrPILImage], ImageSimilarityResult]] = []
     for img1, img2 in list_of_image_pairs:
@@ -231,6 +244,8 @@ def check_list_images_similarity(
     HORDELIB_SKIP_SIMILARITY_FAIL = os.getenv("HORDELIB_SKIP_SIMILARITY_FAIL", None)
 
     if len(all_failed_results) > 0:
+        if exception_on_fail:
+            raise AssertionError(complete_error_message)
         if not HORDELIB_SKIP_SIMILARITY_FAIL:
             pytest.fail(complete_error_message)
         else: