Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3c053e09df | ||
|
|
5a617560a3 | ||
|
|
5389e93994 | ||
|
|
f4843d30d3 | ||
|
|
8eb38a81bf | ||
|
|
e9ff4c2390 | ||
|
|
1687e14d08 | ||
|
|
e28b9c3810 | ||
|
|
5d2e6c5b9c | ||
|
|
e8a0f8c005 | ||
|
|
d128024dff | ||
|
|
e21e2ee7ef | ||
|
|
71e42b303b |
@@ -1,354 +1,159 @@
|
||||
<p align="center"><img src="docs/assets/banner.jpg" alt="NH-Nodes — image, mask, resolution and workflow nodes for ComfyUI" width="100%"></p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://registry.comfy.org/nodes/nh-nodes"><img src="https://img.shields.io/badge/ComfyUI_Registry-nh--nodes-2563eb" alt="ComfyUI Registry"></a>
|
||||
<a href="https://github.com/jetthuangai/NH-Nodes/actions/workflows/tests.yml"><img src="https://github.com/jetthuangai/NH-Nodes/actions/workflows/tests.yml/badge.svg" alt="Tests"></a>
|
||||
<a href="LICENSE"><img src="https://img.shields.io/badge/License-MIT_(see_notices)-16a34a" alt="License"></a>
|
||||
<a href="docs/en/README.md"><img src="https://img.shields.io/badge/Docs-EN_%7C_VI-0f766e" alt="Documentation"></a>
|
||||
</p>
|
||||
|
||||
<p align="center"><b>English</b> · <a href="docs/vi/README.md">Tiếng Việt</a></p>
|
||||
|
||||
# NH-Nodes for ComfyUI
|
||||
|
||||
Production-focused custom nodes for ComfyUI workflows: image loading, tiling,
|
||||
compositing, mask utilities, smart resize, logic routing, prompt/text tooling,
|
||||
batch helpers, indexed model loaders, garment/portrait segmentation, and VTON
|
||||
preprocessing.
|
||||
**75 production-focused nodes** for ComfyUI: load and save image sets with their metadata, tile and composite large
|
||||
images, pick the right resolution for current models and put results back into the original frame, refine masks,
|
||||
segment garments and faces, find every person, window or sky by name with SAM 3, build VTON masks, route workflows
|
||||
with lazy switches, generate prompts, and test many LoRAs or models from one node.
|
||||
|
||||
[](https://registry.comfy.org/nodes/nh-nodes)
|
||||
[](https://github.com/jetthuangai/NH-Nodes)
|
||||
[](LICENSE)
|
||||
Every node is documented with a visual card, its inputs and outputs, and examples — in
|
||||
[English](docs/en/README.md) and [Vietnamese](docs/vi/README.md).
|
||||
|
||||
- **Author:** [jetthuang.com](https://jetthuang.com)
|
||||
- **Hugging Face:** [nhathoangfoto](https://huggingface.co/nhathoangfoto)
|
||||
- **Support:** [PayPal](https://paypal.me/nhathoangfoto)
|
||||
## Highlights
|
||||
|
||||
---
|
||||
|
||||
## Why NH-Nodes
|
||||
|
||||
NH-Nodes is built for practical workflow construction. It focuses on reusable
|
||||
building blocks that remove repetitive graph work:
|
||||
|
||||
- Load images with filename/path metadata.
|
||||
- Split images into tiles, upscale or process tiles, then stitch them back.
|
||||
- Build image grids, layer composites, comparisons, and labeled previews.
|
||||
- Resize images and masks with predictable aspect-ratio behavior.
|
||||
- Route workflow branches with boolean logic and generic data switches.
|
||||
- Generate prompts from templates, regex, lists, schedules, and text utilities.
|
||||
- Pick indexed LoRA/diffusion models for larger configurable workflows.
|
||||
- Prepare VTON masks, agnostic images, DensePose/parsing outputs, and related masks.
|
||||
|
||||
---
|
||||
<table>
|
||||
<tr>
|
||||
<td width="50%"><a href="docs/en/resolution.md"><img src="docs/assets/examples/resize-modes.jpg" alt="Resize modes"></a><br>
|
||||
<b>Resolution done right</b> — presets for FLUX.2, Qwen-Image and Z-Image, cover / contain / stretch resizing, and an
|
||||
exact round trip back to the original frame.</td>
|
||||
<td width="50%"><a href="docs/en/image-compose.md"><img src="docs/assets/examples/tile-untile.jpg" alt="Tile and untile"></a><br>
|
||||
<b>Large images</b> — split into overlapping tiles, process them, and blend them back seamlessly; deep-zoom preview
|
||||
for images too big for the canvas.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="docs/en/vision.md"><img src="docs/assets/examples/garment-segment.jpg" alt="Garment segmentation"></a><br>
|
||||
<b>Segmentation</b> — name what you want (people, windows, the sky) and SAM 3 returns every instance as its own
|
||||
mask, with crisp edges on small and medium objects even in 4K–8K photos; garment and face-part masks from
|
||||
SegFormer models; VTON garment masks with pose and parsing.</td>
|
||||
<td><a href="docs/en/mask.md"><img src="docs/assets/examples/mask-morphology.jpg" alt="Mask morphology"></a><br>
|
||||
<b>Mask tools</b> — fast grow / shrink on the CPU or the GPU, hole filling, soft edges, bounding boxes and
|
||||
aspect-ratio matching.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="docs/en/image-compose.md"><img src="docs/assets/examples/layer-layout.jpg" alt="Layer layout"></a><br>
|
||||
<b>Compositing</b> — layouts, layer stacks with alpha, labelled grids and side-by-side comparisons.</td>
|
||||
<td><a href="docs/en/logic.md"><img src="docs/assets/examples/grid-composite.jpg" alt="Grid composite"></a><br>
|
||||
<b>Workflow logic</b> — lazy If/Else and switches that run only the chosen branch, safe math, counters, lists and
|
||||
prompt tools.</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
## Installation
|
||||
|
||||
### ComfyUI Manager
|
||||
**ComfyUI Manager** — search for `NH-Nodes`, install, restart ComfyUI.
|
||||
|
||||
Search for `NH-Nodes`, install, then restart ComfyUI.
|
||||
|
||||
### Comfy CLI
|
||||
**Comfy CLI**
|
||||
|
||||
```bash
|
||||
comfy node install nh-nodes
|
||||
```
|
||||
|
||||
### Manual
|
||||
**Manual**
|
||||
|
||||
```bash
|
||||
cd ComfyUI/custom_nodes
|
||||
git clone https://github.com/jetthuangai/NH-Nodes.git
|
||||
cd NH-Nodes
|
||||
pip install -r requirements.txt
|
||||
python -m pip install -r requirements.txt
|
||||
```
|
||||
|
||||
Restart ComfyUI after installation.
|
||||
Restart ComfyUI afterwards. Requirements: a recent ComfyUI (tested with 0.41) and Python 3.10+. **SAM3 Segment** has
|
||||
more requirements, listed below.
|
||||
|
||||
---
|
||||
### Optional: VTON and segmentation models
|
||||
|
||||
## Featured Workflows
|
||||
- **VTON Ultimate Processor** needs ONNX Runtime, which is not installed automatically (the CPU wheel would overwrite an
|
||||
existing GPU build). Install one of them into ComfyUI's Python:
|
||||
`python -m pip install onnxruntime-gpu` (NVIDIA) or `python -m pip install onnxruntime` (CPU).
|
||||
- **Garment Segment**, **Portrait Segment** and **VTON** download their models from Hugging Face on first use (pinned
|
||||
revisions, only the needed files). To keep models elsewhere, add these keys to `extra_model_paths.yaml`:
|
||||
|
||||
### Tile, Process, Untile
|
||||
```yaml
|
||||
my_models:
|
||||
base_path: D:/ai-models/
|
||||
nh_nodes: NH-Nodes/ # SegFormer models (garment, face parsing)
|
||||
vton_mask: ComfyUI-Vton-Mask/ # VTON pose + parsing ONNX models
|
||||
sam3: sam3/ # SAM 3 checkpoint (you download sam3.pt yourself)
|
||||
```
|
||||
- **Requirements for SAM 3** (SAM3 Segment only): an NVIDIA GPU with CUDA, Meta's `sam3` package and the `sam3.pt`
|
||||
checkpoint. NH-Nodes installs and downloads none of them: you install the package yourself and request the weights
|
||||
from Meta. The steps, and what to do if another SAM 3 pack is already installed, are in
|
||||
[Before you start with SAM 3](docs/en/vision.md#before-you-start-with-sam-3).
|
||||
|
||||
Use this when an image is too large for a model, or when every tile needs the
|
||||
same enhancement/upscale step.
|
||||
## Quick start
|
||||
|
||||
```text
|
||||
Load Image -> Image Tile (NH) -> upscale/process tiles -> Image Untile (NH)
|
||||
```
|
||||
| Goal | Chain | Guide |
|
||||
|---|---|---|
|
||||
| Upscale or refine a huge image piece by piece | Load Image → **Image Tile** → your sampler/upscaler per tile → **Image Untile** | [image-compose](docs/en/image-compose.md#image-tile-nh) |
|
||||
| Generate at the model's native size, then restore the original framing | Load Image → **NH Smart Ratio Image Resize** → edit → **NH Restore Image Geometry** | [resolution](docs/en/resolution.md) |
|
||||
| Inpaint only the clothes | Load Image → **Garment Segment** → **Mask Morphology** (grow) → inpaint | [vision](docs/en/vision.md) |
|
||||
| Mask every person, window or the sky in a large photo | Load Image → **SAM3 Segment** → **Segs Select** or **Segs Compose** → mask → edit or inpaint | [vision](docs/en/vision.md#sam-3-workflows) |
|
||||
| Compare many LoRAs, one per queue | **Counter** → **Load LoRA Model Index** (index) → sampler → **Image Label** (prints the LoRA name) → Save Image | [loaders](docs/en/loaders.md) |
|
||||
| Run one of two branches without executing the other | condition → **If/Else** (lazy) → … | [logic](docs/en/logic.md#ifelse-nh) |
|
||||
|
||||
`Image Tile (NH)` supports:
|
||||
## Node catalog
|
||||
|
||||
- `original_ratio`: derive tile dimensions from the original image aspect ratio.
|
||||
- `custom`: use explicit tile width and height.
|
||||
- `square`: force square tiles, for example `1024 x 1024`.
|
||||
- Automatic overlap calculation.
|
||||
- Edge padding so square/custom tile outputs keep the requested tile size.
|
||||
| Group | Nodes | Guide |
|
||||
|---|---|---|
|
||||
| Image — load & save | Load Image Info, Load Images Folder, Load images matching, Save Image Path, NH Save Large Image | [EN](docs/en/image-io.md) · [VI](docs/vi/image-io.md) |
|
||||
| Image — compose | Image Tile, Image Untile, Image Grid Composite, Layer Layout Composite, Layer Stack Composite, Image Compare, Image Label, Image Resize Unit | [EN](docs/en/image-compose.md) · [VI](docs/vi/image-compose.md) |
|
||||
| Image — large preview | NH Large Image Preview, NH Large Image Compare, NH Large Preview Cache Manager | [EN](docs/en/image-preview.md) · [VI](docs/vi/image-preview.md) |
|
||||
| Image — mask-based retouch | NH Mask-Aware Resize, NH Agnostic Image Generator, NH Simple Face Paste | [EN](docs/en/image-retouch.md) · [VI](docs/vi/image-retouch.md) |
|
||||
| Resolution | NH Smart Resolution Picker, NH Smart Ratio Image Resize, NH Ratio Preset Image Resize, NH Remove Resize Padding, NH Restore Image Geometry | [EN](docs/en/resolution.md) · [VI](docs/vi/resolution.md) |
|
||||
| Mask | Mask Morphology, Mask Properties, NH Create Box Mask, NH Mask Aspect Ratio Match | [EN](docs/en/mask.md) · [VI](docs/vi/mask.md) |
|
||||
| Vision | Garment Segment, Portrait Segment, SAM3 Segment, Segs Select, Segs Compose, Segs To Masks, Segs To Impact SEGS, Segs Preview | [EN](docs/en/vision.md) · [VI](docs/vi/vision.md) |
|
||||
| VTON | VTON Ultimate Processor | [EN](docs/en/vton.md) · [VI](docs/vi/vton.md) |
|
||||
| Logic | Compare, Logic Gate, If/Else, Switch N, Any Switch, Any Branch Switch, Gate Switch, Value Match Index, Math Eval, Random Choice | [EN](docs/en/logic.md) · [VI](docs/vi/logic.md) |
|
||||
| Text | Prompt Template, Prompt Scheduler, String Operations, Prompt Join, Text Split, Regex Extract, Text Index, Text Concatenate, Text Split Lines, Text Random Line | [EN](docs/en/text.md) · [VI](docs/vi/text.md) |
|
||||
| Batch & lists | Batch Index, Batch Merge, Counter, List Create, List Index, List Filter, Any List Split | [EN](docs/en/batch-lists.md) · [VI](docs/vi/batch-lists.md) |
|
||||
| Loaders | Load LoRA Model Index, Load LoRA Clip Index, Load Diffusion Model Index, Load VAE Index, Load Clip Index | [EN](docs/en/loaders.md) · [VI](docs/vi/loaders.md) |
|
||||
| Utils | Multi-Slider (FLOAT/INT), Boolean Switch, Pack Universal, Unpack Universal, Universal Slider Builder *(deprecated)* | [EN](docs/en/utils.md) · [VI](docs/vi/utils.md) |
|
||||
|
||||
`Image Untile (NH)` uses `tile_data` from the tile node to stitch results back.
|
||||
It also handles tiles that were resized or upscaled, such as 2x or 3x per tile.
|
||||
All nodes live under **NH-Nodes** in the node menu; search by name or by node id (for example `NH_ImageTile`).
|
||||
|
||||
### Grid Contact Sheet
|
||||
## Documentation
|
||||
|
||||
```text
|
||||
Image batch -> Image Grid Composite (NH) -> Preview Image
|
||||
```
|
||||
|
||||
Use manual cell size or first-image cell size. Resize modes include `stretch`,
|
||||
`pad`, and `fill`.
|
||||
|
||||
### Metadata-Aware Image Load
|
||||
|
||||
```text
|
||||
Load Image Info (NH) -> image / mask / file_name / path
|
||||
```
|
||||
|
||||
Useful for naming outputs, logging source images, or routing by filename.
|
||||
|
||||
### Smart Resolution Resize
|
||||
|
||||
```text
|
||||
Load Image -> NH Smart Ratio Image Resize -> (your pipeline) -> NH Remove Resize Padding
|
||||
```
|
||||
|
||||
`NH Smart Ratio Image Resize` picks the closest model preset itself from
|
||||
`model_family` + `resolution_level`, so it does not need `NH Smart Resolution
|
||||
Picker` upstream. Resize with cover crop, contain pad, or stretch behavior.
|
||||
|
||||
In `contain_pad` mode it also emits `resize_data` describing the content region,
|
||||
padding, and scale factor. Feed that into `NH Remove Resize Padding` at the end
|
||||
of the pipeline to crop the padded border back off (`content_region`) or to
|
||||
restore the original input size (`original_size`).
|
||||
|
||||
For `cover_crop`, use `NH Restore Image Geometry` after processing. Connect the
|
||||
processed image to `image`, the source image to `original_image`, and the resize
|
||||
node's `resize_data` to the matching input. It returns an image at the exact
|
||||
original dimensions, with processed content placed back in its original region
|
||||
and cropped-away borders retained from the source. Its `mask` is white in the
|
||||
restored region and black in the retained borders; use it for masked blending
|
||||
with the original, optionally softening the mask edges first.
|
||||
|
||||
Processed images may be upscaled, including by a non-integer factor, provided
|
||||
their framing is unchanged. Additional crops, rotations, or model-generated
|
||||
changes in geometry cannot be reversed by this metadata. The restore node also
|
||||
accepts `contain_pad` and `stretch` metadata and supports matching batches or
|
||||
singleton batch broadcasting. Restoration uses bicubic (default), bilinear, or
|
||||
nearest sampling; it does not recover details lost during downsampling.
|
||||
|
||||
Use `NH Smart Resolution Picker` when you need target dimensions and a latent
|
||||
for text-to-image, instead of resizing an existing image.
|
||||
|
||||
### Garment and Portrait Segmentation
|
||||
|
||||
```text
|
||||
Load Image -> Garment Segment (NH) [part=upper_garment] -> mask / cutout_rgba / mask_image
|
||||
Load Image -> Portrait Segment (NH) [hair + face] -> mask / cutout_rgba / mask_image
|
||||
```
|
||||
|
||||
SegFormer-based masks at input resolution. `cutout_rgba` is the input image
|
||||
with the mask as alpha (RGB is left untouched outside the mask); `mask_image`
|
||||
is the mask as an RGB preview. Dresses and jumpsuits belong to `upper_garment`. Both nodes share `mask_expand`, `mask_blur`, and `fill_holes`.
|
||||
Choose `part=all_garments` to combine upper/lower garments, footwear and headwear
|
||||
in one mask, for example a shirt and pants together. `attach_parts` still controls
|
||||
whether touching pockets, zippers and other garment decorations are included.
|
||||
Models are auto-downloaded to `models/NH-Nodes/` on first use (existing
|
||||
`models/face_parsing` or `models/RMBG/segformer_fashion` copies are reused).
|
||||
|
||||
---
|
||||
|
||||
## Node Catalog
|
||||
|
||||
### Image Loading and Saving
|
||||
|
||||
| Node | Purpose |
|
||||
|---|---|
|
||||
| `Load Image Info (NH)` | Load one image and output `IMAGE`, `MASK`, file name, and path. |
|
||||
| `Save Image Path (NH)` | Save image batches to a chosen folder and output saved filenames/path/count. |
|
||||
| `NH Save Large Image` | Save full-resolution images without attaching heavy full-res previews to the canvas. |
|
||||
| `Load Images Folder (NH)` | Load images from a folder by index/step, with filename and path outputs. |
|
||||
| `Load images matching` | Load source/matching images by text, folder context, and matching rules. |
|
||||
|
||||
### Image Tiling, Grid, and Composition
|
||||
|
||||
| Node | Purpose |
|
||||
|---|---|
|
||||
| `Image Tile (NH)` | Split image batches into overlapping tiles with metadata for reconstruction. |
|
||||
| `Image Untile (NH)` | Rebuild full images from processed/upscaled tiles and tile metadata. |
|
||||
| `Image Grid Composite (NH)` | Combine an image batch into a configurable grid. |
|
||||
| `NH Large Image Preview` | Preview large images with lightweight canvas thumbnails and passthrough output. |
|
||||
| `NH Large Image Compare` | Compare large A/B images with lightweight canvas proxy and tiled slider/side/difference viewer. |
|
||||
| `NH Large Preview Cache Manager` | Inspect and clean the NH large preview tile cache by age, size, or cache id. |
|
||||
| `Layer Layout Composite (NH)` | Place layers into preset, grid, or custom layout slots. |
|
||||
| `Layer Stack Composite (NH)` | Paste multiple layers onto a background using explicit coordinates. |
|
||||
| `Image Resize Unit (NH)` | Resize by pixel/mm/cm using stretch, pad, crop, or lock-ratio modes. |
|
||||
| `Image Compare (NH)` | Compare two images side-by-side, top/bottom, split, difference, or overlay. |
|
||||
| `Image Label (NH)` | Add header/bottom labels with font, padding, color, and outline controls. |
|
||||
| `NH Agnostic Image Generator` | Create agnostic/masked/composite images for inpaint and VTON workflows. |
|
||||
| `NH Simple Face Paste` | Paste a source face region into a target using mask feathering. |
|
||||
|
||||
### Smart Resolution
|
||||
|
||||
| Node | Purpose |
|
||||
|---|---|
|
||||
| `NH Smart Resolution Picker` | Pick model/preset-aware dimensions and latent setup. |
|
||||
| `NH Smart Ratio Image Resize` | Auto-pick the closest model preset for the source aspect, then resize with cover, contain, or stretch; outputs `resize_data` for padding removal. |
|
||||
| `NH Ratio Preset Image Resize` | Resize to a chosen model preset ratio with crop, pad, or fill behavior. |
|
||||
| `NH Remove Resize Padding` | Remove the padded border from processed images using resize metadata. |
|
||||
| `NH Restore Image Geometry` | Restore processed or upscaled images to original coordinates with a blend mask; keep source pixels outside the crop. |
|
||||
|
||||
### Vision Segmentation
|
||||
|
||||
| Node | Purpose |
|
||||
|---|---|
|
||||
| `Garment Segment (NH)` | Mask `upper_garment` (tops, jackets, coats, dresses, ties, scarves, collars, sleeves), `lower_garment` (pants, shorts, skirts, belts, tights; no shoes), `footwear`, `headwear`, or combine all four with `all_garments`. Attached parts (pockets, zippers, bows...) merge only when touching the selected garments. |
|
||||
| `Portrait Segment (NH)` | Checklist mask of face, neck, hair, eyes, lips, nose, eyebrows, ears (+ optional mouth interior). `face` is the solid face region including features. |
|
||||
|
||||
### Mask Tools
|
||||
|
||||
| Node | Purpose |
|
||||
|---|---|
|
||||
| `Mask Morphology (NH)` | Expand/shrink masks, fill holes, and blur edges. |
|
||||
| `Mask Properties (NH)` | Output mask bounding box, dimensions, and related measurements. |
|
||||
| `NH Create Box Mask` | Convert a mask area into a rectangular box mask. |
|
||||
| `NH Mask Aspect Ratio Match` | Adjust one mask to match another mask's aspect ratio. |
|
||||
| `NH Mask-Aware Resize` | Resize an image/mask pair while preserving the masked subject region. |
|
||||
|
||||
### Logic and Flow Control
|
||||
|
||||
| Node | Purpose |
|
||||
|---|---|
|
||||
| `Compare (NH)` | Compare values with typed operators. |
|
||||
| `Logic Gate (NH)` | AND, OR, NOT, XOR, NAND, and NOR boolean logic. |
|
||||
| `If/Else (NH)` | Select between two inputs by condition. |
|
||||
| `Switch N (NH)` | Pick one of several generic inputs by index. |
|
||||
| `Any Switch (NH)` | Boolean switch helper. |
|
||||
| `Any Branch Switch (NH)` | Route generic data through selected branches. |
|
||||
| `Gate Switch (NH)` | Pass or block a generic input by boolean state. |
|
||||
| `Value Match Index (NH)` | Return index of matching configured value. |
|
||||
| `Math Eval (NH)` | Safe math expression evaluator with numeric/string outputs. |
|
||||
| `Random Choice (NH)` | Weighted random choice from generic inputs. |
|
||||
|
||||
### Text and Prompt Tools
|
||||
|
||||
| Node | Purpose |
|
||||
|---|---|
|
||||
| `String Operations (NH)` | Strip, case convert, replace, slice, inspect, and test strings. |
|
||||
| `Prompt Join (NH)` | Join text fragments with separators and empty-line handling. |
|
||||
| `Text Split (NH)` | Split text by delimiter and expose count/first/last values. |
|
||||
| `Regex Extract (NH)` | Extract, replace, split, and match with regex patterns. |
|
||||
| `Text Index (NH)` | Select a line/item from text by index. |
|
||||
| `Text Concatenate (NH)` | Concatenate multiple text inputs. |
|
||||
| `Text Split Lines (NH)` | Split multiline text into selected parts and metadata. |
|
||||
| `Text Random Line (NH)` | Pick a random line from multiline text. |
|
||||
| `Prompt Template (NH)` | Fill `{placeholder}` variables from connected text values. |
|
||||
| `Prompt Scheduler (NH)` | Select prompts by step, sequence, ping-pong, or seed. |
|
||||
|
||||
### Lists and Batch Helpers
|
||||
|
||||
| Node | Purpose |
|
||||
|---|---|
|
||||
| `List Create (NH)` | Build an `NH_LIST` from multiline or delimited text. |
|
||||
| `List Index (NH)` | Select an item from an `NH_LIST`. |
|
||||
| `List Filter (NH)` | Filter lists with contains, regex, equals, length, and related conditions. |
|
||||
| `Batch Index (NH)` | Extract one or more images from an image batch. |
|
||||
| `Batch Merge (NH)` | Merge image batches with resize handling. |
|
||||
| `Counter (NH)` | Stateful counter for repeated queue runs. |
|
||||
| `Any List Split (NH)` | Split any incoming list into an `All` output plus up to 10 indexed outputs. |
|
||||
|
||||
### Indexed Loaders
|
||||
|
||||
| Node | Purpose |
|
||||
|---|---|
|
||||
| `Load LoRA Model Index (NH)` | Apply one selected LoRA to `MODEL` by 1-based index. |
|
||||
| `Load LoRA Clip Index (NH)` | Apply one selected LoRA to `CLIP` by 1-based index. |
|
||||
| `Load Diffusion Model Index (NH)` | Load one diffusion model by 1-based index. |
|
||||
| `Load VAE Index (NH)` | Load one VAE by 1-based index or boolean toggle. |
|
||||
| `Load Clip Index (NH)` | Load one CLIP by 1-based index or boolean toggle, with CLIP type selection. |
|
||||
|
||||
### Workflow Utilities
|
||||
|
||||
| Node | Purpose |
|
||||
|---|---|
|
||||
| `Multi-Slider (FLOAT)` | Five float controls in one node. |
|
||||
| `Multi-Slider (INT)` | Five integer controls in one node. |
|
||||
| `Universal Slider Builder (NH)` | Generate dynamic slider outputs from text config. |
|
||||
| `Boolean Switch (NH)` | Simple boolean toggle. |
|
||||
| `Pack Universal (NH)` | Bundle generic values into an `NH_UNIVERSAL_PIPE`. |
|
||||
| `Unpack Universal (NH)` | Extract values from an `NH_UNIVERSAL_PIPE`. |
|
||||
|
||||
### VTON Preprocessing
|
||||
|
||||
| Node | Purpose |
|
||||
|---|---|
|
||||
| `VTON Ultimate Processor (NH)` | Generate masks, agnostic image data, DensePose/parsing outputs, hair/hands masks, and related VTON preprocessing assets. |
|
||||
|
||||
---
|
||||
|
||||
## Menu Structure
|
||||
|
||||
```text
|
||||
NH-Nodes/
|
||||
Image/
|
||||
Load/save helpers
|
||||
Tile/untile
|
||||
Grid and layer composition
|
||||
Resize, compare, label
|
||||
Mask/
|
||||
Morphology, properties, bbox, aspect matching
|
||||
Vision/
|
||||
Garment and portrait segmentation
|
||||
Logic/
|
||||
Compare, gates, switches, branching, math, random choice
|
||||
Text/
|
||||
String, regex, split, concatenate, templates, scheduling
|
||||
Batch/
|
||||
Image batch, list helpers, any-list split
|
||||
Resolution/
|
||||
Smart resolution picker, ratio resize, preset resize, padding removal
|
||||
Loaders/
|
||||
Indexed LoRA, diffusion model, VAE, and CLIP loaders
|
||||
Utils/
|
||||
Sliders, booleans, universal pipe
|
||||
VTON/
|
||||
VTON Ultimate Processor
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Requirements
|
||||
|
||||
```text
|
||||
numpy
|
||||
Pillow
|
||||
scipy
|
||||
opencv-python
|
||||
scikit-image>=0.18.0
|
||||
transformers>=4.20.0
|
||||
tqdm>=4.62.0
|
||||
huggingface_hub>=0.16.0
|
||||
```
|
||||
|
||||
`VTON Ultimate Processor (NH)` also needs ONNX Runtime, which is not installed
|
||||
automatically (the CPU wheel would overwrite an existing GPU build). Install one:
|
||||
`python -m pip install onnxruntime-gpu` (NVIDIA) or `python -m pip install onnxruntime` (CPU).
|
||||
|
||||
Most image/text/logic nodes use common ComfyUI dependencies. VTON and Vision
|
||||
nodes download model assets on first use (`transformers` SegFormer weights for
|
||||
Vision: ~190 MB fashion, ~340 MB face).
|
||||
|
||||
---
|
||||
|
||||
## Development Notes
|
||||
|
||||
- Layout: `core/` holds shared helpers and model backends (no nodes); `nodes/<category>/`
|
||||
mirrors the ComfyUI menu, and every module there that defines `NODE_CLASS_MAPPINGS`
|
||||
is registered automatically by `__init__.py` - drop a file in the right folder
|
||||
and restart. VTON's vendored preprocessing lives in `nodes/vton/preprocess/`.
|
||||
- Tests: `python -m pytest custom_nodes/NH-Nodes/tests -q` from the ComfyUI root.
|
||||
- Restart ComfyUI after installing or updating nodes.
|
||||
- If a node appears cached in the browser, refresh the ComfyUI page after restart.
|
||||
- `Image Tile (NH)` and `Image Untile (NH)` must pass `tile_data` directly through
|
||||
the workflow so reconstruction uses the original tile layout.
|
||||
|
||||
---
|
||||
- **Guide:** [English](docs/en/README.md) · [Tiếng Việt](docs/vi/README.md)
|
||||
- **PDF guides:** [English](https://github.com/jetthuangai/NH-Nodes/releases/latest/download/NH-Nodes-Guide-EN.pdf) ·
|
||||
[Tiếng Việt](https://github.com/jetthuangai/NH-Nodes/releases/latest/download/NH-Nodes-Huong-dan-VI.pdf)
|
||||
- **Changelog:** [docs/CHANGELOG.md](docs/CHANGELOG.md) — read it when updating: some fixes change outputs on purpose.
|
||||
|
||||
## License
|
||||
|
||||
NH-Nodes' own code is [MIT](LICENSE). `VTON Ultimate Processor (NH)` and
|
||||
`Portrait Segment (NH)` depend on third-party code or models that are licensed for
|
||||
**non-commercial use only**, and the model behind `Garment Segment (NH)` declares no
|
||||
license. See [THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md) before using these
|
||||
three nodes commercially.
|
||||
NH-Nodes' own code is [MIT](LICENSE). Some features rely on third-party code or models with other terms — see
|
||||
[THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md):
|
||||
|
||||
If NH-Nodes helps your workflow, consider starring the repository:
|
||||
[github.com/jetthuangai/NH-Nodes](https://github.com/jetthuangai/NH-Nodes)
|
||||
- **VTON Ultimate Processor** and **Portrait Segment** are for **non-commercial use only** (CC BY-NC-SA 4.0 code and
|
||||
parsing models; non-commercial face-parsing model).
|
||||
- **Garment Segment**'s model does not declare a licence.
|
||||
- **SAM3 Segment** uses Meta's SAM 3 under the SAM License: royalty-free, with no non-commercial clause, but it bars
|
||||
military or warfare, nuclear, espionage, and gun or illegal-weapon uses. The weights and the `sam3` package are not
|
||||
included; you get them from Meta.
|
||||
|
||||
## Development
|
||||
|
||||
- Layout: `core/` holds shared helpers and model backends (no nodes); `nodes/<category>/` mirrors the menu, and every
|
||||
module there that defines `NODE_CLASS_MAPPINGS` is registered automatically; `web/` holds the frontend extensions.
|
||||
- Tests and lint (run from the ComfyUI root / the pack folder):
|
||||
|
||||
```bash
|
||||
python -m pytest custom_nodes/NH-Nodes/tests -q
|
||||
ruff check .
|
||||
```
|
||||
- Docs assets are generated from the live code: `docs/_tools/build_node_cards.py` (node cards) and
|
||||
`docs/_tools/build_examples.py` (example images), both run from the ComfyUI root.
|
||||
|
||||
## Author
|
||||
|
||||
[jetthuang.com](https://jetthuang.com) · [Hugging Face](https://huggingface.co/nhathoangfoto) ·
|
||||
[Support via PayPal](https://paypal.me/nhathoangfoto). If NH-Nodes helps your work, a ⭐ on GitHub is appreciated.
|
||||
|
||||
@@ -16,6 +16,7 @@ MIT license.
|
||||
| `VTON Ultimate Processor (NH)` | **No** | Its code and parsing models are CC BY-NC-SA 4.0 (FitDiT / OOTDiffusion) |
|
||||
| `Portrait Segment (NH)` | **No** | The face-parsing model card restricts use to non-commercial research and education |
|
||||
| `Garment Segment (NH)` | Unclear | The model uploader does not declare a license |
|
||||
| `SAM3 Segment (NH)` | Yes, within trade-control limits | Meta's SAM License is royalty-free and has no non-commercial clause, but it forbids ITAR-regulated activities and uses that trade controls prohibit (military or warfare, nuclear, espionage, guns and illegal weapons). The model and the `sam3` package are not part of NH-Nodes: you get them from Meta |
|
||||
|
||||
## Source code bundled in this repository
|
||||
|
||||
@@ -44,17 +45,49 @@ example, ONNX Runtime sessions in the vendored code are created through
|
||||
## Models downloaded at runtime
|
||||
|
||||
These files are **not** stored in this repository. The nodes download them from
|
||||
Hugging Face on first use.
|
||||
Hugging Face on first use, pinned to the revisions below (only the files the nodes need).
|
||||
They are looked up first in the folders registered as `vton_mask` and `nh_nodes`
|
||||
(configurable in `extra_model_paths.yaml`).
|
||||
|
||||
| Node | Hugging Face repository | Contents | Terms |
|
||||
|------|-------------------------|----------|-------|
|
||||
| VTON Ultimate Processor | [`kg-09/kg-vton-mask`](https://huggingface.co/kg-09/kg-vton-mask) | `dwpose/yolox_l.onnx`, `dwpose/dw-ll_ucoco_384.onnx` | DWPose / YOLOX weights, Apache-2.0 upstream |
|
||||
| VTON Ultimate Processor | [`kg-09/kg-vton-mask`](https://huggingface.co/kg-09/kg-vton-mask) @ `4648778` | `dwpose/yolox_l.onnx`, `dwpose/dw-ll_ucoco_384.onnx` | DWPose / YOLOX weights, Apache-2.0 upstream |
|
||||
| VTON Ultimate Processor | same | `humanparsing/parsing_atr.onnx`, `humanparsing/parsing_lip.onnx` | From the OOTDiffusion/FitDiT checkpoints (CC BY-NC-SA 4.0); trained on the ATR and LIP datasets, which are distributed for research use |
|
||||
| Portrait Segment | [`jonathandinu/face-parsing`](https://huggingface.co/jonathandinu/face-parsing) | SegFormer fine-tuned on CelebAMask-HQ | Model card: "non-commercial research and educational purposes" |
|
||||
| Garment Segment | [`1038lab/segformer_fashion`](https://huggingface.co/1038lab/segformer_fashion) | SegFormer trained for Fashionpedia classes | No license declared by the uploader (Fashionpedia annotations are CC BY 4.0) |
|
||||
| Portrait Segment | [`jonathandinu/face-parsing`](https://huggingface.co/jonathandinu/face-parsing) @ `758b82e` | SegFormer fine-tuned on CelebAMask-HQ | Model card: "non-commercial research and educational purposes" |
|
||||
| Garment Segment | [`1038lab/segformer_fashion`](https://huggingface.co/1038lab/segformer_fashion) @ `e5bae2d` | SegFormer trained for Fashionpedia classes | No license declared by the uploader (Fashionpedia annotations are CC BY 4.0) |
|
||||
|
||||
## Software and models you install yourself
|
||||
|
||||
`SAM3 Segment (NH)` runs Meta's SAM 3. NH-Nodes neither bundles nor downloads it, and the
|
||||
nodes only load it when you use them. You get both parts from Meta and accept Meta's terms
|
||||
yourself.
|
||||
|
||||
| What | Where it comes from | Terms |
|
||||
|------|---------------------|-------|
|
||||
| `sam3` package (code) | [`facebookresearch/sam3`](https://github.com/facebookresearch/sam3), installed by the user with pip | SAM License |
|
||||
| SAM 3 checkpoint `sam3.pt` | [`facebook/sam3`](https://huggingface.co/facebook/sam3) on Hugging Face, a gated repository: access is granted after the user accepts the license. It goes in the `sam3` model folder (`models/sam3`, or any location added under `sam3` in `extra_model_paths.yaml`), which other SAM 3 packs share | SAM License |
|
||||
|
||||
The [SAM License](https://github.com/facebookresearch/sam3/blob/main/LICENSE) (Meta, last
|
||||
updated 19 November 2025) is a non-exclusive, worldwide, non-transferable, royalty-free
|
||||
limited license to use, reproduce, distribute, copy, modify and create derivative works of
|
||||
the SAM Materials. It does not mention commercial use. What matters to a user of NH-Nodes:
|
||||
|
||||
- **Trade controls.** You must not be a target of trade controls and must comply with them.
|
||||
The materials may not be used for activities subject to the International Traffic in Arms
|
||||
Regulations (ITAR), or for end uses that trade controls prohibit, including military or
|
||||
warfare purposes, nuclear industries or applications, espionage, and the development or
|
||||
use of guns or illegal weapons.
|
||||
- Whoever redistributes the SAM Materials must include a copy of the license. NH-Nodes does
|
||||
not redistribute them.
|
||||
- Research publications that use the SAM Materials must acknowledge it.
|
||||
- The materials may not be used to reverse engineer or decompile them, nor to encourage
|
||||
others to.
|
||||
- They are provided "as is", and Meta may terminate the license if you breach it.
|
||||
|
||||
## Python dependencies
|
||||
|
||||
Dependencies listed in `requirements.txt` are installed from PyPI and are not
|
||||
redistributed here; each keeps its own license. ONNX Runtime (MIT) is optional
|
||||
and only needed by the VTON node.
|
||||
and only needed by the VTON node. The `sam3` package (SAM License) and its extras
|
||||
(`timm`, `ftfy`, `regex`, `iopath`) are optional too: only `SAM3 Segment (NH)` needs
|
||||
them, and you install them yourself.
|
||||
|
||||
@@ -45,58 +45,16 @@ for _category in pkgutil.iter_modules(_nodes_pkg.__path__):
|
||||
NODE_DISPLAY_NAME_MAPPINGS.update(getattr(_module, "NODE_DISPLAY_NAME_MAPPINGS", {}))
|
||||
_module_count += 1
|
||||
|
||||
# Route-only module: registers /nh-nodes/large-preview/* on import.
|
||||
# Route-only modules: they register the /nh-nodes/* HTTP routes on import.
|
||||
try:
|
||||
from .core import large_preview_routes # noqa: F401
|
||||
except Exception as e:
|
||||
print(f"[NH-Nodes] Failed to import core.large_preview_routes: {e}")
|
||||
|
||||
try:
|
||||
from aiohttp import web
|
||||
from server import PromptServer
|
||||
|
||||
@PromptServer.instance.routes.get("/nh-nodes/load-images-folder/count")
|
||||
async def nh_load_images_folder_count(request):
|
||||
folder_path = request.query.get("folder_path", "")
|
||||
recursive = request.query.get("recursive", "").lower() in {"1", "true", "yes", "on"}
|
||||
try:
|
||||
from .nodes.image.image_tools_nodes import _collect_image_files, _resolve_load_dir
|
||||
|
||||
resolved_folder = _resolve_load_dir(folder_path)
|
||||
image_count = len(_collect_image_files(resolved_folder, recursive=recursive))
|
||||
return web.json_response({"count": image_count, "path": resolved_folder})
|
||||
except Exception as exc:
|
||||
return web.json_response({"count": 0, "error": str(exc)})
|
||||
|
||||
@PromptServer.instance.routes.get("/nh-nodes/smart-resolution-picker/presets")
|
||||
async def nh_smart_resolution_picker_presets(request):
|
||||
try:
|
||||
from .core.resolution_data import DEFAULT_MODEL_LABEL, DEFAULT_PRESET, MODEL_LABELS, PRESET_LABELS_BY_MODEL
|
||||
from .nodes.resolution.smart_resolution_picker import TARGET_RESOLUTION_LEVELS, _resolution_candidates
|
||||
|
||||
ratios_by_model_level = {}
|
||||
for model_label in MODEL_LABELS:
|
||||
ratios_by_model_level[model_label] = {}
|
||||
for resolution_level in TARGET_RESOLUTION_LEVELS:
|
||||
candidates = _resolution_candidates(model_label, resolution_level)
|
||||
ratios = []
|
||||
for entry in candidates:
|
||||
if entry["aspect"] not in ratios:
|
||||
ratios.append(entry["aspect"])
|
||||
ratios_by_model_level[model_label][resolution_level] = ratios
|
||||
|
||||
return web.json_response({
|
||||
"models": MODEL_LABELS,
|
||||
"default_model": DEFAULT_MODEL_LABEL,
|
||||
"default_preset": DEFAULT_PRESET,
|
||||
"presets_by_model": PRESET_LABELS_BY_MODEL,
|
||||
"resolution_levels": TARGET_RESOLUTION_LEVELS,
|
||||
"ratios_by_model_level": ratios_by_model_level,
|
||||
})
|
||||
except Exception as exc:
|
||||
return web.json_response({"error": str(exc)}, status=500)
|
||||
from .core import folder_routes # noqa: F401
|
||||
except Exception as e:
|
||||
print(f"[NH-Nodes] Failed to register web routes: {e}")
|
||||
print(f"[NH-Nodes] Failed to import core.folder_routes: {e}")
|
||||
|
||||
__all__ = ["NODE_CLASS_MAPPINGS", "NODE_DISPLAY_NAME_MAPPINGS", "WEB_DIRECTORY"]
|
||||
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
"""HTTP routes behind the NH-Nodes frontend extensions.
|
||||
|
||||
GET /nh-nodes/load-images-folder/count how many images a folder loader sees (auto increment / wrap)
|
||||
GET /nh-nodes/smart-resolution-picker/presets the preset lists for the resolution pickers' dropdowns
|
||||
|
||||
Importing this module registers both on ComfyUI's PromptServer, like core.large_preview_routes. The handlers are plain
|
||||
module-level functions, so a test can serve them from any aiohttp app through ``register_routes``.
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import os
|
||||
|
||||
from aiohttp import web
|
||||
|
||||
from .image_folders import collect_image_files, resolve_load_dir
|
||||
|
||||
COUNT_PATH = "/nh-nodes/load-images-folder/count"
|
||||
PRESETS_PATH = "/nh-nodes/smart-resolution-picker/presets"
|
||||
|
||||
_MAX_FOLDER_PATH_CHARS = 4096
|
||||
# Spellings of the ``recursive`` query parameter. Anything else is a bad request, not a silent "false".
|
||||
_FLAGS = {"1": True, "true": True, "yes": True, "on": True, "": False, "0": False, "false": False, "no": False, "off": False}
|
||||
|
||||
|
||||
def _error(status, message):
|
||||
# Fixed short texts only: ComfyUI can be reachable from the network (--listen), so an answer never carries a
|
||||
# resolved path or the text of an exception.
|
||||
return web.json_response({"error": message}, status=status)
|
||||
|
||||
|
||||
def _count_images(folder_path, recursive):
|
||||
"""How many images a loader with these settings sees. Blocking (a recursive walk): run it in a worker thread."""
|
||||
folder = resolve_load_dir(folder_path)
|
||||
if not os.path.isdir(folder):
|
||||
raise FileNotFoundError("folder not found")
|
||||
try:
|
||||
return len(collect_image_files(folder, recursive=recursive))
|
||||
except ValueError:
|
||||
return 0 # the folder exists but holds no image
|
||||
|
||||
|
||||
async def nh_load_images_folder_count(request):
|
||||
folder_path = request.query.get("folder_path", "")
|
||||
recursive = _FLAGS.get(request.query.get("recursive", "").strip().lower())
|
||||
if recursive is None:
|
||||
return _error(400, "recursive must be true or false")
|
||||
if len(folder_path) > _MAX_FOLDER_PATH_CHARS or "\0" in folder_path:
|
||||
return _error(400, "invalid folder_path")
|
||||
|
||||
try:
|
||||
count = await asyncio.to_thread(_count_images, folder_path, recursive)
|
||||
except FileNotFoundError:
|
||||
return _error(404, "folder not found")
|
||||
except PermissionError:
|
||||
return _error(403, "folder is not readable")
|
||||
except Exception as exc:
|
||||
print(f"[NH-Nodes] load-images-folder/count failed: {type(exc).__name__}: {exc}")
|
||||
return _error(500, "could not read the folder")
|
||||
return web.json_response({"count": count})
|
||||
|
||||
|
||||
async def nh_smart_resolution_picker_presets(request):
|
||||
try:
|
||||
# Imported here: core must not depend on the node modules at import time.
|
||||
from ..nodes.resolution.smart_resolution_picker import TARGET_RESOLUTION_LEVELS, _resolution_candidates
|
||||
from .resolution_data import DEFAULT_MODEL_LABEL, DEFAULT_PRESET, MODEL_LABELS, PRESET_LABELS_BY_MODEL
|
||||
|
||||
ratios_by_model_level = {}
|
||||
for model_label in MODEL_LABELS:
|
||||
ratios_by_model_level[model_label] = {}
|
||||
for resolution_level in TARGET_RESOLUTION_LEVELS:
|
||||
candidates = _resolution_candidates(model_label, resolution_level)
|
||||
ratios = []
|
||||
for entry in candidates:
|
||||
if entry["aspect"] not in ratios:
|
||||
ratios.append(entry["aspect"])
|
||||
ratios_by_model_level[model_label][resolution_level] = ratios
|
||||
|
||||
return web.json_response({
|
||||
"models": MODEL_LABELS,
|
||||
"default_model": DEFAULT_MODEL_LABEL,
|
||||
"default_preset": DEFAULT_PRESET,
|
||||
"presets_by_model": PRESET_LABELS_BY_MODEL,
|
||||
"resolution_levels": TARGET_RESOLUTION_LEVELS,
|
||||
"ratios_by_model_level": ratios_by_model_level,
|
||||
})
|
||||
except Exception as exc:
|
||||
print(f"[NH-Nodes] smart-resolution-picker/presets failed: {type(exc).__name__}: {exc}")
|
||||
return _error(500, "could not build the preset lists")
|
||||
|
||||
|
||||
def register_routes(routes):
|
||||
"""Attach the handlers to an aiohttp RouteTableDef (ComfyUI's ``PromptServer.instance.routes``)."""
|
||||
routes.get(COUNT_PATH)(nh_load_images_folder_count)
|
||||
routes.get(PRESETS_PATH)(nh_smart_resolution_picker_presets)
|
||||
|
||||
|
||||
try:
|
||||
from server import PromptServer
|
||||
|
||||
register_routes(PromptServer.instance.routes)
|
||||
except Exception as exc:
|
||||
print(f"[NH-Nodes] Failed to register folder routes: {exc}")
|
||||
@@ -0,0 +1,78 @@
|
||||
"""Image folder helpers shared by the folder loader nodes and the folder-count HTTP route.
|
||||
|
||||
``resolve_load_dir`` turns the folder text of a loader into a directory, ``collect_image_files`` lists the images in
|
||||
it in natural order. Nothing here needs torch or Pillow.
|
||||
"""
|
||||
|
||||
import os
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
import folder_paths
|
||||
|
||||
IMAGE_EXTENSIONS = {".png", ".jpg", ".jpeg", ".webp", ".avif", ".bmp", ".tif", ".tiff"}
|
||||
|
||||
|
||||
def natural_key(value):
|
||||
"""Sort key that puts 'img2' before 'img10' and ignores case."""
|
||||
return [int(part) if part.isdigit() else part.lower() for part in re.split(r"(\d+)", value)]
|
||||
|
||||
|
||||
def resolve_load_dir(folder_path, image="", upload_probe=""):
|
||||
"""The folder a loader reads.
|
||||
|
||||
``folder_path`` when given (relative to ComfyUI's input folder unless absolute; a file stands for its folder),
|
||||
else the folder of the uploaded ``image`` / ``upload_probe``, else the input folder.
|
||||
"""
|
||||
folder_path = (folder_path or "").strip()
|
||||
if folder_path:
|
||||
resolved = folder_path
|
||||
if not os.path.isabs(resolved):
|
||||
resolved = os.path.join(folder_paths.get_input_directory(), resolved)
|
||||
if os.path.isfile(resolved):
|
||||
resolved = os.path.dirname(resolved)
|
||||
return resolved
|
||||
|
||||
upload_name = image or upload_probe
|
||||
if upload_name:
|
||||
return os.path.dirname(folder_paths.get_annotated_filepath(upload_name))
|
||||
|
||||
return folder_paths.get_input_directory()
|
||||
|
||||
|
||||
def collect_image_files(folder_path, recursive=False):
|
||||
"""The images of ``folder_path`` in natural order, as names (paths relative to the folder below it).
|
||||
|
||||
Sub-folders are read when ``recursive`` is set, and also when the folder holds no image itself. ValueError for a
|
||||
folder that does not exist or has no images.
|
||||
"""
|
||||
if not os.path.isdir(folder_path):
|
||||
raise ValueError(f"Folder does not exist: {folder_path}")
|
||||
|
||||
files = []
|
||||
for entry in os.listdir(folder_path):
|
||||
full_path = os.path.join(folder_path, entry)
|
||||
if os.path.isfile(full_path) and Path(entry).suffix.lower() in IMAGE_EXTENSIONS:
|
||||
files.append(entry)
|
||||
|
||||
if recursive or not files:
|
||||
seen = set(files)
|
||||
models_dir = os.path.normcase(os.path.abspath(folder_paths.models_dir))
|
||||
for current_root, dir_names, file_names in os.walk(folder_path):
|
||||
# Natural order, and never into the ComfyUI models folder (a walk from one of its parents would crawl it).
|
||||
dir_names[:] = sorted(
|
||||
(name for name in dir_names if os.path.normcase(os.path.abspath(os.path.join(current_root, name))) != models_dir),
|
||||
key=natural_key,
|
||||
)
|
||||
for file_name in sorted(file_names, key=natural_key):
|
||||
full_path = os.path.join(current_root, file_name)
|
||||
if os.path.isfile(full_path) and Path(file_name).suffix.lower() in IMAGE_EXTENSIONS:
|
||||
rel_path = os.path.relpath(full_path, folder_path)
|
||||
if rel_path not in seen:
|
||||
seen.add(rel_path)
|
||||
files.append(rel_path)
|
||||
|
||||
files.sort(key=natural_key)
|
||||
if not files:
|
||||
raise ValueError(f"No images found in folder: {folder_path}")
|
||||
return files
|
||||
@@ -0,0 +1,47 @@
|
||||
"""Pillow / tensor helpers shared by the image nodes (save, load, compare, label, resize, layer composite).
|
||||
|
||||
IMAGE tensors become PIL images with core.utils.tensor_to_pil (rgb=True, plus keep_alpha=True for layers);
|
||||
``pil_to_image_tensor`` converts back in ComfyUI's intermediate dtype.
|
||||
"""
|
||||
|
||||
from PIL import Image, ImageColor
|
||||
|
||||
import comfy.model_management
|
||||
|
||||
from .utils import parse_hex_color, pil_to_tensor
|
||||
|
||||
RESAMPLING = getattr(Image, "Resampling", Image)
|
||||
|
||||
|
||||
def parse_color(value, default):
|
||||
"""Parse a colour into an (r, g, b, a) tuple of 0-255 ints; ``default`` when the text is not a colour.
|
||||
|
||||
'#RGB', '#RRGGBB' and '#RRGGBBAA' go through core.utils.parse_hex_color (opaque unless AA is given). Other CSS
|
||||
colours Pillow understands ('red', 'rgb(1, 2, 3)') keep working, as they did before.
|
||||
"""
|
||||
text = str(value or "").strip()
|
||||
color = parse_hex_color(text, ())
|
||||
if color:
|
||||
return color
|
||||
try:
|
||||
return ImageColor.getcolor(text, "RGBA")
|
||||
except ValueError:
|
||||
return tuple(default) + (255,) * (4 - len(default))
|
||||
|
||||
|
||||
|
||||
|
||||
def pil_to_image_tensor(image):
|
||||
"""A PIL image as a [1, H, W, C] float IMAGE in ComfyUI's intermediate dtype."""
|
||||
return pil_to_tensor(image).to(dtype=comfy.model_management.intermediate_dtype())
|
||||
|
||||
|
||||
def resize_pil(image, size, method):
|
||||
"""``image`` resized to ``size`` (width, height) with the 'lanczos', 'bicubic', 'bilinear' or 'nearest' filter."""
|
||||
resample = {
|
||||
"lanczos": RESAMPLING.LANCZOS,
|
||||
"bicubic": RESAMPLING.BICUBIC,
|
||||
"bilinear": RESAMPLING.BILINEAR,
|
||||
"nearest": RESAMPLING.NEAREST,
|
||||
}[method]
|
||||
return image.resize(size, resample)
|
||||
@@ -10,10 +10,11 @@ import uuid
|
||||
from pathlib import Path
|
||||
|
||||
import folder_paths
|
||||
import numpy as np
|
||||
import torch
|
||||
from PIL import Image, ImageChops, ImageDraw
|
||||
|
||||
from .utils import tensor_to_pil
|
||||
|
||||
|
||||
CACHE_SUBDIR = "nh_large_preview"
|
||||
CACHE_ID_RE = re.compile(r"^[a-f0-9]{32}$")
|
||||
@@ -139,21 +140,6 @@ def delete_cache(cache_id):
|
||||
}
|
||||
|
||||
|
||||
def _tensor_to_pil(image_tensor):
|
||||
if not isinstance(image_tensor, torch.Tensor):
|
||||
raise TypeError(f"Expected IMAGE tensor, got {type(image_tensor).__name__}")
|
||||
|
||||
array = image_tensor.detach().cpu().numpy()
|
||||
array = np.clip(array * 255.0, 0, 255).astype(np.uint8)
|
||||
if array.ndim == 2:
|
||||
return Image.fromarray(array).convert("RGB")
|
||||
if array.ndim == 3 and array.shape[-1] == 1:
|
||||
return Image.fromarray(array[:, :, 0]).convert("RGB")
|
||||
if array.ndim == 3 and array.shape[-1] == 4:
|
||||
return Image.fromarray(array).convert("RGBA")
|
||||
return Image.fromarray(array).convert("RGB")
|
||||
|
||||
|
||||
def _preview_dimensions(width, height, preview_megapixels):
|
||||
width = max(1, int(width))
|
||||
height = max(1, int(height))
|
||||
@@ -292,7 +278,9 @@ def create_preview_cache(
|
||||
batch_index=0,
|
||||
batch_count=1,
|
||||
):
|
||||
source = _tensor_to_pil(image_tensor)
|
||||
if not isinstance(image_tensor, torch.Tensor):
|
||||
raise TypeError(f"Expected IMAGE tensor, got {type(image_tensor).__name__}")
|
||||
source = tensor_to_pil(image_tensor, rgb=True, keep_alpha=True)
|
||||
source_width, source_height = source.size
|
||||
preview_width, preview_height = _preview_dimensions(source_width, source_height, preview_megapixels)
|
||||
preview = source.copy()
|
||||
|
||||
@@ -1,67 +1,106 @@
|
||||
"""HTTP routes for NH large image preview cache."""
|
||||
"""HTTP routes for NH large image preview cache.
|
||||
|
||||
Importing this module registers the routes on ComfyUI's PromptServer. The handlers are plain module-level functions,
|
||||
so a test can serve them from any aiohttp app through ``register_routes``.
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
|
||||
from aiohttp import web
|
||||
|
||||
from .large_preview_cache import cache_info, cleanup_cache, delete_cache, get_thumb_path, get_tile_path, read_manifest
|
||||
|
||||
|
||||
def _without_paths(value):
|
||||
"""Answers go to the browser: drop absolute filesystem paths (the Cache Manager node still shows them)."""
|
||||
if isinstance(value, dict):
|
||||
return {key: _without_paths(item) for key, item in value.items() if key not in ("path", "root")}
|
||||
if isinstance(value, list):
|
||||
return [_without_paths(item) for item in value]
|
||||
return value
|
||||
|
||||
|
||||
async def _read_json_object(request):
|
||||
"""The JSON object in the request body ({} when the body is empty). ValueError for anything else."""
|
||||
try:
|
||||
text = await request.text()
|
||||
payload = json.loads(text) if text.strip() else {}
|
||||
except ValueError: # not JSON, or not UTF-8 text: json.JSONDecodeError and UnicodeDecodeError both are ValueErrors
|
||||
raise ValueError("request body is not valid JSON") from None
|
||||
if not isinstance(payload, dict):
|
||||
raise ValueError("request body must be a JSON object")
|
||||
return payload
|
||||
|
||||
|
||||
async def nh_large_preview_manifest(request):
|
||||
try:
|
||||
manifest = read_manifest(request.match_info["cache_id"])
|
||||
return web.json_response(manifest)
|
||||
except FileNotFoundError:
|
||||
return web.json_response({"error": "manifest not found"}, status=404)
|
||||
except ValueError as exc:
|
||||
return web.json_response({"error": str(exc)}, status=400)
|
||||
|
||||
|
||||
async def nh_large_preview_thumb(request):
|
||||
try:
|
||||
return web.FileResponse(get_thumb_path(request.match_info["cache_id"]))
|
||||
except FileNotFoundError:
|
||||
return web.json_response({"error": "thumbnail not found"}, status=404)
|
||||
except ValueError as exc:
|
||||
return web.json_response({"error": str(exc)}, status=400)
|
||||
|
||||
|
||||
async def nh_large_preview_tile(request):
|
||||
try:
|
||||
return web.FileResponse(get_tile_path(
|
||||
request.match_info["cache_id"],
|
||||
request.match_info["level"],
|
||||
request.match_info["tile"],
|
||||
))
|
||||
except FileNotFoundError:
|
||||
return web.json_response({"error": "tile not available in thumbnail-only preview"}, status=404)
|
||||
except ValueError as exc:
|
||||
return web.json_response({"error": str(exc)}, status=400)
|
||||
|
||||
|
||||
async def nh_large_preview_cache_info(request):
|
||||
try:
|
||||
# Directory walks run in a worker thread so a large cache never stalls the server's event loop.
|
||||
return web.json_response(_without_paths(await asyncio.to_thread(cache_info)))
|
||||
except ValueError as exc:
|
||||
return web.json_response({"error": str(exc)}, status=400)
|
||||
|
||||
|
||||
async def nh_large_preview_cleanup(request):
|
||||
try:
|
||||
payload = await _read_json_object(request)
|
||||
except ValueError as exc:
|
||||
return web.json_response({"error": str(exc)}, status=400)
|
||||
try:
|
||||
cache_id = payload.get("cache_id", "")
|
||||
if cache_id:
|
||||
result = await asyncio.to_thread(delete_cache, cache_id)
|
||||
else:
|
||||
result = await asyncio.to_thread(cleanup_cache, payload.get("max_age_hours", 24), payload.get("max_total_mb", 0))
|
||||
return web.json_response(_without_paths(result))
|
||||
except (ValueError, TypeError, OverflowError) as exc: # a bad id, or a limit that is not a number
|
||||
return web.json_response({"error": str(exc)}, status=400)
|
||||
|
||||
|
||||
def register_routes(routes):
|
||||
"""Attach the handlers to an aiohttp RouteTableDef (ComfyUI's ``PromptServer.instance.routes``)."""
|
||||
routes.get("/nh-nodes/large-preview/manifest/{cache_id}")(nh_large_preview_manifest)
|
||||
routes.get("/nh-nodes/large-preview/thumb/{cache_id}")(nh_large_preview_thumb)
|
||||
routes.get("/nh-nodes/large-preview/tile/{cache_id}/{level}/{tile}")(nh_large_preview_tile)
|
||||
routes.get("/nh-nodes/large-preview/cache-info")(nh_large_preview_cache_info)
|
||||
routes.post("/nh-nodes/large-preview/cleanup")(nh_large_preview_cleanup)
|
||||
|
||||
|
||||
try:
|
||||
from server import PromptServer
|
||||
|
||||
@PromptServer.instance.routes.get("/nh-nodes/large-preview/manifest/{cache_id}")
|
||||
async def nh_large_preview_manifest(request):
|
||||
try:
|
||||
manifest = read_manifest(request.match_info["cache_id"])
|
||||
return web.json_response(manifest)
|
||||
except FileNotFoundError:
|
||||
return web.json_response({"error": "manifest not found"}, status=404)
|
||||
except ValueError as exc:
|
||||
return web.json_response({"error": str(exc)}, status=400)
|
||||
|
||||
@PromptServer.instance.routes.get("/nh-nodes/large-preview/thumb/{cache_id}")
|
||||
async def nh_large_preview_thumb(request):
|
||||
try:
|
||||
return web.FileResponse(get_thumb_path(request.match_info["cache_id"]))
|
||||
except FileNotFoundError:
|
||||
return web.json_response({"error": "thumbnail not found"}, status=404)
|
||||
except ValueError as exc:
|
||||
return web.json_response({"error": str(exc)}, status=400)
|
||||
|
||||
@PromptServer.instance.routes.get("/nh-nodes/large-preview/tile/{cache_id}/{level}/{tile}")
|
||||
async def nh_large_preview_tile(request):
|
||||
try:
|
||||
return web.FileResponse(get_tile_path(
|
||||
request.match_info["cache_id"],
|
||||
request.match_info["level"],
|
||||
request.match_info["tile"],
|
||||
))
|
||||
except FileNotFoundError:
|
||||
return web.json_response({"error": "tile not available in thumbnail-only preview"}, status=404)
|
||||
except ValueError as exc:
|
||||
return web.json_response({"error": str(exc)}, status=400)
|
||||
|
||||
@PromptServer.instance.routes.get("/nh-nodes/large-preview/cache-info")
|
||||
async def nh_large_preview_cache_info(request):
|
||||
try:
|
||||
return web.json_response(cache_info())
|
||||
except ValueError as exc:
|
||||
return web.json_response({"error": str(exc)}, status=400)
|
||||
|
||||
@PromptServer.instance.routes.post("/nh-nodes/large-preview/cleanup")
|
||||
async def nh_large_preview_cleanup(request):
|
||||
try:
|
||||
payload = await request.json()
|
||||
except Exception:
|
||||
payload = {}
|
||||
try:
|
||||
cache_id = payload.get("cache_id", "")
|
||||
if cache_id:
|
||||
result = delete_cache(cache_id)
|
||||
else:
|
||||
result = cleanup_cache(payload.get("max_age_hours", 24), payload.get("max_total_mb", 0))
|
||||
return web.json_response(result)
|
||||
except ValueError as exc:
|
||||
return web.json_response({"error": str(exc)}, status=400)
|
||||
register_routes(PromptServer.instance.routes)
|
||||
except Exception as exc:
|
||||
print(f"[NH-Nodes] Failed to register large preview routes: {exc}")
|
||||
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
"""Mask helpers shared by the NH mask and image nodes.
|
||||
|
||||
NH-Nodes' own code (MIT). It is deliberately not part of core/utils_mask.py: that file is third-party
|
||||
FitDiT code under CC BY-NC-SA 4.0 (see THIRD_PARTY_NOTICES.md), and plain MIT nodes must not depend on it.
|
||||
"""
|
||||
|
||||
import numpy as np
|
||||
|
||||
|
||||
def mask_bbox(mask, threshold=0.5):
|
||||
"""Inclusive bounding box ``(x1, y1, x2, y2)`` of the pixels of a 2-D mask that are above ``threshold``.
|
||||
|
||||
``mask`` is an [H, W] array (a torch mask goes through ``.cpu().numpy()`` first). A pixel counts only when it is
|
||||
strictly greater than ``threshold``, so a value of exactly 0.5 is outside, as in every NH mask node. The result is
|
||||
four Python ints; (x2, y2) is the last pixel inside, so width = x2 - x1 + 1 and height = y2 - y1 + 1.
|
||||
Returns None when no pixel is above the threshold, i.e. for an empty mask. Anything that is not 2-D raises ValueError.
|
||||
"""
|
||||
inside = np.asarray(mask) > threshold
|
||||
if inside.ndim != 2:
|
||||
raise ValueError(f"mask_bbox expects a 2-D [H, W] mask, got shape {inside.shape}")
|
||||
rows = np.flatnonzero(inside.any(axis=1))
|
||||
if rows.size == 0:
|
||||
return None
|
||||
cols = np.flatnonzero(inside.any(axis=0))
|
||||
return int(cols[0]), int(rows[0]), int(cols[-1]), int(rows[-1])
|
||||
@@ -0,0 +1,131 @@
|
||||
"""Model-folder, download and VRAM helpers shared by the SegFormer and VTON model loaders.
|
||||
|
||||
Both model families follow the same rules:
|
||||
|
||||
* Model folders are registered with ComfyUI's ``folder_paths`` under the names
|
||||
``nh_nodes`` (SegFormer) and ``vton_mask`` (VTON). Users can add more
|
||||
locations with those names in ``extra_model_paths.yaml``; every registered
|
||||
location is searched before anything is downloaded. Each name is registered
|
||||
with the extension of its model files (``.safetensors``, ``.onnx``), so
|
||||
ComfyUI's model lists show those files and not the Hugging Face cache
|
||||
metadata that downloads leave in the same folder.
|
||||
* Downloads always go to the default folder under the models dir, pinned to a
|
||||
fixed Hugging Face revision.
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
import torch
|
||||
|
||||
try:
|
||||
import folder_paths
|
||||
except Exception: # running outside ComfyUI (tests)
|
||||
folder_paths = None
|
||||
|
||||
try:
|
||||
import comfy.model_management as _mm
|
||||
except Exception:
|
||||
_mm = None
|
||||
|
||||
|
||||
def models_dir():
|
||||
"""ComfyUI's models directory, read on every call (--models-directory, tests)."""
|
||||
if folder_paths is not None:
|
||||
return folder_paths.models_dir
|
||||
return os.path.join(os.path.dirname(__file__), "models")
|
||||
|
||||
|
||||
def unique_dirs(paths):
|
||||
"""Drop repeats (same folder spelled differently) but keep the first spelling and the order."""
|
||||
seen, unique = set(), []
|
||||
for path in paths:
|
||||
key = os.path.normcase(os.path.abspath(path))
|
||||
if key not in seen:
|
||||
seen.add(key)
|
||||
unique.append(path)
|
||||
return unique
|
||||
|
||||
|
||||
def _list_only(folder_name, extensions):
|
||||
"""Add `extensions` to the file types ComfyUI lists for the registered `folder_name`.
|
||||
|
||||
add_model_folder_path registers a name with an empty extension set, which ComfyUI reads as "list
|
||||
every file": /models and the Model Library then show the .cache/huggingface metadata of our
|
||||
downloads and a repo's .gitattributes. The set is replaced, never changed in place (ComfyUI shares
|
||||
one set between many folder names), and the locations list stays the very same object, so every
|
||||
extra_model_paths.yaml entry survives.
|
||||
"""
|
||||
if not extensions:
|
||||
return
|
||||
registry = folder_paths.folder_names_and_paths
|
||||
paths, current = registry[folder_name]
|
||||
merged = {*current, *extensions}
|
||||
if merged != set(current):
|
||||
registry[folder_name] = (paths, merged)
|
||||
|
||||
|
||||
def register_model_folder(folder_name, default_dir, extensions=()):
|
||||
"""Register `folder_name` with ComfyUI and return every folder searched for it, best first.
|
||||
|
||||
ComfyUI applies extra_model_paths.yaml before custom nodes load, so locations the user
|
||||
configured under `folder_name` are already registered; they come first. `default_dir`
|
||||
is added too (a no-op when already present) and is always part of the result, so models
|
||||
that live there are found whether or not the user configured anything. Safe to call repeatedly.
|
||||
|
||||
`extensions` (".onnx") are the file types of the models in these folders: ComfyUI's lists for
|
||||
`folder_name` then show only those, also for the locations the user added.
|
||||
"""
|
||||
registered = []
|
||||
if folder_paths is not None:
|
||||
try:
|
||||
folder_paths.add_model_folder_path(folder_name, default_dir)
|
||||
registered = folder_paths.get_folder_paths(folder_name)
|
||||
_list_only(folder_name, extensions) # last: if it fails, the locations above are still returned
|
||||
except Exception as exc: # an unusual ComfyUI build: fall back to the default location alone
|
||||
print(f"[NH-Nodes] Could not register model folder '{folder_name}' with ComfyUI: {exc}")
|
||||
return unique_dirs([*registered, default_dir])
|
||||
|
||||
|
||||
def download_pinned(repo_id, revision, target, allow_patterns):
|
||||
"""snapshot_download of `allow_patterns` from `repo_id` at `revision` into `target`.
|
||||
|
||||
Finished files are kept and partial ones resume, so calling it again after an interrupted
|
||||
download completes the folder. Failures are re-raised with where to get the files by hand.
|
||||
"""
|
||||
from huggingface_hub import snapshot_download
|
||||
|
||||
os.makedirs(target, exist_ok=True)
|
||||
print(f"[NH-Nodes] Downloading {repo_id}@{revision[:8]} -> {target}")
|
||||
try:
|
||||
snapshot_download(repo_id=repo_id, revision=revision, local_dir=target, allow_patterns=list(allow_patterns))
|
||||
except Exception as exc:
|
||||
raise RuntimeError(
|
||||
f"[NH-Nodes] Downloading {repo_id} (revision {revision[:8]}) into {target} failed: {exc}\n"
|
||||
f"Run again once the connection works (finished files are kept), or download "
|
||||
f"{', '.join(allow_patterns)} from https://huggingface.co/{repo_id}/tree/{revision} into that folder."
|
||||
) from exc
|
||||
|
||||
|
||||
def make_room_on_gpu(nbytes):
|
||||
"""Ask ComfyUI to unload its own models until about `nbytes` of VRAM are free.
|
||||
|
||||
ComfyUI's own loaders ask for their size plus the VRAM it keeps in reserve (extra_reserved_memory(),
|
||||
set by --reserve-vram or its built-in default), so that reserve is added here as well: asking for
|
||||
`nbytes` alone would leave us that much short. Also hands PyTorch's cached blocks back to the
|
||||
driver: libraries that allocate outside PyTorch (ONNX Runtime) cannot use them. Nothing happens
|
||||
without a CUDA device, when ComfyUI itself runs on the CPU, or outside ComfyUI. A failure is
|
||||
reported, not raised: the caller still tries its allocation.
|
||||
"""
|
||||
if _mm is None or not torch.cuda.is_available():
|
||||
return
|
||||
try:
|
||||
# ComfyUI's loaded models carry an indexed device (cuda:0); an index-less
|
||||
# torch.device("cuda") compares unequal to it and would match none of them.
|
||||
device = _mm.get_torch_device()
|
||||
if device.type != "cuda":
|
||||
return
|
||||
reserved = getattr(_mm, "extra_reserved_memory", None) # absent in older ComfyUI builds
|
||||
_mm.free_memory(nbytes + (reserved() if reserved else 0), device)
|
||||
_mm.soft_empty_cache()
|
||||
except Exception as exc:
|
||||
print(f"[NH-Nodes] Could not ask ComfyUI to free VRAM ({exc}); continuing")
|
||||
@@ -2,6 +2,10 @@
|
||||
|
||||
Data is kept separate from node logic so new model families can be added
|
||||
without changing execution code.
|
||||
|
||||
min_res / max_res are the clamp range for *custom* sizes. Curated presets may go
|
||||
beyond max_res (e.g. 4 MP tiers); their own reliability/warning fields say how
|
||||
safe they are, and the node reports that warning in its info output.
|
||||
"""
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,126 @@
|
||||
"""Arithmetic expression evaluator shared by Math Eval (NH) and layer position expressions.
|
||||
|
||||
Accepted: numbers, named variables, + - * / // % **, unary + and -, and an optional
|
||||
whitelist of functions. Everything is computed in floating point, so no expression can
|
||||
build a huge integer and freeze the worker (``9**9**9`` simply overflows and is reported).
|
||||
"""
|
||||
|
||||
import ast
|
||||
import math
|
||||
import numbers
|
||||
import operator
|
||||
|
||||
# Python's ** returns a complex number for a negative base with a fractional exponent;
|
||||
# math.pow raises instead, which is what a node input needs.
|
||||
_BINARY = {
|
||||
ast.Add: operator.add,
|
||||
ast.Sub: operator.sub,
|
||||
ast.Mult: operator.mul,
|
||||
ast.Div: operator.truediv,
|
||||
ast.FloorDiv: operator.floordiv,
|
||||
ast.Mod: operator.mod,
|
||||
ast.Pow: math.pow,
|
||||
}
|
||||
_UNARY = {
|
||||
ast.UAdd: operator.pos,
|
||||
ast.USub: operator.neg,
|
||||
}
|
||||
_DIVISIONS = (ast.Div, ast.FloorDiv, ast.Mod)
|
||||
|
||||
MATH_FUNCTIONS = {
|
||||
"min": min,
|
||||
"max": max,
|
||||
"abs": abs,
|
||||
"round": lambda value, digits=0: round(value, int(digits)),
|
||||
"sqrt": math.sqrt,
|
||||
"floor": math.floor,
|
||||
"ceil": math.ceil,
|
||||
"clamp": lambda value, low, high: max(low, min(high, value)),
|
||||
}
|
||||
|
||||
|
||||
class ExpressionError(ValueError):
|
||||
"""Raised for any expression that cannot be evaluated to a finite number."""
|
||||
|
||||
|
||||
def evaluate(expression, variables=None, functions=None, *, zero_division="zero",
|
||||
case_insensitive=False, on_warning=None):
|
||||
"""Evaluate ``expression`` and return a finite float.
|
||||
|
||||
zero_division: "zero" makes x/0, x//0 and x%0 evaluate to 0 (reported through
|
||||
``on_warning``); "error" raises ExpressionError.
|
||||
case_insensitive: match variable names with str.casefold (keys must already be folded).
|
||||
"""
|
||||
variables = variables or {}
|
||||
functions = functions or {}
|
||||
text = str(expression).strip()
|
||||
if not text:
|
||||
raise ExpressionError("Expression is empty")
|
||||
|
||||
def warn(message):
|
||||
if on_warning is not None:
|
||||
on_warning(message)
|
||||
|
||||
def number(value, what):
|
||||
# numpy scalars and 0-d torch tensors arrive from upstream nodes on FLOAT/INT sockets.
|
||||
if getattr(value, "ndim", None) == 0 and callable(getattr(value, "item", None)):
|
||||
value = value.item()
|
||||
if not isinstance(value, numbers.Real): # bool is a Real (True + 1 == 2, as before)
|
||||
raise ExpressionError(f"{what} must be a number, got {type(value).__name__}")
|
||||
try:
|
||||
return float(value)
|
||||
except OverflowError as exc:
|
||||
raise ExpressionError(f"{what} is too large") from exc
|
||||
|
||||
def lookup(name):
|
||||
key = name.casefold() if case_insensitive else name
|
||||
if key in variables:
|
||||
return number(variables[key], f"Variable '{name}'")
|
||||
raise ExpressionError(f"Unknown variable '{name}'")
|
||||
|
||||
def visit(node):
|
||||
if isinstance(node, ast.Constant):
|
||||
return number(node.value, f"Constant {node.value!r}")
|
||||
if isinstance(node, ast.Name):
|
||||
return lookup(node.id)
|
||||
if isinstance(node, ast.BinOp):
|
||||
op = _BINARY.get(type(node.op))
|
||||
if op is None:
|
||||
raise ExpressionError(f"Operator '{type(node.op).__name__}' is not allowed")
|
||||
left, right = visit(node.left), visit(node.right)
|
||||
if isinstance(node.op, _DIVISIONS) and right == 0:
|
||||
if zero_division == "zero":
|
||||
warn("division by zero, using 0")
|
||||
return 0.0
|
||||
raise ExpressionError("Division by zero")
|
||||
return float(op(left, right))
|
||||
if isinstance(node, ast.UnaryOp):
|
||||
op = _UNARY.get(type(node.op))
|
||||
if op is None:
|
||||
raise ExpressionError(f"Operator '{type(node.op).__name__}' is not allowed")
|
||||
return op(visit(node.operand))
|
||||
if isinstance(node, ast.Call):
|
||||
name = node.func.id if isinstance(node.func, ast.Name) else None
|
||||
if name not in functions or node.keywords:
|
||||
raise ExpressionError(f"Function '{name}' is not allowed")
|
||||
return number(functions[name](*[visit(arg) for arg in node.args]), f"{name}()")
|
||||
raise ExpressionError(f"'{type(node).__name__}' is not allowed in an expression")
|
||||
|
||||
try:
|
||||
result = visit(ast.parse(text, mode="eval").body)
|
||||
except ExpressionError:
|
||||
raise
|
||||
except SyntaxError as exc:
|
||||
raise ExpressionError(f"Invalid expression: {text}") from exc
|
||||
except OverflowError as exc:
|
||||
raise ExpressionError(f"Result is too large: {text}") from exc
|
||||
except (ValueError, TypeError, ZeroDivisionError) as exc:
|
||||
# math domain errors, wrong argument counts, 0 ** -1 ...
|
||||
raise ExpressionError(f"Cannot evaluate {text}: {exc}") from exc
|
||||
except (RecursionError, MemoryError) as exc:
|
||||
# The parser raises MemoryError ("parser stack overflowed") for absurdly nested input.
|
||||
raise ExpressionError("Expression is nested too deeply") from exc
|
||||
|
||||
if not math.isfinite(result):
|
||||
raise ExpressionError(f"Result is not a finite number: {text}")
|
||||
return result
|
||||
@@ -0,0 +1,626 @@
|
||||
"""SAM 3 for the NH vision nodes: finding the model, loading it, and segmenting concepts in two passes.
|
||||
|
||||
NH-Nodes does not ship SAM 3. It uses Meta's ``sam3`` package, installed separately (see ``INSTALL_HINT``), and the
|
||||
``sam3.pt`` checkpoint, which Hugging Face hands out only after Meta's license is accepted, so it is never
|
||||
downloaded automatically. Checkpoints are looked up in the ``sam3`` model folder (``models/sam3`` by default, more
|
||||
locations in ``extra_model_paths.yaml``); other SAM 3 packs use the same folder, so one copy serves all of them.
|
||||
|
||||
Pass 1 looks at the whole image once: the image is resized to the model's square input, encoded once and asked for
|
||||
every concept. Each instance's mask is sampled back to full resolution inside its own box only.
|
||||
Pass 2 (``detail_level="balanced"``) cuts each instance out of the full-resolution image with a margin and segments it
|
||||
again with SAM 3's box-prompt decoder, the pass-1 mask given as a hint. Objects that are small or medium in the frame
|
||||
then get their edges from the original pixels instead of a ~1000 px view of the whole frame. SAM 3 draws masks on a
|
||||
grid of about 288 cells across whatever it looks at, so a subject that fills most of an 8K frame still has edges at
|
||||
about 1/300 of its size; hair-level edges need matting. A close-up that does not show the pass-1 instance is dropped
|
||||
and the pass-1 mask kept.
|
||||
|
||||
The official sam3 code builds some of its lookup tables on the GPU, so SAM 3 runs on CUDA only.
|
||||
"""
|
||||
|
||||
import contextlib
|
||||
import importlib
|
||||
import importlib.util
|
||||
import inspect
|
||||
import itertools
|
||||
import math
|
||||
import os
|
||||
|
||||
import cv2
|
||||
import numpy as np
|
||||
import torch
|
||||
import torch.nn.functional as F
|
||||
|
||||
from .model_files import make_room_on_gpu, models_dir, register_model_folder
|
||||
from .segs import SegsFrame, make_segment
|
||||
|
||||
try:
|
||||
import folder_paths
|
||||
except Exception: # running outside ComfyUI (tests)
|
||||
folder_paths = None
|
||||
|
||||
try:
|
||||
import comfy.model_management as _mm
|
||||
except Exception:
|
||||
_mm = None
|
||||
|
||||
FOLDER_NAME = "sam3" # shared with the other SAM 3 packs
|
||||
# Listed in ComfyUI for the folder: SAM 3 checkpoints in the formats SAM 3 packs use, not Hugging Face cache files.
|
||||
FOLDER_EXTENSIONS = (".pt", ".pth", ".safetensors")
|
||||
CHECKPOINT_EXTENSIONS = (".pt", ".pth") # what sam3's own loader reads (torch.load)
|
||||
DEFAULT_CHECKPOINT = "sam3.pt"
|
||||
DETAIL_LEVELS = ("balanced", "fast")
|
||||
|
||||
SAM3_COMMIT = "b26a5f330e05d321afb39d01d3d4881f258f65ff" # the sam3 build NH-Nodes is tested with
|
||||
INSTALL_HINT = (
|
||||
"SAM 3 needs Meta's sam3 package, which NH-Nodes does not install for you. In ComfyUI's Python environment run:\n"
|
||||
f" pip install --no-deps git+https://github.com/facebookresearch/sam3@{SAM3_COMMIT}\n"
|
||||
" pip install timm ftfy regex iopath\n"
|
||||
"--no-deps keeps your numpy: sam3 asks for numpy<2, which ComfyUI does not need. "
|
||||
"Restart ComfyUI afterwards."
|
||||
)
|
||||
CHECKPOINT_HINT = (
|
||||
"Ask for access at https://huggingface.co/facebook/sam3 (Meta has you accept the SAM License first), "
|
||||
"download sam3.pt from that page and put it in {folder}. Then refresh ComfyUI (press R) and pick it."
|
||||
)
|
||||
|
||||
DEDUP_IOU = 0.9 # a second instance of the same concept overlapping a better one this much is a duplicate
|
||||
# A close-up replaces the pass-1 mask when it shows the same object: at least REFINE_MIN_INSIDE of it lies within the
|
||||
# pass-1 blur of the pass-1 mask, and it covers at least REFINE_MIN_CORE of the pass-1 core (see close_up_agrees).
|
||||
REFINE_MIN_INSIDE = 0.8
|
||||
REFINE_MIN_CORE = 0.6
|
||||
_PASS1_GRID = 288 # SAM 3's detector draws masks on a 288 x 288 grid of its square view of the whole image
|
||||
_AGREE_PIXELS = 1 << 21 # masks larger than this are compared at a reduced scale
|
||||
_WIDE_ASPECT = 4.0 # beyond this aspect ratio SAM 3's square view squeezes the picture enough to miss small things
|
||||
_CROP_MARGIN = 0.2 # pass-2 crop margin on each side, as a fraction of the instance's longer side
|
||||
_CROP_MIN_MARGIN = 16 # px
|
||||
_CROP_MAX_ASPECT = 2.0 # pass-2 crops are widened to at most 2:1, the model squeezes them into a square
|
||||
_SAMPLE_CHUNK = 1 << 22 # output pixels per grid_sample call when resampling masks
|
||||
_WORKING_BYTES = 3 << 30 # VRAM for activations and the full-resolution image, on top of the weights
|
||||
|
||||
_API = None
|
||||
_CACHE = {}
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Checkpoints
|
||||
# ---------------------------------------------------------------------------
|
||||
def default_folder():
|
||||
"""<models dir>/sam3, the folder ComfyUI and the other SAM 3 packs use."""
|
||||
return os.path.join(models_dir(), FOLDER_NAME)
|
||||
|
||||
|
||||
def checkpoint_folders():
|
||||
"""Every folder searched for checkpoints, best first (registers the folder name with ComfyUI)."""
|
||||
return register_model_folder(FOLDER_NAME, default_folder(), FOLDER_EXTENSIONS)
|
||||
|
||||
|
||||
def checkpoint_choices():
|
||||
"""Checkpoint names for the node's list: .pt/.pth files in the sam3 folders, or the default name if none yet."""
|
||||
names = set()
|
||||
folders = checkpoint_folders()
|
||||
if folder_paths is not None:
|
||||
try:
|
||||
names.update(folder_paths.get_filename_list(FOLDER_NAME))
|
||||
except Exception as exc:
|
||||
print(f"[NH-Nodes] Could not list the {FOLDER_NAME} model folder: {exc}")
|
||||
else:
|
||||
for folder in folders:
|
||||
if os.path.isdir(folder):
|
||||
names.update(os.listdir(folder))
|
||||
names = sorted(name for name in names if name.lower().endswith(CHECKPOINT_EXTENSIONS))
|
||||
return names or [DEFAULT_CHECKPOINT]
|
||||
|
||||
|
||||
def resolve_checkpoint(name):
|
||||
"""Full path of checkpoint ``name`` in the sam3 folders; a RuntimeError says where to get it when it is missing."""
|
||||
name = str(name or DEFAULT_CHECKPOINT)
|
||||
parts = name.replace("\\", "/").split("/")
|
||||
if os.path.isabs(name) or ".." in parts:
|
||||
raise RuntimeError(f"[NH-Nodes] SAM 3 checkpoint name must be a file in the {FOLDER_NAME} folder: {name!r}")
|
||||
if not name.lower().endswith(CHECKPOINT_EXTENSIONS):
|
||||
raise RuntimeError(
|
||||
f"[NH-Nodes] {name} is not a SAM 3 .pt checkpoint. The safetensors export on Hugging Face is for the "
|
||||
"transformers library and uses other weight names; download sam3.pt instead."
|
||||
)
|
||||
folders = checkpoint_folders()
|
||||
path = folder_paths.get_full_path(FOLDER_NAME, name) if folder_paths is not None else None
|
||||
if path is None:
|
||||
path = next((os.path.join(folder, *parts) for folder in folders
|
||||
if os.path.isfile(os.path.join(folder, *parts))), None)
|
||||
if path is None or not os.path.isfile(path):
|
||||
raise RuntimeError(
|
||||
f"[NH-Nodes] SAM 3 checkpoint {name} not found in: {', '.join(folders)}.\n"
|
||||
+ CHECKPOINT_HINT.format(folder=folders[0] if folders else default_folder())
|
||||
)
|
||||
return path
|
||||
|
||||
|
||||
# Make the folder name known to ComfyUI as soon as the pack loads.
|
||||
checkpoint_folders()
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# The sam3 package and the model
|
||||
# ---------------------------------------------------------------------------
|
||||
def _tf32_flags():
|
||||
"""(matmul, cudnn) TF32 switches, or None when torch refuses to report them (its newer precision API in use)."""
|
||||
try:
|
||||
return torch.backends.cuda.matmul.allow_tf32, torch.backends.cudnn.allow_tf32
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
|
||||
def _restore_tf32(flags):
|
||||
if flags is not None:
|
||||
try:
|
||||
torch.backends.cuda.matmul.allow_tf32, torch.backends.cudnn.allow_tf32 = flags
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def _missing_parameters(func, names):
|
||||
"""Names from ``names`` that ``func`` does not accept (none when its signature cannot be read or takes **kwargs)."""
|
||||
try:
|
||||
parameters = inspect.signature(func).parameters
|
||||
except (TypeError, ValueError):
|
||||
return []
|
||||
if any(p.kind == p.VAR_KEYWORD for p in parameters.values()):
|
||||
return []
|
||||
return [name for name in names if name not in parameters]
|
||||
|
||||
|
||||
def ensure_sam3():
|
||||
"""(build_sam3_image_model, Sam3Processor) from the installed sam3 package.
|
||||
|
||||
A RuntimeError explains what to do when the package is missing, fails to import or lacks the image API
|
||||
NH-Nodes uses. Importing sam3's model builder switches TF32 on for the whole process; the previous setting is
|
||||
put back, so ComfyUI's own models keep theirs.
|
||||
"""
|
||||
global _API
|
||||
if _API is not None:
|
||||
return _API
|
||||
if importlib.util.find_spec("sam3") is None:
|
||||
raise RuntimeError("[NH-Nodes] " + INSTALL_HINT)
|
||||
flags = _tf32_flags()
|
||||
try:
|
||||
builder = importlib.import_module("sam3.model_builder")
|
||||
processors = importlib.import_module("sam3.model.sam3_image_processor")
|
||||
except Exception as exc:
|
||||
raise RuntimeError(f"[NH-Nodes] The sam3 package is installed but failed to load: {exc}\n{INSTALL_HINT}") from exc
|
||||
finally:
|
||||
_restore_tf32(flags)
|
||||
|
||||
build = getattr(builder, "build_sam3_image_model", None)
|
||||
processor_cls = getattr(processors, "Sam3Processor", None)
|
||||
missing = []
|
||||
if not callable(build):
|
||||
missing.append("build_sam3_image_model")
|
||||
else:
|
||||
missing += [f"build_sam3_image_model({name}=)" for name in _missing_parameters(
|
||||
build, ("checkpoint_path", "load_from_HF", "enable_inst_interactivity", "device", "eval_mode"))]
|
||||
if processor_cls is None:
|
||||
missing.append("Sam3Processor")
|
||||
else:
|
||||
missing += [f"Sam3Processor({name}=)" for name in
|
||||
_missing_parameters(processor_cls, ("device", "confidence_threshold"))]
|
||||
missing += [f"Sam3Processor.{name}" for name in ("set_image", "set_text_prompt", "reset_all_prompts")
|
||||
if not callable(getattr(processor_cls, name, None))]
|
||||
if missing:
|
||||
raise RuntimeError(
|
||||
f"[NH-Nodes] The installed sam3 package lacks {', '.join(missing)}; it is older or newer than the "
|
||||
f"versions NH-Nodes supports. Reinstall it:\n{INSTALL_HINT}"
|
||||
)
|
||||
_API = (build, processor_cls)
|
||||
return _API
|
||||
|
||||
|
||||
def sam3_device():
|
||||
"""ComfyUI's torch device, which must be a CUDA GPU for SAM 3."""
|
||||
if _mm is not None:
|
||||
device = _mm.get_torch_device()
|
||||
else:
|
||||
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
||||
if device.type != "cuda":
|
||||
raise RuntimeError(
|
||||
f"[NH-Nodes] SAM 3 needs an NVIDIA GPU with CUDA (ComfyUI runs on {device}): the official sam3 code "
|
||||
"builds part of the model on the GPU."
|
||||
)
|
||||
return device
|
||||
|
||||
|
||||
def check_checkpoint(path):
|
||||
"""Fail early, with the download advice, when ``path`` is not a readable SAM 3 checkpoint.
|
||||
|
||||
sam3 loads weights non-strictly and only prints the names it misses, so another model's .pt in the shared folder
|
||||
would run with random weights, and a truncated download fails deep inside torch. Reading the names is quick:
|
||||
the file is memory-mapped, not loaded.
|
||||
"""
|
||||
advice = CHECKPOINT_HINT.format(folder=os.path.dirname(path))
|
||||
try:
|
||||
state = torch.load(path, map_location="cpu", weights_only=True, mmap=True)
|
||||
except Exception as exc:
|
||||
raise RuntimeError(f"[NH-Nodes] {path} cannot be read as a SAM 3 checkpoint ({exc}); it may be an "
|
||||
f"incomplete download.\n{advice}") from exc
|
||||
if isinstance(state, dict) and isinstance(state.get("model"), dict):
|
||||
state = state["model"]
|
||||
names = list(state.keys()) if isinstance(state, dict) else []
|
||||
del state
|
||||
if not any(name.startswith("detector.") for name in names) or not any(name.startswith("tracker.") for name in names):
|
||||
raise RuntimeError(f"[NH-Nodes] {path} is not a SAM 3 checkpoint (it has no SAM 3 detector and tracker "
|
||||
f"weights).\n{advice}")
|
||||
|
||||
|
||||
def load_model(checkpoint):
|
||||
"""The SAM 3 image model for ``checkpoint`` (a name in the sam3 folders); kept in RAM between runs."""
|
||||
path = resolve_checkpoint(checkpoint)
|
||||
stamp = (os.path.normcase(os.path.abspath(path)), os.path.getmtime(path), os.path.getsize(path))
|
||||
if _CACHE.get("model") is not None and _CACHE.get("stamp") == stamp:
|
||||
return _CACHE["model"]
|
||||
unload()
|
||||
build, _ = ensure_sam3()
|
||||
device = sam3_device()
|
||||
check_checkpoint(path)
|
||||
print(f"[NH-Nodes] Loading SAM 3 from {path}")
|
||||
with torch.cuda.device(device): # the tables sam3 builds on "cuda" go to ComfyUI's GPU
|
||||
model = build(checkpoint_path=path, load_from_HF=False, enable_inst_interactivity=True,
|
||||
device="cpu", eval_mode=True)
|
||||
if not callable(getattr(model, "predict_inst", None)) or getattr(model, "inst_interactive_predictor", None) is None:
|
||||
raise RuntimeError("[NH-Nodes] This sam3 build has no box-prompt decoder (predict_inst). " + INSTALL_HINT)
|
||||
transforms = getattr(model.inst_interactive_predictor, "_transforms", None)
|
||||
if getattr(transforms, "max_hole_area", 0):
|
||||
# sam3 fills holes of close-up masks by default; keep real ones (between an arm and the body), as pass 1 does
|
||||
transforms.max_hole_area = 0.0
|
||||
model.requires_grad_(False)
|
||||
_CACHE.update(model=model, stamp=stamp)
|
||||
return model
|
||||
|
||||
|
||||
def _model_device(model):
|
||||
return next(model.parameters()).device
|
||||
|
||||
|
||||
def _is_on(current, wanted):
|
||||
"""True when ``current`` satisfies ``wanted``; an index-less "cuda" accepts any cuda:N."""
|
||||
return current.type == wanted.type and (wanted.index is None or current.index == wanted.index)
|
||||
|
||||
|
||||
_TABLE_ATTRIBUTES = ("freqs_cis", "freqs_cis_real", "freqs_cis_imag") # sam3's rotary tables, plain attributes
|
||||
|
||||
|
||||
def _all_on(model, device):
|
||||
return all(_is_on(p.device, device) for p in model.parameters())
|
||||
|
||||
|
||||
def _move_tables(model, device):
|
||||
"""Move sam3's rotary tables, which model.to() leaves behind because they are not buffers."""
|
||||
for module in model.modules():
|
||||
for name in _TABLE_ATTRIBUTES:
|
||||
value = getattr(module, name, None)
|
||||
if isinstance(value, torch.Tensor) and not _is_on(value.device, device):
|
||||
setattr(module, name, value.to(device))
|
||||
|
||||
|
||||
def move_model(model, device):
|
||||
"""Put the weights on ``device``, asking ComfyUI to free VRAM first; back to the CPU if the move fails."""
|
||||
if device.type == "cuda":
|
||||
weights = 0 if _all_on(model, device) else sum(
|
||||
t.numel() * t.element_size() for t in itertools.chain(model.parameters(), model.buffers()))
|
||||
make_room_on_gpu(weights + _WORKING_BYTES)
|
||||
try:
|
||||
model.to(device)
|
||||
_move_tables(model, device)
|
||||
except Exception:
|
||||
offload(model) # a half-moved model would fail every later run
|
||||
raise
|
||||
|
||||
|
||||
def offload(model):
|
||||
"""Move the weights and sam3's GPU-side tables to the CPU, so nothing holds VRAM between runs.
|
||||
|
||||
The position-encoding caches sam3 fills at build time (about 200 MB on the GPU) are emptied; sam3 rebuilds them
|
||||
on the GPU when it needs them again.
|
||||
"""
|
||||
cpu = torch.device("cpu")
|
||||
if not _all_on(model, cpu):
|
||||
model.to(cpu)
|
||||
_move_tables(model, cpu)
|
||||
for module in model.modules():
|
||||
if type(module).__name__ == "PositionEmbeddingSine" and isinstance(getattr(module, "cache", None), dict):
|
||||
module.cache.clear()
|
||||
if torch.cuda.is_available():
|
||||
torch.cuda.empty_cache()
|
||||
|
||||
|
||||
def unload():
|
||||
"""Forget the cached model (another checkpoint was chosen)."""
|
||||
model = _CACHE.pop("model", None)
|
||||
_CACHE.pop("stamp", None)
|
||||
if model is not None:
|
||||
offload(model)
|
||||
|
||||
|
||||
def _native_bf16():
|
||||
try:
|
||||
return torch.cuda.is_bf16_supported(including_emulation=False)
|
||||
except TypeError: # torch without the including_emulation switch
|
||||
return torch.cuda.is_bf16_supported()
|
||||
|
||||
|
||||
def _autocast(device):
|
||||
"""bfloat16 autocast on GPUs with native bfloat16 (Ampere and newer), as Meta runs SAM 3; full precision elsewhere."""
|
||||
if device.type == "cuda" and _native_bf16():
|
||||
return torch.autocast("cuda", dtype=torch.bfloat16)
|
||||
return contextlib.nullcontext()
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Geometry helpers (model-free, unit tested)
|
||||
# ---------------------------------------------------------------------------
|
||||
def to_logits(masks):
|
||||
"""sam3 returns its masks after a sigmoid (under the key "masks_logits"); turn them back into logits.
|
||||
|
||||
Values outside [0, 1] are taken as logits already, in case a later sam3 returns them as the key says.
|
||||
"""
|
||||
masks = masks.float()
|
||||
if masks.numel() and (float(masks.min()) < 0.0 or float(masks.max()) > 1.0):
|
||||
return masks
|
||||
masks = masks.clamp(1e-6, 1.0 - 1e-6)
|
||||
return torch.log(masks) - torch.log1p(-masks)
|
||||
|
||||
|
||||
def sample_region(logits, image_size, region, out_size):
|
||||
"""Resample the square model-space map ``logits`` [S, S] over ``region`` of the image, bilinearly.
|
||||
|
||||
``region`` is (x1, y1, x2, y2) in pixels of an image of ``image_size`` (height, width), which the model saw
|
||||
squeezed into S x S; the result is [h, w] for ``out_size`` (h, w), each value taken at the centre of its output
|
||||
pixel. Runs in row chunks, so a full 8K region needs a few hundred MB at most.
|
||||
"""
|
||||
height, width = image_size
|
||||
size = logits.shape[-1]
|
||||
x1, y1, x2, y2 = region
|
||||
out_h, out_w = int(out_size[0]), int(out_size[1])
|
||||
device = logits.device
|
||||
xs = x1 + (torch.arange(out_w, device=device, dtype=torch.float32) + 0.5) * ((x2 - x1) / out_w)
|
||||
ys = y1 + (torch.arange(out_h, device=device, dtype=torch.float32) + 0.5) * ((y2 - y1) / out_h)
|
||||
# model-map pixel index of each centre, as grid_sample coordinates (align_corners=True: -1/+1 = first/last centre)
|
||||
grid_x = (xs * (size / width) - 0.5) * (2.0 / max(1, size - 1)) - 1.0
|
||||
grid_y = (ys * (size / height) - 0.5) * (2.0 / max(1, size - 1)) - 1.0
|
||||
source = logits.float()[None, None]
|
||||
result = torch.empty((out_h, out_w), device=device, dtype=torch.float32)
|
||||
rows = max(1, _SAMPLE_CHUNK // max(1, out_w))
|
||||
for start in range(0, out_h, rows):
|
||||
gy = grid_y[start:start + rows]
|
||||
grid = torch.stack(torch.broadcast_tensors(grid_x[None, :], gy[:, None]), dim=-1)[None]
|
||||
result[start:start + rows] = F.grid_sample(source, grid, mode="bilinear", padding_mode="border",
|
||||
align_corners=True)[0, 0]
|
||||
return result
|
||||
|
||||
|
||||
def mask_box(mask):
|
||||
"""Tight (x1, y1, x2, y2) box of a bool [H, W] tensor with exclusive ends; None when empty."""
|
||||
rows = torch.nonzero(mask.any(dim=1)).flatten()
|
||||
if rows.numel() == 0:
|
||||
return None
|
||||
cols = torch.nonzero(mask.any(dim=0)).flatten()
|
||||
return int(cols[0]), int(rows[0]), int(cols[-1]) + 1, int(rows[-1]) + 1
|
||||
|
||||
|
||||
def model_box_to_image(box, size, image_size, margin=1):
|
||||
"""An S x S model-space box as an image-space region, ``margin`` model pixels wider on each side, in the image."""
|
||||
height, width = image_size
|
||||
sx, sy = width / size, height / size
|
||||
x1, y1, x2, y2 = box
|
||||
return (max(0, int((x1 - margin) * sx)), max(0, int((y1 - margin) * sy)),
|
||||
min(width, int(math.ceil((x2 + margin) * sx))), min(height, int(math.ceil((y2 + margin) * sy))))
|
||||
|
||||
|
||||
def _fit_span(center, size, limit):
|
||||
size = int(round(min(size, limit)))
|
||||
start = int(round(center - size / 2.0))
|
||||
start = min(max(0, start), limit - size)
|
||||
return start, start + size
|
||||
|
||||
|
||||
def crop_region(box, image_size):
|
||||
"""Pass-2 crop around an image-space box: a margin on each side, widened to at most 2:1, kept inside the image."""
|
||||
height, width = image_size
|
||||
x1, y1, x2, y2 = box
|
||||
margin = max(_CROP_MIN_MARGIN, _CROP_MARGIN * max(x2 - x1, y2 - y1))
|
||||
crop_w, crop_h = (x2 - x1) + 2 * margin, (y2 - y1) + 2 * margin
|
||||
crop_w, crop_h = max(crop_w, crop_h / _CROP_MAX_ASPECT), max(crop_h, crop_w / _CROP_MAX_ASPECT)
|
||||
left, right = _fit_span((x1 + x2) / 2.0, crop_w, width)
|
||||
top, bottom = _fit_span((y1 + y2) / 2.0, crop_h, height)
|
||||
# the box itself always stays inside, whatever the rounding did
|
||||
return min(left, x1), min(top, y1), max(right, x2), max(bottom, y2)
|
||||
|
||||
|
||||
def drop_stray_parts(refined, coarse):
|
||||
"""``refined`` without the connected parts that do not touch ``coarse`` at all (specks around the object)."""
|
||||
count, labels = cv2.connectedComponents(refined.astype(np.uint8), connectivity=8)
|
||||
if count <= 2:
|
||||
return refined
|
||||
touching = np.bincount(labels[coarse], minlength=count) > 0
|
||||
touching[0] = False
|
||||
return touching[labels]
|
||||
|
||||
|
||||
def close_up_agrees(refined, coarse, tolerance):
|
||||
"""True when the close-up mask ``refined`` shows the same object as the pass-1 mask ``coarse`` (bool arrays).
|
||||
|
||||
The pass-1 mask is blurred by up to ``tolerance`` px, so plain IoU would turn down a sharp mask of a thin pole or
|
||||
cable. Instead most of the close-up must lie within ``tolerance`` of the pass-1 mask, and it must cover most of
|
||||
the pass-1 core (pixels deeper than twice ``tolerance`` inside it); thin objects have no core and need only the
|
||||
first. Large masks are compared at a reduced scale.
|
||||
"""
|
||||
if not refined.any() or not coarse.any():
|
||||
return False
|
||||
step = max(1, int(math.sqrt(refined.size / _AGREE_PIXELS)))
|
||||
if step > 1 and not refined[::step, ::step].any():
|
||||
step = 1 # something thinner than the step: compare at full scale
|
||||
refined, coarse = refined[::step, ::step], coarse[::step, ::step]
|
||||
tolerance = max(1.0, tolerance / step)
|
||||
to_coarse = cv2.distanceTransform((~coarse).astype(np.uint8), cv2.DIST_L2, 3)
|
||||
inside = np.count_nonzero(refined & (to_coarse <= tolerance)) / np.count_nonzero(refined)
|
||||
core = cv2.distanceTransform(coarse.astype(np.uint8), cv2.DIST_L2, 3) > 2.0 * tolerance
|
||||
covered = np.count_nonzero(refined & core) / np.count_nonzero(core) if core.any() else 1.0
|
||||
return inside >= REFINE_MIN_INSIDE and covered >= REFINE_MIN_CORE
|
||||
|
||||
|
||||
def _box_overlap(a, b):
|
||||
"""Intersection box of two (x1, y1, x2, y2) boxes, or None."""
|
||||
x1, y1, x2, y2 = max(a[0], b[0]), max(a[1], b[1]), min(a[2], b[2]), min(a[3], b[3])
|
||||
return (x1, y1, x2, y2) if x1 < x2 and y1 < y2 else None
|
||||
|
||||
|
||||
def find_instances(processor, state, concept, max_instances=0):
|
||||
"""[(score, logits [S, S], model-space box)] for one concept on an encoded image, best first, duplicates dropped."""
|
||||
processor.reset_all_prompts(state)
|
||||
output = processor.set_text_prompt(concept, state)
|
||||
masks, scores = output["masks_logits"], output["scores"]
|
||||
if masks is None or len(scores) == 0:
|
||||
return []
|
||||
scores = scores.float().flatten()
|
||||
found, kept = [], [] # kept: (bool mask, box, area) of accepted instances
|
||||
for i in torch.argsort(scores, descending=True).tolist():
|
||||
logits = to_logits(masks[i].reshape(masks.shape[-2], masks.shape[-1]))
|
||||
inside = logits > 0
|
||||
box = mask_box(inside)
|
||||
if box is None:
|
||||
continue
|
||||
area = int(inside.sum())
|
||||
duplicate = False
|
||||
for other, other_box, other_area in kept:
|
||||
overlap = _box_overlap(box, other_box)
|
||||
if overlap is None:
|
||||
continue
|
||||
ox1, oy1, ox2, oy2 = overlap
|
||||
inter = int((inside[oy1:oy2, ox1:ox2] & other[oy1:oy2, ox1:ox2]).sum())
|
||||
if inter / max(1, area + other_area - inter) >= DEDUP_IOU:
|
||||
duplicate = True
|
||||
break
|
||||
if duplicate:
|
||||
continue
|
||||
kept.append((inside, box, area))
|
||||
found.append((float(scores[i]), logits, box))
|
||||
if max_instances and len(found) >= max_instances:
|
||||
break
|
||||
return found
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Segmenting
|
||||
# ---------------------------------------------------------------------------
|
||||
def _hint_size(model):
|
||||
"""Side of the low-resolution mask the box-prompt decoder takes as a hint (288 for SAM 3)."""
|
||||
encoder = getattr(getattr(getattr(model, "inst_interactive_predictor", None), "model", None),
|
||||
"sam_prompt_encoder", None)
|
||||
size = getattr(encoder, "mask_input_size", (288, 288))
|
||||
return int(size[0]), int(size[1])
|
||||
|
||||
|
||||
def _upsample(concept, score, logits, box, image_size):
|
||||
"""Pass 1 only: the instance's mask at full resolution, inside its own region."""
|
||||
region = model_box_to_image(box, logits.shape[-1], image_size)
|
||||
x1, y1, x2, y2 = region
|
||||
mask = sample_region(logits, image_size, region, (y2 - y1, x2 - x1)) > 0
|
||||
return make_segment(concept, score, mask, origin=(x1, y1)), False
|
||||
|
||||
|
||||
def _refine(model, processor, image, concept, score, logits, box, image_size, hint_size):
|
||||
"""Pass 2: segment the instance again on a full-resolution crop, the pass-1 mask as a hint.
|
||||
|
||||
Returns (segment, refined) where ``refined`` is False when the pass-1 mask was kept.
|
||||
"""
|
||||
target = model_box_to_image(box, logits.shape[-1], image_size)
|
||||
cx1, cy1, cx2, cy2 = crop_region(target, image_size)
|
||||
coarse = (sample_region(logits, image_size, (cx1, cy1, cx2, cy2), (cy2 - cy1, cx2 - cx1)) > 0).cpu().numpy()
|
||||
hint = sample_region(logits, image_size, (cx1, cy1, cx2, cy2), hint_size).clamp(-20.0, 20.0)
|
||||
state = processor.set_image(image[:, cy1:cy2, cx1:cx2])
|
||||
x1, y1, x2, y2 = target
|
||||
prompt_box = np.array([x1 - cx1, y1 - cy1, x2 - cx1, y2 - cy1], dtype=np.float32)
|
||||
masks, _, _ = model.predict_inst(state, box=prompt_box, mask_input=hint[None], multimask_output=False,
|
||||
return_logits=True)
|
||||
refined = np.asarray(masks).reshape(-1, cy2 - cy1, cx2 - cx1)[0] > 0
|
||||
refined = drop_stray_parts(refined, coarse)
|
||||
accepted = close_up_agrees(refined, coarse, max(image_size) / _PASS1_GRID + 2.0)
|
||||
return make_segment(concept, score, refined if accepted else coarse, origin=(cx1, cy1)), accepted
|
||||
|
||||
|
||||
def _image_chw(image):
|
||||
"""One ComfyUI image [H, W, C] -> float [3, H, W] in [0, 1]."""
|
||||
channels = image.shape[-1]
|
||||
if channels == 1:
|
||||
image = image.expand(-1, -1, 3)
|
||||
elif channels not in (3, 4):
|
||||
raise ValueError(f"[NH-Nodes] SAM 3 expects an IMAGE with 1, 3 or 4 channels, got {channels}")
|
||||
return image[..., :3].permute(2, 0, 1).float().clamp(0.0, 1.0)
|
||||
|
||||
|
||||
def segment_images(model, make_processor, images, concepts, threshold=0.5, detail_level="balanced",
|
||||
max_per_concept=0, device=None, interrupt=None, progress=None):
|
||||
"""NH_SEGS (one SegsFrame per image) for ``concepts`` found in the ComfyUI IMAGE batch ``images``.
|
||||
|
||||
``make_processor(threshold)`` returns a Sam3Processor for ``model`` on ``device``; the model must already be
|
||||
there. Instances come in concept order, best score first within a concept. ``interrupt()`` runs before each
|
||||
concept and each close-up (it raises to stop), ``progress(done, total)`` after each concept of each image.
|
||||
"""
|
||||
device = device or _model_device(model)
|
||||
refine = detail_level != "fast"
|
||||
hint_size = _hint_size(model) if refine else None
|
||||
frames, kept_pass1, warned_wide = [], 0, False
|
||||
done, total = 0, len(images) * len(concepts)
|
||||
with torch.inference_mode(), _autocast(device):
|
||||
for image in images:
|
||||
height, width = int(image.shape[0]), int(image.shape[1])
|
||||
if not warned_wide and max(height, width) > _WIDE_ASPECT * min(height, width):
|
||||
warned_wide = True
|
||||
print(f"[NH-Nodes] SAM 3 sees the whole {width}x{height} picture squeezed into a square, so on images "
|
||||
"wider than 4:1 it can miss small things; split such a picture into tiles for better results")
|
||||
pixels = _image_chw(image).to(device)
|
||||
processor = make_processor(threshold)
|
||||
size = int(getattr(processor, "resolution", 1008))
|
||||
square = F.interpolate(pixels[None], size=(size, size), mode="bilinear", antialias=True,
|
||||
align_corners=False)[0].clamp_(0.0, 1.0)
|
||||
state = processor.set_image(square)
|
||||
segments = []
|
||||
for concept in concepts:
|
||||
if interrupt is not None:
|
||||
interrupt()
|
||||
for score, logits, box in find_instances(processor, state, concept, max_per_concept):
|
||||
if refine:
|
||||
if interrupt is not None:
|
||||
interrupt()
|
||||
seg, refined = _refine(model, processor, pixels, concept, score, logits, box,
|
||||
(height, width), hint_size)
|
||||
kept_pass1 += 0 if refined else 1
|
||||
else:
|
||||
seg, _ = _upsample(concept, score, logits, box, (height, width))
|
||||
if seg is not None:
|
||||
segments.append(seg)
|
||||
done += 1
|
||||
if progress is not None:
|
||||
progress(done, total)
|
||||
frames.append(SegsFrame(height, width, tuple(segments)))
|
||||
del pixels, square, state
|
||||
if kept_pass1:
|
||||
print(f"[NH-Nodes] SAM 3: kept the first-pass mask for {kept_pass1} instance(s) whose close-up disagreed")
|
||||
return tuple(frames)
|
||||
|
||||
|
||||
def segment(images, concepts, threshold=0.5, detail_level="balanced", max_per_concept=0, checkpoint=DEFAULT_CHECKPOINT,
|
||||
keep_model_loaded=False, interrupt=None, progress=None):
|
||||
"""Load SAM 3 and run ``segment_images`` on ComfyUI's GPU; the weights go back to RAM afterwards unless kept."""
|
||||
if images.shape[0] == 0:
|
||||
raise ValueError("[NH-Nodes] SAM 3 got an empty image batch")
|
||||
model = load_model(checkpoint)
|
||||
_, processor_cls = ensure_sam3()
|
||||
device = sam3_device()
|
||||
try:
|
||||
move_model(model, device)
|
||||
return segment_images(
|
||||
model, lambda value: processor_cls(model, device=device, confidence_threshold=value), images, concepts,
|
||||
threshold, detail_level, max_per_concept, device, interrupt, progress,
|
||||
)
|
||||
finally:
|
||||
if not keep_model_loaded:
|
||||
offload(model)
|
||||
@@ -3,6 +3,14 @@
|
||||
Handles model discovery (reuse dirs already on disk before downloading),
|
||||
per-model caching with CPU offload between runs, and full-resolution label
|
||||
map prediction.
|
||||
|
||||
The model-folder, download and VRAM helpers it shares with the VTON node live
|
||||
in model_files.py, so both model families follow the same rules. SegFormer
|
||||
models are registered with ComfyUI's ``folder_paths`` under the name
|
||||
``nh_nodes``; users can add more locations with that name in
|
||||
``extra_model_paths.yaml`` and every registered location is searched before
|
||||
anything is downloaded. Downloads always go to the default folder under the
|
||||
models dir, pinned to a fixed Hugging Face revision.
|
||||
"""
|
||||
|
||||
import os
|
||||
@@ -12,35 +20,34 @@ import numpy as np
|
||||
import torch
|
||||
import torch.nn.functional as F
|
||||
|
||||
try:
|
||||
import folder_paths
|
||||
|
||||
_MODELS_DIR = folder_paths.models_dir
|
||||
except Exception: # running outside ComfyUI (tests)
|
||||
_MODELS_DIR = os.path.join(os.path.dirname(__file__), "models")
|
||||
|
||||
try:
|
||||
import comfy.model_management as _mm
|
||||
except Exception:
|
||||
_mm = None
|
||||
from .model_files import download_pinned, make_room_on_gpu, models_dir, register_model_folder, unique_dirs
|
||||
|
||||
_REQUIRED_FILES = ("config.json", "model.safetensors", "preprocessor_config.json")
|
||||
# Logits are interpolated at most to this size before argmax; the label map is
|
||||
# then nearest-resized to the input. Keeps VRAM bounded on very large inputs.
|
||||
_MAX_LOGIT_SIDE = 2048
|
||||
|
||||
# name -> HF repo, local dirs to probe before downloading (relative to models
|
||||
# dir; other packs may already hold the same weights) and a label signature
|
||||
# that guards against a different checkpoint sitting in a probed dir.
|
||||
# extra_model_paths.yaml key for the SegFormer models. Each registered location holds
|
||||
# one sub-folder per model (see MODEL_SPECS), e.g. <location>/segformer_fashion/config.json.
|
||||
FOLDER_NAME = "nh_nodes"
|
||||
FOLDER_EXTENSIONS = (".safetensors",) # what ComfyUI lists for FOLDER_NAME: the weights, not config.json or cache metadata
|
||||
_DEFAULT_FOLDER = "NH-Nodes" # under the models dir: the download target, searched first by default
|
||||
|
||||
# name -> HF repo + pinned revision, legacy local dirs to probe after the registered
|
||||
# folders (relative to the models dir; older versions of this pack and other packs may
|
||||
# already hold the same weights) and a label signature that guards against a different
|
||||
# checkpoint sitting in a probed dir.
|
||||
MODEL_SPECS = {
|
||||
"segformer_fashion": {
|
||||
"repo_id": "1038lab/segformer_fashion",
|
||||
"probe_dirs": ("NH-Nodes/segformer_fashion", "RMBG/segformer_fashion"),
|
||||
"revision": "e5bae2d8e3333be9f12eb6aae454c0542c1371d3",
|
||||
"legacy_dirs": ("RMBG/segformer_fashion",),
|
||||
"signature": {1: "shirt, blouse", 24: "shoe", 46: "tassel"},
|
||||
},
|
||||
"face_parsing": {
|
||||
"repo_id": "jonathandinu/face-parsing",
|
||||
"probe_dirs": ("NH-Nodes/face_parsing", "face_parsing", "RMBG/segformer_face"),
|
||||
"revision": "758b82e15a0178c9db39c1ff666a8b56e3a550c8",
|
||||
"legacy_dirs": ("face_parsing", "RMBG/segformer_face"),
|
||||
"signature": {1: "skin", 13: "hair", 17: "neck"},
|
||||
},
|
||||
}
|
||||
@@ -49,35 +56,54 @@ _LOADED = {}
|
||||
_warned_no_cuda = False
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Model folders and downloads (the shared helpers are in model_files.py)
|
||||
# ---------------------------------------------------------------------------
|
||||
def default_folder():
|
||||
"""Where SegFormer models are downloaded and searched first: <models dir>/NH-Nodes."""
|
||||
return os.path.join(models_dir(), _DEFAULT_FOLDER)
|
||||
|
||||
|
||||
def _has_required_files(path):
|
||||
return all(os.path.isfile(os.path.join(path, name)) for name in _REQUIRED_FILES)
|
||||
|
||||
|
||||
def model_search_dirs(model_name):
|
||||
"""Folders that may hold `model_name`, best match first.
|
||||
|
||||
The registered ``nh_nodes`` locations come first (models/NH-Nodes by default, plus whatever
|
||||
extra_model_paths.yaml adds), then the legacy dirs under the models dir.
|
||||
"""
|
||||
spec = MODEL_SPECS[model_name]
|
||||
dirs = [os.path.join(base, model_name)
|
||||
for base in register_model_folder(FOLDER_NAME, default_folder(), FOLDER_EXTENSIONS)]
|
||||
dirs += [os.path.normpath(os.path.join(models_dir(), *rel.split("/"))) for rel in spec["legacy_dirs"]]
|
||||
return unique_dirs(dirs)
|
||||
|
||||
|
||||
def resolve_model_dir(model_name, download=True):
|
||||
"""Return a local dir holding the model, downloading into models/NH-Nodes if absent."""
|
||||
spec = MODEL_SPECS[model_name]
|
||||
for rel in spec["probe_dirs"]:
|
||||
candidate = os.path.join(_MODELS_DIR, rel)
|
||||
for candidate in model_search_dirs(model_name):
|
||||
if _has_required_files(candidate):
|
||||
return candidate
|
||||
|
||||
target = os.path.join(_MODELS_DIR, spec["probe_dirs"][0])
|
||||
if not download:
|
||||
return None
|
||||
from huggingface_hub import snapshot_download
|
||||
|
||||
os.makedirs(target, exist_ok=True)
|
||||
print(f"[NH-Nodes] Downloading {spec['repo_id']} -> {target}")
|
||||
snapshot_download(
|
||||
repo_id=spec["repo_id"],
|
||||
local_dir=target,
|
||||
allow_patterns=list(_REQUIRED_FILES),
|
||||
)
|
||||
target = os.path.join(default_folder(), model_name)
|
||||
download_pinned(spec["repo_id"], spec["revision"], target, _REQUIRED_FILES)
|
||||
if not _has_required_files(target):
|
||||
raise RuntimeError(f"[NH-Nodes] Model files missing after download: {target}")
|
||||
return target
|
||||
|
||||
|
||||
# Make the folder name known to ComfyUI as soon as the pack loads.
|
||||
register_model_folder(FOLDER_NAME, default_folder(), FOLDER_EXTENSIONS)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# SegFormer inference
|
||||
# ---------------------------------------------------------------------------
|
||||
def _torch_device(device):
|
||||
global _warned_no_cuda
|
||||
if device == "cuda":
|
||||
@@ -121,14 +147,19 @@ def load_segformer(model_name, device="cuda"):
|
||||
return bundle
|
||||
|
||||
|
||||
def _is_on(model_device, wanted):
|
||||
"""True when `model_device` satisfies `wanted`; an index-less "cuda" accepts any cuda:N."""
|
||||
return model_device.type == wanted.type and (wanted.index is None or model_device.index == wanted.index)
|
||||
|
||||
|
||||
def _ensure_on_device(bundle):
|
||||
model, device = bundle["model"], bundle["device"]
|
||||
if model.device == device:
|
||||
if _is_on(model.device, device):
|
||||
return
|
||||
if device.type == "cuda" and _mm is not None:
|
||||
if device.type == "cuda":
|
||||
# Let ComfyUI evict its own models first instead of OOM-ing on ours.
|
||||
size = sum(p.numel() * p.element_size() for p in model.parameters())
|
||||
_mm.free_memory(size * 2, device)
|
||||
make_room_on_gpu(size * 2)
|
||||
model.to(device)
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,515 @@
|
||||
"""NH_SEGS: the instances the NH vision nodes pass between each other.
|
||||
|
||||
An ``NH_SEGS`` value is a tuple with one ``SegsFrame`` per image of a batch. A frame holds the image size and the
|
||||
``Segment`` instances found in that image. A segment keeps its label, score and box, and only the mask pixels inside
|
||||
its box, so dozens of instances on an 8K image stay small. Full-size masks are built only when a node outputs a MASK.
|
||||
|
||||
Plain torch/numpy, no model: the select, compose, preview and convert nodes and their tests run anywhere.
|
||||
"""
|
||||
|
||||
import fnmatch
|
||||
import functools
|
||||
from collections import namedtuple
|
||||
from dataclasses import dataclass
|
||||
|
||||
import cv2
|
||||
import numpy as np
|
||||
import torch
|
||||
from PIL import Image, ImageDraw, ImageFont
|
||||
|
||||
SEGS_TYPE = "NH_SEGS"
|
||||
|
||||
|
||||
@dataclass(frozen=True, eq=False)
|
||||
class Segment:
|
||||
"""One instance found in an image.
|
||||
|
||||
``box`` is (x1, y1, x2, y2) in image pixels with exclusive ends, so the instance covers ``image[y1:y2, x1:x2]``;
|
||||
it is the tight box around the mask. ``mask`` is a bool [y2 - y1, x2 - x1] CPU tensor over that box.
|
||||
"""
|
||||
|
||||
label: str
|
||||
score: float
|
||||
box: tuple
|
||||
mask: torch.Tensor
|
||||
source: str = "sam3"
|
||||
|
||||
@property
|
||||
def area(self):
|
||||
"""Number of mask pixels."""
|
||||
return int(self.mask.sum())
|
||||
|
||||
@property
|
||||
def center(self):
|
||||
x1, y1, x2, y2 = self.box
|
||||
return (x1 + x2) / 2.0, (y1 + y2) / 2.0
|
||||
|
||||
|
||||
@dataclass(frozen=True, eq=False)
|
||||
class SegsFrame:
|
||||
"""The instances found in one image of ``height`` x ``width`` pixels, in the order they were found."""
|
||||
|
||||
height: int
|
||||
width: int
|
||||
segments: tuple = ()
|
||||
|
||||
def with_segments(self, segments):
|
||||
return SegsFrame(self.height, self.width, tuple(segments))
|
||||
|
||||
def _comfy_cache_tensors(self):
|
||||
"""The tensors this frame holds, so ComfyUI's RAM-pressure cache can size it."""
|
||||
return [seg.mask for seg in self.segments]
|
||||
|
||||
|
||||
def make_segment(label, score, mask, origin=(0, 0), source="sam3"):
|
||||
"""Segment for the True pixels of the bool [h, w] ``mask`` whose top-left pixel sits at ``origin`` (x, y) of the image.
|
||||
|
||||
The stored box is the tight box around those pixels and the stored mask a compact CPU copy of that box. Works on
|
||||
any device, cropping before the copy to the CPU. Returns None when the mask is empty.
|
||||
"""
|
||||
mask = torch.as_tensor(mask)
|
||||
if mask.dtype != torch.bool:
|
||||
mask = mask > 0.5
|
||||
rows = torch.nonzero(mask.any(dim=1)).flatten()
|
||||
if rows.numel() == 0:
|
||||
return None
|
||||
cols = torch.nonzero(mask.any(dim=0)).flatten()
|
||||
y1, y2 = int(rows[0]), int(rows[-1]) + 1
|
||||
x1, x2 = int(cols[0]), int(cols[-1]) + 1
|
||||
crop = mask[y1:y2, x1:x2].to("cpu").clone(memory_format=torch.contiguous_format)
|
||||
ox, oy = int(origin[0]), int(origin[1])
|
||||
return Segment(str(label), float(score), (ox + x1, oy + y1, ox + x2, oy + y2), crop, source)
|
||||
|
||||
|
||||
def frame_mask(frame, segments=None):
|
||||
"""Bool [H, W] union of ``segments`` (every segment of ``frame`` by default)."""
|
||||
canvas = torch.zeros((frame.height, frame.width), dtype=torch.bool)
|
||||
for seg in frame.segments if segments is None else segments:
|
||||
x1, y1, x2, y2 = seg.box
|
||||
canvas[y1:y2, x1:x2] |= seg.mask
|
||||
return canvas
|
||||
|
||||
|
||||
def segs_union(segs):
|
||||
"""MASK [B, H, W] float: the union of every instance, one mask per image."""
|
||||
return torch.stack([frame_mask(frame) for frame in segs]).float()
|
||||
|
||||
|
||||
def check_memory(nbytes, what):
|
||||
"""Raise MemoryError with advice when ``nbytes`` would take more than half of the RAM that is free now."""
|
||||
try:
|
||||
import psutil
|
||||
|
||||
available = psutil.virtual_memory().available
|
||||
except Exception: # no psutil: nothing to compare with
|
||||
return
|
||||
if nbytes > available // 2:
|
||||
raise MemoryError(
|
||||
f"[NH-Nodes] {what} would need {nbytes / 2**30:.1f} GiB but only {available / 2**30:.1f} GiB of RAM is "
|
||||
"free. Keep fewer instances with Segs Select, or combine them into one mask with Segs Compose."
|
||||
)
|
||||
|
||||
|
||||
def instance_masks(segs):
|
||||
"""(MASK [N, H, W] float with one full-size mask per instance of every image, in order; the labels).
|
||||
|
||||
With no instance at all the MASK holds a single empty mask, as nodes downstream may not accept an empty batch.
|
||||
Each full-size mask takes H x W x 4 bytes (about 145 MB on an 8K image), so a MemoryError explains what to do
|
||||
when they would not fit in RAM.
|
||||
"""
|
||||
labels = [seg.label for frame in segs for seg in frame.segments]
|
||||
height, width = segs[0].height, segs[0].width
|
||||
check_memory(max(1, len(labels)) * height * width * 4, f"{len(labels)} full-size instance masks")
|
||||
masks = torch.zeros((max(1, len(labels)), height, width))
|
||||
index = 0
|
||||
for frame in segs:
|
||||
for seg in frame.segments:
|
||||
x1, y1, x2, y2 = seg.box
|
||||
masks[index, y1:y2, x1:x2] = seg.mask.float()
|
||||
index += 1
|
||||
return masks, labels
|
||||
|
||||
|
||||
def count_segments(segs):
|
||||
return sum(len(frame.segments) for frame in segs)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Labels
|
||||
# ---------------------------------------------------------------------------
|
||||
def normalize_label(text):
|
||||
"""Lower case, '_' as a space, single spaces: "Red_Car " and "red car" both become "red car"."""
|
||||
return " ".join(str(text).replace("_", " ").lower().split())
|
||||
|
||||
|
||||
def label_matches(label, pattern):
|
||||
"""True when ``label`` matches ``pattern``, ignoring case; the pattern may use * and ? wildcards."""
|
||||
return fnmatch.fnmatchcase(normalize_label(label), normalize_label(pattern))
|
||||
|
||||
|
||||
def parse_label_list(text):
|
||||
"""Commas and new lines separate entries: "person, red car" -> ["person", "red car"].
|
||||
|
||||
Blank entries and lines starting with '#' are dropped; a repeated entry (ignoring case and spacing) is kept once,
|
||||
with its first spelling.
|
||||
"""
|
||||
entries, seen = [], set()
|
||||
for line in str(text or "").splitlines():
|
||||
if line.strip().startswith("#"):
|
||||
continue
|
||||
for part in line.split(","):
|
||||
entry = " ".join(part.split())
|
||||
key = normalize_label(entry)
|
||||
if entry and key not in seen:
|
||||
seen.add(key)
|
||||
entries.append(entry)
|
||||
return entries
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Select
|
||||
# ---------------------------------------------------------------------------
|
||||
SORT_ORDERS = ("score", "area", "left_to_right", "right_to_left", "top_to_bottom", "bottom_to_top",
|
||||
"center_first", "as_found")
|
||||
|
||||
|
||||
def sort_segments(segments, order, frame):
|
||||
"""``segments`` in ``order`` (one of SORT_ORDERS); positions use each box's centre. Ties keep their found order."""
|
||||
segments = list(segments)
|
||||
if order == "score":
|
||||
return sorted(segments, key=lambda s: -s.score)
|
||||
if order == "area":
|
||||
return sorted(segments, key=lambda s: -s.area)
|
||||
if order == "left_to_right":
|
||||
return sorted(segments, key=lambda s: s.center[0])
|
||||
if order == "right_to_left":
|
||||
return sorted(segments, key=lambda s: -s.center[0])
|
||||
if order == "top_to_bottom":
|
||||
return sorted(segments, key=lambda s: s.center[1])
|
||||
if order == "bottom_to_top":
|
||||
return sorted(segments, key=lambda s: -s.center[1])
|
||||
if order == "center_first":
|
||||
cx, cy = frame.width / 2.0, frame.height / 2.0
|
||||
return sorted(segments, key=lambda s: (s.center[0] - cx) ** 2 + (s.center[1] - cy) ** 2)
|
||||
return segments
|
||||
|
||||
|
||||
def select_segments(frame, labels=(), min_score=0.0, min_area=0.0, max_area=1.0, order="score", start=0, count=0):
|
||||
"""Frame with the segments that pass the filters, sorted by ``order``, from position ``start`` (0 = first).
|
||||
|
||||
``labels`` are patterns (any one must match; none = every label). Areas are fractions of the image. ``count`` = 0
|
||||
keeps every segment from ``start`` on.
|
||||
"""
|
||||
total = max(1, frame.height * frame.width)
|
||||
kept = []
|
||||
for seg in frame.segments:
|
||||
if labels and not any(label_matches(seg.label, pattern) for pattern in labels):
|
||||
continue
|
||||
if seg.score < min_score:
|
||||
continue
|
||||
if not min_area <= seg.area / total <= max_area:
|
||||
continue
|
||||
kept.append(seg)
|
||||
kept = sort_segments(kept, order, frame)
|
||||
start = max(0, int(start))
|
||||
kept = kept[start:] if count <= 0 else kept[start:start + int(count)]
|
||||
return frame.with_segments(kept)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Compose: a small boolean language over labels
|
||||
# ---------------------------------------------------------------------------
|
||||
class ExpressionError(ValueError):
|
||||
"""An NH_SEGS mask expression that cannot be read."""
|
||||
|
||||
|
||||
_OPERATORS = {"+": "|", "|": "|", "-": "-", "&": "&", "~": "~", "!": "~", "(": "(", ")": ")"}
|
||||
_WORD_OPERATORS = {"or": "|", "and": "&", "not": "~"}
|
||||
_KEYWORDS = ("all", "image", "none")
|
||||
_QUOTES = "\"'"
|
||||
|
||||
|
||||
def _is_word_char(ch):
|
||||
return ch.isalnum() or ch == "_"
|
||||
|
||||
|
||||
def _tokenize(text):
|
||||
"""[(kind, value, position, written)] with kinds "op", "keyword", "label"; positions count from 1.
|
||||
|
||||
A quote opens a quoted label only where a token starts, and a '-' between two word characters is part of the
|
||||
word, so ``women's`` and ``t-shirt`` are plain labels while ``person - hair`` subtracts.
|
||||
"""
|
||||
tokens, i, n = [], 0, len(text)
|
||||
while i < n:
|
||||
ch = text[i]
|
||||
if ch.isspace():
|
||||
i += 1
|
||||
elif ch in _OPERATORS:
|
||||
tokens.append(("op", _OPERATORS[ch], i + 1, ch))
|
||||
i += 1
|
||||
elif ch in _QUOTES:
|
||||
end = text.find(ch, i + 1)
|
||||
if end < 0:
|
||||
raise ExpressionError(f"quote opened at position {i + 1} is never closed")
|
||||
tokens.append(("label", text[i + 1:end], i + 1, text[i:end + 1]))
|
||||
i = end + 1
|
||||
else:
|
||||
j = i
|
||||
while j < n and not text[j].isspace():
|
||||
hyphen_in_word = (text[j] == "-" and _is_word_char(text[j - 1])
|
||||
and j + 1 < n and _is_word_char(text[j + 1]))
|
||||
if text[j] in _OPERATORS and not hyphen_in_word:
|
||||
break
|
||||
j += 1
|
||||
word = text[i:j]
|
||||
lowered = word.lower()
|
||||
if lowered in _WORD_OPERATORS:
|
||||
tokens.append(("op", _WORD_OPERATORS[lowered], i + 1, word))
|
||||
elif lowered in _KEYWORDS:
|
||||
tokens.append(("keyword", lowered, i + 1, word))
|
||||
else:
|
||||
tokens.append(("label", word, i + 1, word))
|
||||
i = j
|
||||
return tokens
|
||||
|
||||
|
||||
class _Parser:
|
||||
"""expr := term (('|' | '-') term)* ; term := unary ('&' unary)* ; unary := '~' unary | atom ;
|
||||
atom := label | keyword | '(' expr ')'. Builds a tuple tree evaluated by ``evaluate``."""
|
||||
|
||||
def __init__(self, text):
|
||||
self.tokens = _tokenize(text)
|
||||
self.pos = 0
|
||||
|
||||
def _peek(self):
|
||||
return self.tokens[self.pos] if self.pos < len(self.tokens) else None
|
||||
|
||||
def _where(self):
|
||||
token = self._peek()
|
||||
return f"'{token[3]}' at position {token[2]}" if token else "the end"
|
||||
|
||||
def parse(self):
|
||||
if not self.tokens:
|
||||
raise ExpressionError("the expression is empty; use 'all' for every instance")
|
||||
tree = self._expr()
|
||||
if self._peek() is not None:
|
||||
raise ExpressionError(f"unexpected {self._where()}")
|
||||
return tree
|
||||
|
||||
def _expr(self):
|
||||
tree = self._term()
|
||||
while (token := self._peek()) is not None and token[0] == "op" and token[1] in ("|", "-"):
|
||||
self.pos += 1
|
||||
tree = (token[1], tree, self._term())
|
||||
return tree
|
||||
|
||||
def _term(self):
|
||||
tree = self._unary()
|
||||
while (token := self._peek()) is not None and token[0] == "op" and token[1] == "&":
|
||||
self.pos += 1
|
||||
tree = ("&", tree, self._unary())
|
||||
return tree
|
||||
|
||||
def _unary(self):
|
||||
token = self._peek()
|
||||
if token is not None and token[0] == "op" and token[1] == "~":
|
||||
self.pos += 1
|
||||
return ("~", self._unary())
|
||||
return self._atom()
|
||||
|
||||
def _atom(self):
|
||||
token = self._peek()
|
||||
if token is None:
|
||||
raise ExpressionError("the expression ends where a label, 'all', 'image', 'none' or '(' was expected")
|
||||
kind, value, position, written = token
|
||||
self.pos += 1
|
||||
if kind == "label":
|
||||
return ("label", value)
|
||||
if kind == "keyword":
|
||||
return ("keyword", value)
|
||||
if value == "(":
|
||||
tree = self._expr()
|
||||
closing = self._peek()
|
||||
if closing is None or closing[:2] != ("op", ")"):
|
||||
raise ExpressionError(f"'(' at position {position} is not closed (found {self._where()})")
|
||||
self.pos += 1
|
||||
return tree
|
||||
raise ExpressionError(f"unexpected '{written}' at position {position}")
|
||||
|
||||
|
||||
@functools.lru_cache(maxsize=64)
|
||||
def parse_expression(text):
|
||||
"""Parse a mask expression (see ``evaluate``) into a tree; raises ExpressionError with the position on mistakes."""
|
||||
return _Parser(str(text)).parse()
|
||||
|
||||
|
||||
def expression_labels(tree):
|
||||
"""Label patterns used in a parsed expression, in order."""
|
||||
if tree[0] == "label":
|
||||
return [tree[1]]
|
||||
if tree[0] == "keyword":
|
||||
return []
|
||||
return [label for child in tree[1:] for label in expression_labels(child)]
|
||||
|
||||
|
||||
def evaluate(tree, frame):
|
||||
"""Bool [H, W] mask of a parsed expression for one frame.
|
||||
|
||||
A label (wildcards allowed; quotes or '_' for spaces; a '-' inside a word is part of it) is the union of the
|
||||
matching instances,
|
||||
``all`` every instance, ``image`` the whole frame, ``none`` nothing. ``a | b`` (also ``+``, ``or``) is the union,
|
||||
``a - b`` the difference, ``a & b`` (``and``) the intersection, ``~a`` (``!``, ``not``) everything outside ``a``.
|
||||
"""
|
||||
kind = tree[0]
|
||||
if kind == "label":
|
||||
return frame_mask(frame, [seg for seg in frame.segments if label_matches(seg.label, tree[1])])
|
||||
if kind == "keyword":
|
||||
if tree[1] == "all":
|
||||
return frame_mask(frame)
|
||||
if tree[1] == "image":
|
||||
return torch.ones((frame.height, frame.width), dtype=torch.bool)
|
||||
return torch.zeros((frame.height, frame.width), dtype=torch.bool)
|
||||
if kind == "~":
|
||||
return ~evaluate(tree[1], frame)
|
||||
left, right = evaluate(tree[1], frame), evaluate(tree[2], frame)
|
||||
if kind == "|":
|
||||
return left | right
|
||||
if kind == "&":
|
||||
return left & right
|
||||
return left & ~right
|
||||
|
||||
|
||||
def expression_masks(tree, segs):
|
||||
"""MASK [B, H, W] float of a parsed expression, one mask per image of ``segs``."""
|
||||
return torch.stack([evaluate(tree, frame) for frame in segs]).float()
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Impact Pack SEGS
|
||||
# ---------------------------------------------------------------------------
|
||||
# The field names of Impact Pack's SEG, so its detailers read these items; NH-Nodes does not import Impact Pack.
|
||||
IMPACT_SEG = namedtuple("SEG", ["cropped_image", "cropped_mask", "confidence", "crop_region", "bbox", "label",
|
||||
"control_net_wrapper"], defaults=[None])
|
||||
|
||||
|
||||
def _centred_span(lo, hi, factor, limit):
|
||||
"""[start, end) of ``factor`` times the length of [lo, hi), centred on it and slid inside [0, limit).
|
||||
|
||||
It is never shorter than [lo, hi) and only shorter than asked when the image is, so it always contains [lo, hi).
|
||||
"""
|
||||
size = min(limit, max(hi - lo, round((hi - lo) * factor)))
|
||||
start = min(max(0, round((lo + hi - size) / 2)), limit - size)
|
||||
return start, start + size
|
||||
|
||||
|
||||
def detail_crop_region(width, height, box, crop_factor):
|
||||
"""[x1, y1, x2, y2] a detailer works on around ``box``: ``crop_factor`` times its size, centred, inside the image."""
|
||||
rx1, rx2 = _centred_span(box[0], box[2], crop_factor, width)
|
||||
ry1, ry2 = _centred_span(box[1], box[3], crop_factor, height)
|
||||
return [rx1, ry1, rx2, ry2]
|
||||
|
||||
|
||||
def to_impact_segs(frame, crop_factor=3.0):
|
||||
"""Impact Pack SEGS ``((height, width), [SEG, ...])`` for one frame; each mask is placed in its crop region."""
|
||||
items = []
|
||||
for seg in frame.segments:
|
||||
region = detail_crop_region(frame.width, frame.height, seg.box, crop_factor)
|
||||
rx1, ry1, rx2, ry2 = region
|
||||
x1, y1, x2, y2 = seg.box
|
||||
cropped = np.zeros((ry2 - ry1, rx2 - rx1), dtype=np.float32)
|
||||
cropped[y1 - ry1:y2 - ry1, x1 - rx1:x2 - rx1] = seg.mask.numpy()
|
||||
items.append(IMPACT_SEG(None, cropped, float(seg.score), region, [x1, y1, x2, y2], seg.label, None))
|
||||
return (frame.height, frame.width), items
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Preview
|
||||
# ---------------------------------------------------------------------------
|
||||
_PALETTE = (
|
||||
(230, 25, 75), (60, 180, 75), (255, 225, 25), (0, 130, 200), (245, 130, 48), (145, 30, 180),
|
||||
(70, 240, 240), (240, 50, 230), (210, 245, 60), (250, 190, 212), (0, 128, 128), (170, 110, 40),
|
||||
)
|
||||
|
||||
|
||||
@functools.lru_cache(maxsize=16)
|
||||
def _label_font(size):
|
||||
for name in ("arial.ttf", "DejaVuSans.ttf"): # both cover Vietnamese
|
||||
try:
|
||||
return ImageFont.truetype(name, size)
|
||||
except Exception:
|
||||
continue
|
||||
try:
|
||||
return ImageFont.load_default(size=size)
|
||||
except TypeError: # Pillow < 10.1
|
||||
return ImageFont.load_default()
|
||||
|
||||
|
||||
def _free_spot(x, y, width, height, placed, limit):
|
||||
"""``y`` moved down (or up near the bottom ``limit``) until a ``width`` x ``height`` label at ``x`` overlaps none
|
||||
of the ``placed`` (x1, y1, x2, y2) rectangles; labels of instances sharing a top edge (person, hair) then stack."""
|
||||
for _ in range(len(placed)):
|
||||
hit = next((r for r in placed if x < r[2] and r[0] < x + width and y < r[3] and r[1] < y + height), None)
|
||||
if hit is None:
|
||||
break
|
||||
y = hit[3] + 1 if hit[3] + 1 + height <= limit else max(0, hit[1] - height - 1)
|
||||
return y
|
||||
|
||||
|
||||
def render_preview(image, frame, fill=0.45):
|
||||
"""uint8 HxWx3 copy of ``image`` with each instance tinted, outlined and labelled "label score"."""
|
||||
canvas = np.ascontiguousarray(image[:, :, :3]).copy()
|
||||
height, width = canvas.shape[:2]
|
||||
if not frame.segments:
|
||||
return canvas
|
||||
thickness = max(1, round(min(height, width) / 700))
|
||||
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (2 * thickness + 1, 2 * thickness + 1))
|
||||
for index, seg in enumerate(frame.segments):
|
||||
color = np.array(_PALETTE[index % len(_PALETTE)], dtype=np.float32)
|
||||
x1, y1, x2, y2 = seg.box
|
||||
region = canvas[y1:y2, x1:x2]
|
||||
inside = seg.mask.numpy()
|
||||
region[inside] = (region[inside] * (1.0 - fill) + color * fill).astype(np.uint8)
|
||||
padded = np.pad(inside.astype(np.uint8), thickness) # so a box edge counts as an outline too
|
||||
edge = (padded > 0) & (cv2.erode(padded, kernel) == 0)
|
||||
region[edge[thickness:-thickness, thickness:-thickness]] = color.astype(np.uint8)
|
||||
|
||||
font = _label_font(max(12, round(min(height, width) / 45)))
|
||||
picture = Image.fromarray(canvas)
|
||||
draw = ImageDraw.Draw(picture)
|
||||
placed = [] # label rectangles drawn so far, so a label never hides another one
|
||||
for index, seg in enumerate(frame.segments):
|
||||
color = _PALETTE[index % len(_PALETTE)]
|
||||
text = f"{seg.label} {seg.score:.2f}"
|
||||
left, top, right, bottom = draw.textbbox((0, 0), text, font=font)
|
||||
pad = max(2, (bottom - top) // 6)
|
||||
tw, th = right - left + 2 * pad, bottom - top + 2 * pad
|
||||
# centred over the instance's topmost pixels (a head, a roof), not at a box corner that may be far from it
|
||||
top_columns = torch.nonzero(seg.mask[0]).flatten()
|
||||
anchor = seg.box[0] + int(top_columns[top_columns.numel() // 2]) if top_columns.numel() else seg.box[0]
|
||||
x = min(max(0, anchor - tw // 2), max(0, width - tw))
|
||||
y = _free_spot(x, seg.box[1] - th if seg.box[1] - th >= 0 else seg.box[1], tw, th, placed, height)
|
||||
placed.append((x, y, x + tw, y + th))
|
||||
draw.rectangle((x, y, x + tw, y + th), fill=color)
|
||||
dark_text = (0.299 * color[0] + 0.587 * color[1] + 0.114 * color[2]) > 150
|
||||
draw.text((x + pad - left, y + pad - top), text, font=font, fill=(0, 0, 0) if dark_text else (255, 255, 255))
|
||||
return np.asarray(picture)
|
||||
|
||||
|
||||
def preview_batch(image, segs):
|
||||
"""IMAGE batch with every instance of ``segs`` drawn on its image (tinted, outlined, labelled)."""
|
||||
if len(segs) != image.shape[0]:
|
||||
raise ValueError(
|
||||
f"[NH-Nodes] The image batch has {image.shape[0]} image(s) but the segs belong to {len(segs)}; "
|
||||
"connect the image the segs were found in."
|
||||
)
|
||||
pictures = []
|
||||
for picture, frame in zip(image, segs):
|
||||
if (picture.shape[0], picture.shape[1]) != (frame.height, frame.width):
|
||||
raise ValueError(
|
||||
f"[NH-Nodes] The image is {picture.shape[1]}x{picture.shape[0]} but the segs were found on a "
|
||||
f"{frame.width}x{frame.height} image; connect the image the segs were found in."
|
||||
)
|
||||
rgb = picture[..., :3] if picture.shape[-1] >= 3 else picture[..., :1].expand(-1, -1, 3)
|
||||
pixels = (rgb.float().clamp(0.0, 1.0) * 255.0).round().to(torch.uint8).cpu().numpy()
|
||||
pictures.append(torch.from_numpy(render_preview(pixels, frame).astype(np.float32) / 255.0))
|
||||
return torch.stack(pictures)
|
||||
@@ -11,12 +11,34 @@ def resize_image(image, target_size=768):
|
||||
new_h, new_w = target_size, int(w * target_size / h)
|
||||
return image.resize((new_w, new_h), Image.LANCZOS)
|
||||
|
||||
def tensor_to_pil(tensor_image):
|
||||
"""Convert tensor to PIL image"""
|
||||
def tensor_to_pil(tensor_image, *, rgb=False, keep_alpha=False):
|
||||
"""Convert ONE ComfyUI image ([H,W,C] or [1,H,W,C]) to PIL.
|
||||
|
||||
Values are clipped to [0, 1] first, so overshoot from resampling cannot wrap around
|
||||
in uint8. 1/3/4 channels become L/RGB/RGBA. Batches must be converted per image.
|
||||
|
||||
``rgb=True`` always gives an RGB picture: grey is expanded and alpha is dropped. With ``keep_alpha=True`` as
|
||||
well, a 4-channel image stays RGBA instead (every other image is still RGB).
|
||||
"""
|
||||
if tensor_image.dim() == 4:
|
||||
tensor_image = tensor_image.squeeze(0)
|
||||
np_image = (tensor_image.cpu().numpy() * 255).astype(np.uint8)
|
||||
return Image.fromarray(np_image)
|
||||
if tensor_image.shape[0] != 1:
|
||||
raise ValueError(
|
||||
f"tensor_to_pil expects a single image but got a batch of {tensor_image.shape[0]}; "
|
||||
"convert each image of the batch separately"
|
||||
)
|
||||
tensor_image = tensor_image[0]
|
||||
pixels = tensor_image.detach().cpu()
|
||||
if pixels.dtype == torch.bfloat16: # numpy has no bfloat16; other dtypes keep their own rounding
|
||||
pixels = pixels.float()
|
||||
np_image = np.clip(pixels.numpy() * 255.0, 0, 255).astype(np.uint8)
|
||||
if np_image.ndim == 3 and np_image.shape[2] == 1:
|
||||
np_image = np_image[:, :, 0]
|
||||
pil_image = Image.fromarray(np_image)
|
||||
if rgb:
|
||||
kept_modes = ("RGB", "RGBA") if keep_alpha else ("RGB",)
|
||||
if pil_image.mode not in kept_modes:
|
||||
pil_image = pil_image.convert("RGB")
|
||||
return pil_image
|
||||
|
||||
def pil_to_tensor(pil_image):
|
||||
"""Convert PIL image to tensor in ComfyUI format for IMAGE"""
|
||||
@@ -32,4 +54,21 @@ def pil_to_mask(pil_image):
|
||||
if len(np_image.shape) == 3: # Ensure single channel
|
||||
np_image = np_image[:,:,0]
|
||||
tensor = torch.from_numpy(np_image).unsqueeze(0)
|
||||
return tensor
|
||||
return tensor
|
||||
|
||||
def parse_hex_color(value, default=(0, 0, 0, 255)):
|
||||
"""Parse '#RGB', '#RRGGBB' or '#RRGGBBAA' (the '#' is optional) into an (r, g, b, a) tuple of 0-255 ints.
|
||||
|
||||
Without an alpha part the colour is opaque. Text that is not a valid colour returns ``default``.
|
||||
"""
|
||||
text = str(value or "").strip().lstrip("#")
|
||||
if len(text) == 3:
|
||||
text = "".join(ch * 2 for ch in text)
|
||||
if len(text) == 6:
|
||||
text += "ff"
|
||||
if len(text) != 8:
|
||||
return tuple(default)
|
||||
try:
|
||||
return tuple(int(text[i:i + 2], 16) for i in range(0, 8, 2))
|
||||
except ValueError:
|
||||
return tuple(default)
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
# Garment mask construction for VTON Ultimate Processor (NH).
|
||||
# Third-party code: FitDiT src/utils_mask.py (https://github.com/BoyuanJiang/FitDiT),
|
||||
# received via ComfyUI-Vton-Mask. License: CC BY-NC-SA 4.0 (non-commercial), not the
|
||||
# pack's MIT license. NH-Nodes changes: lint cleanups and bug fixes. See THIRD_PARTY_NOTICES.md.
|
||||
# pack's MIT license. NH-Nodes changes: lint cleanups, bug fixes and Image.fromarray calls without the mode
|
||||
# argument that Pillow 11.3 deprecated. See THIRD_PARTY_NOTICES.md.
|
||||
import numpy as np
|
||||
import cv2
|
||||
from PIL import Image, ImageDraw
|
||||
@@ -15,11 +16,27 @@ def remove_small(binary_mask, min_area=10000):
|
||||
small_white_regions = [region for region in regions if region.area < min_area]
|
||||
|
||||
for region in small_white_regions:
|
||||
min_row, min_col, max_row, max_col = region.bbox
|
||||
binary_mask[min_row:max_row, min_col:max_col] = 0
|
||||
# Only this region's pixels: its bounding box can overlap a large region.
|
||||
binary_mask[tuple(region.coords.T)] = 0
|
||||
return binary_mask
|
||||
|
||||
|
||||
def _merge_bound(keypoint_bound, cloth_bound, pick):
|
||||
"""Combine a keypoint bound (None when the whole keypoint group is missing) with the garment contour bound."""
|
||||
if keypoint_bound is None:
|
||||
return cloth_bound
|
||||
return pick(keypoint_bound, cloth_bound)
|
||||
|
||||
|
||||
def _fallback_box(x1, y1, x2, y2, shape):
|
||||
"""Whole-image edges only for bounds that neither keypoints nor the garment contour provided."""
|
||||
height, width = shape[:2]
|
||||
return (0 if x1 is None else x1, 0 if y1 is None else y1,
|
||||
width if x2 is None else x2, height if y2 is None else y2)
|
||||
|
||||
|
||||
# pose_data rows: body 0-17, foot 18-23, face 24-91, hands 92-133. A keypoint that was not detected is
|
||||
# negative (DWPose marks it -1 and the node then scales it by the width/height), so only values > 0 count.
|
||||
def get_img_agnostic_upper_rectangle(im_parse, pose_data, offset_top, offset_bottom, offset_left, offset_right):
|
||||
|
||||
faces = pose_data[24:92]
|
||||
@@ -69,48 +86,37 @@ def get_img_agnostic_upper_rectangle(im_parse, pose_data, offset_top, offset_bot
|
||||
except Exception:
|
||||
cloth_exist = False
|
||||
|
||||
x1 = [body[i, 0] for i in [2, 3, 4] if body[i, 0] != 0]+\
|
||||
[hands2[i, 0] for i in [5,9,13] if hands2[i, 0] != 0]+\
|
||||
[hands1[i, 0] for i in [5,9,13] if hands1[i, 0] != 0]
|
||||
if x1:
|
||||
x1 = np.min(x1)
|
||||
else:
|
||||
x1 = 0
|
||||
y1 = [body[i, 1] for i in [2, 5] if body[i, 1] != 0]+\
|
||||
[hands2[i, 1] for i in [5,9,13] if hands2[i, 1] != 0]+\
|
||||
[hands1[i, 1] for i in [5,9,13] if hands1[i, 1] != 0]
|
||||
if y1:
|
||||
y1 = np.min(y1)
|
||||
else:
|
||||
y1 = 0
|
||||
x2 = [body[i, 0] for i in [5, 6, 7] if body[i, 0] != 0]+\
|
||||
[hands1[i, 0] for i in [5,9,13] if hands1[i, 0] != 0]+\
|
||||
[hands2[i, 0] for i in [5,9,13] if hands2[i, 0] != 0]
|
||||
if x2:
|
||||
x2 = np.max(x2)
|
||||
else:
|
||||
x2 = parse_array.shape[1]
|
||||
y2 = [body[i, 1] for i in [8, 11] if body[i, 1] != 0]+\
|
||||
[hands2[i, 1] for i in [5,9,13] if hands2[i, 1] != 0]+\
|
||||
[hands1[i, 1] for i in [5,9,13] if hands1[i, 1] != 0]
|
||||
if y2:
|
||||
y2 = np.max(y2)
|
||||
else:
|
||||
y2 = parse_array.shape[0]
|
||||
x1 = [body[i, 0] for i in [2, 3, 4] if body[i, 0] > 0]+\
|
||||
[hands2[i, 0] for i in [5,9,13] if hands2[i, 0] > 0]+\
|
||||
[hands1[i, 0] for i in [5,9,13] if hands1[i, 0] > 0]
|
||||
x1 = np.min(x1) if x1 else None
|
||||
y1 = [body[i, 1] for i in [2, 5] if body[i, 1] > 0]+\
|
||||
[hands2[i, 1] for i in [5,9,13] if hands2[i, 1] > 0]+\
|
||||
[hands1[i, 1] for i in [5,9,13] if hands1[i, 1] > 0]
|
||||
y1 = np.min(y1) if y1 else None
|
||||
x2 = [body[i, 0] for i in [5, 6, 7] if body[i, 0] > 0]+\
|
||||
[hands1[i, 0] for i in [5,9,13] if hands1[i, 0] > 0]+\
|
||||
[hands2[i, 0] for i in [5,9,13] if hands2[i, 0] > 0]
|
||||
x2 = np.max(x2) if x2 else None
|
||||
y2 = [body[i, 1] for i in [8, 11] if body[i, 1] > 0]+\
|
||||
[hands2[i, 1] for i in [5,9,13] if hands2[i, 1] > 0]+\
|
||||
[hands1[i, 1] for i in [5,9,13] if hands1[i, 1] > 0]
|
||||
y2 = np.max(y2) if y2 else None
|
||||
|
||||
pad_y1 = 20
|
||||
pad_y2 = 10
|
||||
pad_x1 = pad_x2 = 25
|
||||
|
||||
if cloth_exist:
|
||||
x1 = min(x1, _x1)
|
||||
x2 = max(x2, _x2)
|
||||
y1 = min(y1, _y1)
|
||||
if y2>_y2:
|
||||
x1 = _merge_bound(x1, _x1, min)
|
||||
x2 = _merge_bound(x2, _x2, max)
|
||||
y1 = _merge_bound(y1, _y1, min)
|
||||
if y2 is not None and y2>_y2:
|
||||
pad_y2 = 0
|
||||
y2 = max(y2, _y2)
|
||||
y2 = _merge_bound(y2, _y2, max)
|
||||
x1, y1, x2, y2 = _fallback_box(x1, y1, x2, y2, parse_array.shape)
|
||||
|
||||
y_face = [faces[i, 1] for i in [5, 11] if faces[i, 1] != 0]
|
||||
y_face = [faces[i, 1] for i in [5, 11] if faces[i, 1] > 0]
|
||||
|
||||
if y_face:
|
||||
y_face = np.mean(y_face)
|
||||
@@ -120,8 +126,8 @@ def get_img_agnostic_upper_rectangle(im_parse, pose_data, offset_top, offset_bot
|
||||
|
||||
|
||||
agnostic_draw.rectangle((max(0, x1-pad_x1+offset_left), max(0, y1-pad_y1+offset_top), min(x2+pad_x2+offset_right, parse_array.shape[1]), min(y2+pad_y2+offset_bottom, parse_array.shape[0])), 'gray', 'gray')
|
||||
agnostic.paste(img_black, None, Image.fromarray(np.uint8(parse_head * 255), 'L'))
|
||||
# agnostic.paste(img_black, None, Image.fromarray(np.uint8(parse_fixed * 255), 'L'))
|
||||
agnostic.paste(img_black, None, Image.fromarray(np.uint8(parse_head * 255)))
|
||||
# agnostic.paste(img_black, None, Image.fromarray(np.uint8(parse_fixed * 255)))
|
||||
|
||||
mask_gray = agnostic.copy()
|
||||
mask = agnostic.point(lambda p: p == 128 and 255)
|
||||
@@ -180,33 +186,27 @@ def get_img_agnostic_lower_rectangle(im_parse, pose_data, offset_top, offset_bot
|
||||
except Exception:
|
||||
all_cloth_exist = False
|
||||
|
||||
x1 = [body[i, 0] for i in [8, 9, 10] if body[i, 0] != 0]+[foot2[i, 0] for i in [0, 1, 2] if foot2[i, 0] != 0]+\
|
||||
[body[i, 0] for i in [11, 12, 13] if body[i, 0] != 0]+[foot1[i, 0] for i in [0, 1, 2] if foot1[i, 0] != 0]
|
||||
if x1:
|
||||
x1 = np.min(x1)
|
||||
else:
|
||||
x1=0
|
||||
y1 = min(body[8, 1], body[11, 1])
|
||||
x2 = [body[i, 0] for i in [11, 12, 13] if body[i, 0] != 0]+[foot1[i, 0] for i in [0, 1, 2] if foot1[i, 0] != 0]+\
|
||||
[body[i, 0] for i in [8, 9, 10] if body[i, 0] != 0]+[foot2[i, 0] for i in [0, 1, 2] if foot2[i, 0] != 0]
|
||||
if x2:
|
||||
x2 = np.max(x2)
|
||||
else:
|
||||
x2 = parse_array.shape[1]
|
||||
y2 = [body[i, 1] for i in [10, 13] if body[i, 1] != 0]+[foot1[i, 1] for i in [2] if foot2[i, 1] != 0]+\
|
||||
[foot2[i, 1] for i in [2] if foot2[i, 1] != 0]
|
||||
if y2:
|
||||
y2 = np.max(y2)
|
||||
else:
|
||||
y2 = parse_array.shape[0]
|
||||
x1 = [body[i, 0] for i in [8, 9, 10] if body[i, 0] > 0]+[foot2[i, 0] for i in [0, 1, 2] if foot2[i, 0] > 0]+\
|
||||
[body[i, 0] for i in [11, 12, 13] if body[i, 0] > 0]+[foot1[i, 0] for i in [0, 1, 2] if foot1[i, 0] > 0]
|
||||
x1 = np.min(x1) if x1 else None
|
||||
# Keypoints that were not detected are negative (DWPose marks them -1): use only the detected hips.
|
||||
y1 = [body[i, 1] for i in [8, 11] if body[i, 1] > 0]
|
||||
y1 = np.min(y1) if y1 else None
|
||||
x2 = [body[i, 0] for i in [11, 12, 13] if body[i, 0] > 0]+[foot1[i, 0] for i in [0, 1, 2] if foot1[i, 0] > 0]+\
|
||||
[body[i, 0] for i in [8, 9, 10] if body[i, 0] > 0]+[foot2[i, 0] for i in [0, 1, 2] if foot2[i, 0] > 0]
|
||||
x2 = np.max(x2) if x2 else None
|
||||
y2 = [body[i, 1] for i in [10, 13] if body[i, 1] > 0]+[foot1[i, 1] for i in [2] if foot1[i, 1] > 0]+\
|
||||
[foot2[i, 1] for i in [2] if foot2[i, 1] > 0]
|
||||
y2 = np.max(y2) if y2 else None
|
||||
if cloth_exist:
|
||||
x1 = min(x1, _x1)
|
||||
x2 = max(x2, _x2)
|
||||
y1 = min(y1, _y1)
|
||||
y2 = max(y2, _y2)
|
||||
x1 = _merge_bound(x1, _x1, min)
|
||||
x2 = _merge_bound(x2, _x2, max)
|
||||
y1 = _merge_bound(y1, _y1, min)
|
||||
y2 = _merge_bound(y2, _y2, max)
|
||||
elif all_cloth_exist:
|
||||
x1 = min(x1, _x1)
|
||||
x2 = max(x2, _x2)
|
||||
x1 = _merge_bound(x1, _x1, min)
|
||||
x2 = _merge_bound(x2, _x2, max)
|
||||
x1, y1, x2, y2 = _fallback_box(x1, y1, x2, y2, parse_array.shape)
|
||||
|
||||
pad_y1 = 5 if cloth_exist else 50
|
||||
pad_y2 = 10
|
||||
@@ -214,8 +214,8 @@ def get_img_agnostic_lower_rectangle(im_parse, pose_data, offset_top, offset_bot
|
||||
|
||||
|
||||
agnostic_draw.rectangle((max(0, x1-pad_x1+offset_left), max(0, y1-pad_y1+offset_top), min(x2+pad_x2+offset_right, parse_array.shape[1]), min(y2+pad_y2+offset_bottom, parse_array.shape[0])), 'gray', 'gray')
|
||||
agnostic.paste(img_black, None, Image.fromarray(np.uint8(parse_head * 255), 'L'))
|
||||
agnostic.paste(img_black, None, Image.fromarray(np.uint8(parse_fixed * 255), 'L'))
|
||||
agnostic.paste(img_black, None, Image.fromarray(np.uint8(parse_head * 255)))
|
||||
agnostic.paste(img_black, None, Image.fromarray(np.uint8(parse_fixed * 255)))
|
||||
|
||||
mask_gray = agnostic.copy()
|
||||
mask = agnostic.point(lambda p: p == 128 and 255)
|
||||
@@ -266,48 +266,37 @@ def get_img_agnostic_dresses_rectangle(im_parse, pose_data, offset_top, offset_b
|
||||
except Exception:
|
||||
cloth_exist = False
|
||||
|
||||
x1 = [body[i, 0] for i in [2, 3, 4] if body[i, 0] != 0]+\
|
||||
[body[i, 0] for i in [8, 9, 10] if body[i, 0] != 0]+\
|
||||
[foot2[i, 0] for i in [0, 1, 2] if foot2[i, 0] != 0]+\
|
||||
[hands2[i, 0] for i in [5, 9, 13, 17] if hands2[i, 0] != 0]
|
||||
if x1:
|
||||
x1 = np.min(x1)
|
||||
else:
|
||||
x1 = 0
|
||||
y1 = [body[2, 1], body[5, 1]]+\
|
||||
[hands2[i, 1] for i in [5, 9, 13, 17] if hands2[i, 1] != 0]+\
|
||||
[hands1[i, 1] for i in [5, 9, 13, 17] if hands1[i, 1] != 0]
|
||||
if y1:
|
||||
y1 = np.min(y1)
|
||||
else:
|
||||
y1 = 0
|
||||
x2 = [body[i, 0] for i in [5, 6, 7] if body[i, 0] != 0]+\
|
||||
[body[i, 0] for i in [11, 12, 13] if body[i, 0] != 0]+\
|
||||
[foot1[i, 0] for i in [0, 1, 2] if foot1[i, 0] != 0]+\
|
||||
[hands1[i, 0] for i in [5, 9, 13, 17] if hands1[i, 0] != 0]
|
||||
if x2:
|
||||
x2 = np.max(x2)
|
||||
else:
|
||||
x2 = parse_array.shape[1]
|
||||
y2 = [body[i, 1] for i in [10, 13] if body[i, 1] != 0]+[foot1[i, 1] for i in [0, 1, 2] if foot1[i, 1] != 0]+\
|
||||
[foot2[i, 1] for i in [0, 1, 2] if foot2[i, 1] != 0]
|
||||
if y2:
|
||||
y2 = np.max(y2)
|
||||
else:
|
||||
y2 = parse_array.shape[0]
|
||||
x1 = [body[i, 0] for i in [2, 3, 4] if body[i, 0] > 0]+\
|
||||
[body[i, 0] for i in [8, 9, 10] if body[i, 0] > 0]+\
|
||||
[foot2[i, 0] for i in [0, 1, 2] if foot2[i, 0] > 0]+\
|
||||
[hands2[i, 0] for i in [5, 9, 13, 17] if hands2[i, 0] > 0]
|
||||
x1 = np.min(x1) if x1 else None
|
||||
y1 = [body[i, 1] for i in [2, 5] if body[i, 1] > 0]+\
|
||||
[hands2[i, 1] for i in [5, 9, 13, 17] if hands2[i, 1] > 0]+\
|
||||
[hands1[i, 1] for i in [5, 9, 13, 17] if hands1[i, 1] > 0]
|
||||
y1 = np.min(y1) if y1 else None
|
||||
x2 = [body[i, 0] for i in [5, 6, 7] if body[i, 0] > 0]+\
|
||||
[body[i, 0] for i in [11, 12, 13] if body[i, 0] > 0]+\
|
||||
[foot1[i, 0] for i in [0, 1, 2] if foot1[i, 0] > 0]+\
|
||||
[hands1[i, 0] for i in [5, 9, 13, 17] if hands1[i, 0] > 0]
|
||||
x2 = np.max(x2) if x2 else None
|
||||
y2 = [body[i, 1] for i in [10, 13] if body[i, 1] > 0]+[foot1[i, 1] for i in [0, 1, 2] if foot1[i, 1] > 0]+\
|
||||
[foot2[i, 1] for i in [0, 1, 2] if foot2[i, 1] > 0]
|
||||
y2 = np.max(y2) if y2 else None
|
||||
|
||||
if cloth_exist:
|
||||
x1 = min(x1, _x1)
|
||||
x2 = max(x2, _x2)
|
||||
y1 = min(y1, _y1)
|
||||
y2 = max(y2, _y2)
|
||||
x1 = _merge_bound(x1, _x1, min)
|
||||
x2 = _merge_bound(x2, _x2, max)
|
||||
y1 = _merge_bound(y1, _y1, min)
|
||||
y2 = _merge_bound(y2, _y2, max)
|
||||
x1, y1, x2, y2 = _fallback_box(x1, y1, x2, y2, parse_array.shape)
|
||||
|
||||
|
||||
pad_y1 = 20
|
||||
pad_y2 = 10
|
||||
pad_x1 = pad_x2 = 50
|
||||
|
||||
y_face = [faces[i, 1] for i in [5, 11] if faces[i, 1] != 0]
|
||||
y_face = [faces[i, 1] for i in [5, 11] if faces[i, 1] > 0]
|
||||
|
||||
if y_face:
|
||||
y_face = np.mean(y_face)
|
||||
@@ -317,9 +306,9 @@ def get_img_agnostic_dresses_rectangle(im_parse, pose_data, offset_top, offset_b
|
||||
|
||||
|
||||
agnostic_draw.rectangle((max(0, x1-pad_x1+offset_left), max(0, y1-pad_y1+offset_top), min(x2+pad_x2+offset_right, parse_array.shape[1]), min(y2+pad_y2+offset_bottom, parse_array.shape[0])), 'gray', 'gray')
|
||||
agnostic.paste(img_black, None, Image.fromarray(np.uint8(parse_head * 255), 'L'))
|
||||
agnostic.paste(img_black, None, Image.fromarray(np.uint8(parse_shoes * 255), 'L'))
|
||||
agnostic.paste(img_black, None, Image.fromarray(np.uint8(parse_fixed * 255), 'L'))
|
||||
agnostic.paste(img_black, None, Image.fromarray(np.uint8(parse_head * 255)))
|
||||
agnostic.paste(img_black, None, Image.fromarray(np.uint8(parse_shoes * 255)))
|
||||
agnostic.paste(img_black, None, Image.fromarray(np.uint8(parse_fixed * 255)))
|
||||
|
||||
mask_gray = agnostic.copy()
|
||||
mask = agnostic.point(lambda p: p == 128 and 255)
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
# Changelog
|
||||
|
||||
All notable changes to NH-Nodes. Versions follow the Comfy Registry releases.
|
||||
"Behaviour change" marks fixes that can change the output of an existing workflow.
|
||||
|
||||
## 1.7.0 — SAM 3 segmentation and instance nodes (2026-10-18)
|
||||
|
||||
### Added
|
||||
Six nodes under **NH-Nodes → Vision** (75 nodes in total) and a new data type, `NH_SEGS`. Nothing in the existing nodes changes.
|
||||
- `SAM3 Segment (NH)`: finds every instance of the concepts you name (short English noun phrases, one per line or separated by commas) with Meta's SAM 3. Outputs the instances (`segs`), their union `mask` and the `count`; `Segs Preview (NH)` shows what it found. `detail_level` `balanced` (default) segments each instance a second time on a close-up cut from the full-resolution image, so small and medium objects get crisp edges even on 4K to 8K photos; `fast` keeps the whole-image pass only. Other inputs: `threshold`, `max_per_concept`, `checkpoint` and `keep_model_loaded`.
|
||||
- `NH_SEGS`: for every image of a batch, a list of instances with a label, a score, a tight box and the mask inside that box only, so dozens of instances on an 8K photo stay light. Full-size masks are built only when a node outputs a `MASK`.
|
||||
- `Segs Select (NH)`: keeps instances by label (`*` and `?` wildcards), score and size, sorts them (score, area, position, centre first, as found) and takes a slice, for example the second person from the left.
|
||||
- `Segs Compose (NH)`: one mask from a formula over labels: `|` `+` `or` (union), `-` (minus), `&` `and` (overlap), `~` `!` `not` (everything except), parentheses, and `all`, `image`, `none`. `~person` is the background, `(wall | ceiling) - window` the walls without the windows. A hyphen inside a word belongs to the label (`t-shirt` needs no quotes), so put spaces around a minus: `person - hair`.
|
||||
- `Segs To Masks (NH)`: one mask per instance, the union per image, the labels and the count. It stops with a message when the masks would need more than about half of the free RAM, for example hundreds of instances on an 8K photo.
|
||||
- `Segs To Impact SEGS (NH)`: Impact Pack's `SEGS` for one image of the batch (`crop_factor` 3.0 like Impact Pack). Impact Pack is not needed for anything else in NH-Nodes.
|
||||
- `Segs Preview (NH)`: draws the instances (tint, outline, label and score) on the picture they were found in.
|
||||
|
||||
### Notes
|
||||
- SAM3 Segment needs an NVIDIA GPU with CUDA: the official sam3 code builds part of the model on the GPU, so there is no CPU mode.
|
||||
- NH-Nodes does not install or download anything for it. You install Meta's `sam3` package yourself (the Vision guide gives the command, pinned to the commit NH-Nodes was tested with; newer commits with the same image API also work), and the weights are gated on Hugging Face: request access, accept Meta's SAM License and put `sam3.pt` in `models/sam3` (or a folder registered as `sam3` in `extra_model_paths.yaml`). The folder is shared with other SAM 3 packs, so an existing copy is found. The licence is in `THIRD_PARTY_NOTICES.md`.
|
||||
- The model stays in system memory between runs and moves to the GPU only while the node runs, unless `keep_model_loaded` is on.
|
||||
- `detail_level` has two levels for now, `balanced` and `fast`.
|
||||
- SAM 3 describes whatever it looks at on a grid of about 288 cells. A subject that fills most of an 8K frame therefore gets edges at about 1/300 of its size: fine for the outline of a person, soft on hair, lace and fur. Hair-level edges are not part of this release.
|
||||
- SAM 3 first sees the whole picture squeezed into a square, so on a picture wider or taller than 4:1 it can miss small things. The console says so; split such a picture into tiles with `Image Tile (NH)`.
|
||||
- A file in the `sam3` folder that is not a SAM 3 checkpoint, or an incomplete download, is refused with the download advice before anything runs.
|
||||
|
||||
### Planned
|
||||
Not part of this release, and the names may change: scene parsing that labels the whole picture (sky, wall, floor…), automatic concept tagging with ready presets and a report, matte refinement for hair and fur edges (it will add a `max` detail level), and parsing of each person's parts.
|
||||
|
||||
## 1.6.2 — Load images matching picks the right group
|
||||
|
||||
- `Load images matching` (behaviour change): when the source and the matching library share parent folders (for example
|
||||
`D:/Photos/Source/Nu/…` and `D:/Photos/Library/{Nam,Nu}/…`), the shared folders no longer outweigh the source's
|
||||
group, so a source in `Nu` picks `Nu/<match>` instead of the first folder in alphabetical order. Same-drive and
|
||||
cross-drive setups now rank identically, and folders elsewhere in the library no longer change which match wins.
|
||||
|
||||
## 1.6.1 — Cleanup and hardening
|
||||
|
||||
- Internal restructuring with no change to node ids, inputs or outputs: the large image-tools module is split into one
|
||||
module per node family, shared helpers live in `core/` (`image_folders`, `image_ops`, `mask_ops`, `model_files`), and
|
||||
the frontend extensions share one widget helper module.
|
||||
- Widgets are hidden with the frontend's own `hidden` property when available (older frontends keep the previous
|
||||
method); values are still saved with the workflow.
|
||||
- `Load Images Folder`: the image count used for auto increment now honours `recursive`; a wired `folder_path` no
|
||||
longer fingerprints ComfyUI's input folder by mistake.
|
||||
- HTTP routes: the folder-count and large-preview routes no longer return absolute paths, answer with proper status
|
||||
codes (malformed JSON → 400) and do their file work off the server's event loop.
|
||||
- `NH Save Large Image` accepts single-channel images; bfloat16 IMAGE tensors convert correctly; image conversions no
|
||||
longer use Pillow's deprecated `fromarray(mode=…)`.
|
||||
|
||||
## 1.6.0 — Performance, caching and VRAM
|
||||
|
||||
### Faster graphs
|
||||
- **Lazy switches:** `If/Else (NH)`, `Switch N (NH)` and `Random Choice (NH)` now ask ComfyUI only for the branch they pick, so the other branches (for example a second sampler) are never executed. The choice itself is unchanged. A connected input whose upstream returns nothing still counts as connected.
|
||||
- `Layer Stack Composite (NH)` is cached like any pure node instead of re-running on every queue.
|
||||
- `Load images matching`: results are cached until the matched files change; the search stays inside the matching root (no more scanning parent folders, drive roots or the models folder). When nothing matches, `found` is `False` and `images` returns the new optional `fallback_images` input (evaluated only on a miss); without a fallback, `images` is blocked with a message and the rest of the workflow keeps running (behaviour change: it used to raise an error). Use `fallback_images` rather than routing `images` through a switch — a blocked input stops the switch too. A drive or network-share root cannot be searched recursively any more.
|
||||
- Indexed loaders (`Load Diffusion Model Index`, `Load VAE Index`, `Load Clip Index`) reuse the model ComfyUI still holds, so editing a slot you did not select no longer reloads it from disk; they keep only a weak reference, so ComfyUI's RAM-pressure cache can still free the model (`--cache-classic` keeps a normal reference). VAEs are loaded with their file metadata and validated like the core loader does.
|
||||
- `Mask Morphology (NH)`: rewritten on torch (CPU or GPU). Soft masks keep their gradient, masks touching the border no longer shrink from the edge, and large kernels are up to 100× faster. Even kernel lengths act as the next odd length (behaviour change).
|
||||
|
||||
### VRAM and models
|
||||
- `VTON Ultimate Processor (NH)`: new optional `keep_model_loaded` (default **off**). Off frees the ONNX Runtime GPU memory after each run that really used the GPU (about 1.5 s slower per run); on keeps today's speed and is much faster for image lists, which ComfyUI runs one item at a time. ComfyUI is asked to make room before the models are loaded on the GPU, except once ONNX Runtime has fallen back to the CPU.
|
||||
- SegFormer nodes now really make ComfyUI unload models when VRAM is tight (a device-index mismatch prevented it).
|
||||
- Model downloads are pinned to tested Hugging Face revisions and only fetch the needed files; a half-downloaded VTON folder is repaired instead of trusted.
|
||||
- `extra_model_paths.yaml` support: the folder names `nh_nodes` (SegFormer models, `.safetensors`) and `vton_mask` (VTON models, `.onnx`) can point to extra locations; existing installs keep working without moving anything.
|
||||
|
||||
### Viewer
|
||||
- `NH Large Image Preview` / `NH Large Image Compare`: browse every image of a batch with ◀ n / N ▶ or the ←/→ keys.
|
||||
|
||||
## 1.5.0 — Correctness fixes
|
||||
|
||||
### Image
|
||||
- **Batches everywhere:** `VTON Ultimate Processor`, `NH Simple Face Paste` and `NH Agnostic Image Generator` now process every image of a batch (smaller inputs with batch 1 are reused for every item). Behaviour change: Face Paste returns the full destination batch instead of one image. VTON stops with a clear error naming the batch index when no person is detected (it used to return a meaningless mask or crash).
|
||||
- **Values are clipped before 8-bit conversion**, so overshoot from resampling no longer wraps bright pixels to black (Face Paste, Mask-Aware Resize, shared helpers).
|
||||
- `NH Mask-Aware Resize`: an empty mask now respects the mode (pad = fit and pad with `pad_color_hex`, crop = cover and centre) instead of stretching.
|
||||
- `NH Agnostic Image Generator`: masks of a different size are resized to the image, 2-D masks are accepted, soft masks keep their gradient, and the `noise` fill is deterministic (behaviour change).
|
||||
- `Layer Layout Composite` / `Layer Stack Composite`: RGBA layers are pasted with their own alpha instead of as opaque rectangles. RGBA backgrounds give RGBA output.
|
||||
- Padding colours accept `#RRGGBBAA`; without an alpha part the padding is opaque (it used to copy the blue value into alpha).
|
||||
- `Save Image Path (NH)`: `filename_prefix` and `new_folder_name` can no longer write outside the chosen folder (on Windows a prefix may not contain `:`, which blocks drive prefixes such as `D:name`); a prefix such as `renders/img` creates the sub-folder. An absolute `folder_path` is still allowed.
|
||||
- `NH Save Large Image`: WEBP (16383 px) and JPEG (65500 px) size limits are checked before anything is written; `--disable-metadata` is respected.
|
||||
- `Image Label (NH)`: much faster font fitting (cached fonts + binary search), same sizes as before.
|
||||
- `Image Grid Composite (NH)`: refuses a canvas that would not fit in the available memory with a clear message instead of crashing.
|
||||
- `NH Large Preview Cache Manager`: actions run on every queue (it used to be cached).
|
||||
|
||||
### Resolution
|
||||
- `NH Remove Resize Padding`: images resized with `cover_crop` are no longer stretched back (that distorted them). With `original_size` the picture is scaled back uniformly into the original frame and the cropped-away borders are filled from the edges; use `NH Restore Image Geometry` to composite onto the original instead.
|
||||
- Resize nodes keep float precision for Lanczos/box/hamming (no 8-bit quantisation).
|
||||
- `NH Smart Resolution Picker`: a stale preset or model name is reported as a warning in `info`.
|
||||
|
||||
### Logic, text and lists
|
||||
- `Math Eval (NH)`: uses a shared safe evaluator. Behaviour change: an invalid expression now shows an error that names the expression instead of silently returning 0. `x / 0`, `x // 0` and `x % 0` give 0 with a warning. numpy/torch scalar inputs work.
|
||||
- `Random Choice (NH)`: the weight list follows the input slots (`weights` item *i* belongs to `input_i`). Behaviour change when some slots are empty.
|
||||
- Seeds are used literally in `Random Choice`, `Prompt Scheduler`, `Text Split Lines` and `Text Random Line`, so seed 0 is reproducible (behaviour change).
|
||||
- `Prompt Template (NH)`: an empty variable no longer glues the neighbouring words together.
|
||||
- `Regex Extract (NH)`: optional groups that did not match give an empty string instead of "no match".
|
||||
- `Pack Universal (NH)`: values are stored by slot, so an empty `input_0` no longer shifts the other indices (behaviour change).
|
||||
- `Compare (NH)` operator `in`: plain membership / substring test (no more fuzzy numeric matching).
|
||||
- `Batch Merge (NH)`: RGB and RGBA batches can be merged; letterbox bars are transparent for both; very thin inputs no longer crash `pad`.
|
||||
- `NH Mask Aspect Ratio Match`: `pad`/`crop` placement fixed (no crash at the image edge, requested size honoured) and the whole batch is processed.
|
||||
- VTON garment masks: undetected keypoints and missing keypoint groups no longer stretch the mask to the image edges; small parsing islands are removed without cutting rectangles out of the garment.
|
||||
- `Universal Slider Builder (NH)` is marked deprecated (it never produced output); existing workflows still load.
|
||||
|
||||
## 1.4.3 — Tooling, licensing and dependencies
|
||||
- CI runs ruff and the test suite on every pull request; publishing to the Comfy Registry only happens when it passes.
|
||||
- `onnxruntime` is no longer installed automatically (the CPU wheel overwrote `onnxruntime-gpu`). `VTON Ultimate Processor` explains how to install it and falls back to the CPU with a warning.
|
||||
- License notices: the VTON code and parsing models are CC BY-NC-SA 4.0 and the face-parsing model is non-commercial — see `THIRD_PARTY_NOTICES.md`.
|
||||
|
||||
## 1.4.2
|
||||
- `NH Smart Resolution Picker`: FLUX.2 latents are 128 channels at 1/16 (images came out twice the size).
|
||||
- `Load Clip Index (NH)`: works with current ComfyUI (`CLIPType`) and lists every CLIP type.
|
||||
- `Counter (NH)`: counts on every queue.
|
||||
- `NH Mask-Aware Resize`: `crop` keeps the whole mask.
|
||||
@@ -0,0 +1,241 @@
|
||||
"""Render a ComfyUI-style SVG card for every NH-Nodes node from its INPUT_TYPES / RETURN_TYPES.
|
||||
|
||||
Run with ComfyUI's Python from the ComfyUI root:
|
||||
python custom_nodes/NH-Nodes/docs/_tools/build_node_cards.py [output_dir]
|
||||
Cards are generated from the live node definitions, so they never drift from the code.
|
||||
"""
|
||||
|
||||
import importlib
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
from html import escape
|
||||
|
||||
COMFY_ROOT = os.getcwd()
|
||||
sys.path[:0] = [COMFY_ROOT, os.path.join(COMFY_ROOT, "custom_nodes")]
|
||||
|
||||
import torch # noqa: E402
|
||||
|
||||
if not torch.cuda.is_available():
|
||||
from comfy.cli_args import args # noqa: E402
|
||||
|
||||
args.cpu = True
|
||||
|
||||
PACK_DIR = os.path.join(COMFY_ROOT, "custom_nodes", "NH-Nodes")
|
||||
OUT_DIR = sys.argv[1] if len(sys.argv) > 1 else os.path.join(PACK_DIR, "docs", "assets", "nodes")
|
||||
|
||||
# ComfyUI default dark palette (frontend 1.55) for link types; NH custom types get amber.
|
||||
TYPE_COLORS = {
|
||||
"IMAGE": "#64B5F6", "MASK": "#81C784", "LATENT": "#FF9CF9", "MODEL": "#B39DDB",
|
||||
"CLIP": "#FFD500", "VAE": "#FF6E6E", "CONDITIONING": "#FFA931", "CONTROL_NET": "#6EE7B7",
|
||||
"INT": "#9CC2E5", "FLOAT": "#9CC2E5", "STRING": "#C5E1A5", "BOOLEAN": "#F48FB1", "*": "#CFD8DC",
|
||||
}
|
||||
CUSTOM_TYPE_COLOR = "#E0B272"
|
||||
WIDGET_TYPES = {"INT", "FLOAT", "STRING", "BOOLEAN"}
|
||||
|
||||
W = 360
|
||||
PAD = 12
|
||||
TITLE_H = 46
|
||||
ROW_H = 22
|
||||
WIDGET_H = 24
|
||||
WIDGET_GAP = 5
|
||||
FONT = "Inter, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif"
|
||||
MONO = "'JetBrains Mono', Consolas, 'Courier New', monospace"
|
||||
|
||||
|
||||
FILE_EXTENSIONS = (".png", ".jpg", ".jpeg", ".webp", ".bmp", ".tif", ".tiff", ".gif", ".safetensors", ".ckpt",
|
||||
".pt", ".pth", ".bin", ".gguf", ".onnx", ".sft")
|
||||
|
||||
|
||||
def is_file_combo(options, opts):
|
||||
"""Combos listing files from the user's folders (inputs, models): never publish those names."""
|
||||
if opts.get("image_upload") or opts.get("video_upload"):
|
||||
return True
|
||||
return any(str(option).lower().endswith(FILE_EXTENSIONS) for option in options)
|
||||
|
||||
|
||||
def type_name(spec_type):
|
||||
if isinstance(spec_type, (list, tuple)):
|
||||
return "COMBO"
|
||||
return str(spec_type)
|
||||
|
||||
|
||||
def type_color(name):
|
||||
return TYPE_COLORS.get(name, CUSTOM_TYPE_COLOR)
|
||||
|
||||
|
||||
def short(value, limit=24):
|
||||
text = str(value).replace("\n", " ⏎ ")
|
||||
return text if len(text) <= limit else text[: limit - 1] + "…"
|
||||
|
||||
|
||||
def fmt_number(value, opts):
|
||||
if isinstance(value, float):
|
||||
step = opts.get("step", 0.01) or 0.01
|
||||
decimals = max(0, min(4, len(str(step).split(".")[1]) if "." in str(step) else 0))
|
||||
return f"{value:.{decimals}f}"
|
||||
return str(value)
|
||||
|
||||
|
||||
def collapse_numbered(entries):
|
||||
"""Fold long numbered families (lora_1..lora_64, enable_1..) into first two + a summary row."""
|
||||
families = {}
|
||||
for name, *_ in entries:
|
||||
match = re.match(r"^(.*?)(\d+)$", name)
|
||||
if match:
|
||||
families.setdefault(match.group(1), []).append(int(match.group(2)))
|
||||
folded = {prefix: max(nums) for prefix, nums in families.items() if len(nums) > 4}
|
||||
result, emitted = [], set()
|
||||
for entry in entries:
|
||||
match = re.match(r"^(.*?)(\d+)$", entry[0])
|
||||
if match and match.group(1) in folded:
|
||||
prefix, num = match.group(1), int(match.group(2))
|
||||
if num <= 2:
|
||||
result.append(entry)
|
||||
elif prefix not in emitted:
|
||||
emitted.add(prefix)
|
||||
result.append((f"{prefix}3 … {prefix}{folded[prefix]}", "FOLD", {}, entry[3]))
|
||||
continue
|
||||
result.append(entry)
|
||||
return result
|
||||
|
||||
|
||||
def node_entries(cls):
|
||||
spec = cls.INPUT_TYPES()
|
||||
entries = []
|
||||
for section in ("required", "optional"):
|
||||
for name, value in (spec.get(section) or {}).items():
|
||||
spec_type = value[0] if isinstance(value, (list, tuple)) and value else value
|
||||
opts = value[1] if isinstance(value, (list, tuple)) and len(value) > 1 and isinstance(value[1], dict) else {}
|
||||
entries.append((name, spec_type, opts, section == "optional"))
|
||||
return collapse_numbered(entries)
|
||||
|
||||
|
||||
def split_entries(entries):
|
||||
sockets, widgets = [], []
|
||||
for name, spec_type, opts, optional in entries:
|
||||
tname = type_name(spec_type)
|
||||
is_widget = tname == "COMBO" or tname == "FOLD" or (tname in WIDGET_TYPES and not opts.get("forceInput"))
|
||||
(widgets if is_widget else sockets).append((name, spec_type, opts, optional))
|
||||
return sockets, widgets
|
||||
|
||||
|
||||
def widget_value(spec_type, opts):
|
||||
tname = type_name(spec_type)
|
||||
if tname == "COMBO":
|
||||
options = list(spec_type)
|
||||
if is_file_combo(options, opts):
|
||||
return "(choose a file)", ""
|
||||
default = opts.get("default", options[0] if options else "")
|
||||
return (short(default) if default != "" else "—"), f"{len(options)} options" if len(options) > 1 else ""
|
||||
default = opts.get("default", "")
|
||||
if tname == "BOOLEAN":
|
||||
return ("true" if default else "false"), ""
|
||||
if tname in ("INT", "FLOAT"):
|
||||
return fmt_number(default, opts), ""
|
||||
if tname == "STRING":
|
||||
return short(default, 30) if default != "" else "", "multiline" if opts.get("multiline") else ""
|
||||
return short(default), ""
|
||||
|
||||
|
||||
def svg_card(node_id, display, category, cls):
|
||||
entries = node_entries(cls)
|
||||
sockets, widgets = split_entries(entries)
|
||||
return_types = [str(t) for t in getattr(cls, "RETURN_TYPES", ())]
|
||||
return_names = list(getattr(cls, "RETURN_NAMES", None) or return_types)
|
||||
is_list = list(getattr(cls, "OUTPUT_IS_LIST", ()) or [])
|
||||
output_node = getattr(cls, "OUTPUT_NODE", False)
|
||||
|
||||
io_rows = max(len(sockets), len(return_types))
|
||||
multiline_rows = sum(1 for _, t, o, _ in widgets if type_name(t) == "STRING" and o.get("multiline"))
|
||||
widgets_h = len(widgets) * (WIDGET_H + WIDGET_GAP) + multiline_rows * 22
|
||||
height = TITLE_H + 10 + io_rows * ROW_H + (8 if io_rows and widgets else 0) + widgets_h + PAD
|
||||
|
||||
parts = [
|
||||
f'<svg xmlns="http://www.w3.org/2000/svg" width="{W}" height="{height}" viewBox="0 0 {W} {height}" '
|
||||
f'font-family="{FONT}" role="img" aria-label="{escape(display)} node">',
|
||||
f'<rect x="0.5" y="0.5" width="{W - 1}" height="{height - 1}" rx="10" fill="#26282c" stroke="#45484f"/>',
|
||||
f'<path d="M0.5 10.5 a10 10 0 0 1 10 -10 h{W - 21} a10 10 0 0 1 10 10 v{TITLE_H - 10} h-{W - 1} z" fill="#34373d"/>',
|
||||
f'<circle cx="{PAD + 5}" cy="17" r="4.5" fill="{"#F6AD55" if output_node else "#8FD18F"}"/>',
|
||||
f'<text x="{PAD + 16}" y="22" font-size="14" font-weight="600" fill="#EEF0F3">{escape(short(display, 40))}</text>',
|
||||
f'<text x="{PAD + 16}" y="38" font-size="10.5" fill="#9AA0A8" font-family="{MONO}">'
|
||||
f'{escape(node_id)} · {escape(category.replace("NH-Nodes/", ""))}</text>',
|
||||
]
|
||||
|
||||
y = TITLE_H + 10
|
||||
for index in range(io_rows):
|
||||
cy = y + index * ROW_H + ROW_H / 2
|
||||
if index < len(sockets):
|
||||
name, spec_type, opts, optional = sockets[index]
|
||||
tname = type_name(spec_type)
|
||||
color = type_color(tname)
|
||||
parts.append(f'<circle cx="{PAD}" cy="{cy}" r="5" fill="{color}" stroke="#1b1c1f" stroke-width="1.5"/>')
|
||||
label = escape(name) + (' <tspan fill="#7d838c" font-style="italic">(optional)</tspan>' if optional else "")
|
||||
parts.append(f'<text x="{PAD + 12}" y="{cy + 4}" font-size="12" fill="#D7DADF">{label}</text>')
|
||||
if index < len(return_types):
|
||||
tname = return_types[index]
|
||||
color = type_color(tname)
|
||||
name = return_names[index] if index < len(return_names) else tname
|
||||
list_mark = " ≡" if index < len(is_list) and is_list[index] else ""
|
||||
parts.append(f'<text x="{W - PAD - 12}" y="{cy + 4}" font-size="12" fill="#D7DADF" text-anchor="end">'
|
||||
f'{escape(name)}{list_mark}</text>')
|
||||
parts.append(f'<circle cx="{W - PAD}" cy="{cy}" r="5" fill="{color}" stroke="#1b1c1f" stroke-width="1.5"/>')
|
||||
y += io_rows * ROW_H + (8 if io_rows and widgets else 0)
|
||||
|
||||
for name, spec_type, opts, optional in widgets:
|
||||
tname = type_name(spec_type)
|
||||
multiline = tname == "STRING" and opts.get("multiline")
|
||||
h = WIDGET_H + (22 if multiline else 0)
|
||||
parts.append(f'<rect x="{PAD}" y="{y}" width="{W - 2 * PAD}" height="{h}" rx="7" fill="#1d1f22" stroke="#3a3d43"/>')
|
||||
if tname == "FOLD":
|
||||
parts.append(f'<text x="{W / 2}" y="{y + 16}" font-size="11" fill="#9AA0A8" text-anchor="middle" '
|
||||
f'font-style="italic">{escape(name)}</text>')
|
||||
y += h + WIDGET_GAP
|
||||
continue
|
||||
value, hint = widget_value(spec_type, opts)
|
||||
label = escape(name) + (' <tspan fill="#6f747c" font-style="italic">opt</tspan>' if optional else "")
|
||||
if multiline:
|
||||
parts.append(f'<text x="{PAD + 10}" y="{y + 16}" font-size="11.5" fill="#9AA0A8">{label}</text>')
|
||||
parts.append(f'<text x="{PAD + 10}" y="{y + 36}" font-size="11.5" fill="#E6E8EB" font-family="{MONO}">'
|
||||
f'{escape(value) or "…"}</text>')
|
||||
elif tname == "BOOLEAN":
|
||||
on = value == "true"
|
||||
parts.append(f'<text x="{PAD + 10}" y="{y + 16}" font-size="11.5" fill="#9AA0A8">{label}</text>')
|
||||
tx = W - PAD - 40
|
||||
parts.append(f'<rect x="{tx}" y="{y + 6}" width="30" height="13" rx="6.5" fill="{"#4C8BF5" if on else "#4a4d53"}"/>')
|
||||
parts.append(f'<circle cx="{tx + (23 if on else 7)}" cy="{y + 12.5}" r="5" fill="#F5F6F7"/>')
|
||||
else:
|
||||
arrows = tname in ("INT", "FLOAT", "COMBO")
|
||||
if arrows:
|
||||
parts.append(f'<text x="{PAD + 8}" y="{y + 16}" font-size="10" fill="#6f747c">◀</text>')
|
||||
parts.append(f'<text x="{W - PAD - 16}" y="{y + 16}" font-size="10" fill="#6f747c">▶</text>')
|
||||
lx = PAD + (22 if arrows else 10)
|
||||
rx = W - PAD - (22 if arrows else 10)
|
||||
parts.append(f'<text x="{lx}" y="{y + 16}" font-size="11.5" fill="#9AA0A8">{label}</text>')
|
||||
parts.append(f'<text x="{rx}" y="{y + 16}" font-size="11.5" fill="#E6E8EB" text-anchor="end">{escape(value)}</text>')
|
||||
y += h + WIDGET_GAP
|
||||
|
||||
parts.append("</svg>")
|
||||
return "\n".join(parts) + "\n"
|
||||
|
||||
|
||||
def main():
|
||||
pack = importlib.import_module("NH-Nodes")
|
||||
os.makedirs(OUT_DIR, exist_ok=True)
|
||||
index = []
|
||||
for node_id, cls in sorted(pack.NODE_CLASS_MAPPINGS.items()):
|
||||
display = pack.NODE_DISPLAY_NAME_MAPPINGS.get(node_id, node_id)
|
||||
category = getattr(cls, "CATEGORY", "")
|
||||
svg = svg_card(node_id, display, category, cls)
|
||||
with open(os.path.join(OUT_DIR, f"{node_id}.svg"), "w", encoding="utf-8", newline="\n") as handle:
|
||||
handle.write(svg)
|
||||
index.append({"id": node_id, "name": display, "category": category,
|
||||
"deprecated": bool(getattr(cls, "DEPRECATED", False))})
|
||||
with open(os.path.join(OUT_DIR, "index.json"), "w", encoding="utf-8", newline="\n") as handle:
|
||||
json.dump(index, handle, ensure_ascii=False, indent=1)
|
||||
print(f"wrote {len(index)} cards to {OUT_DIR}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,82 @@
|
||||
"""Dump every NH node's live definition (inputs with defaults/ranges/options/tooltips, outputs) to JSON.
|
||||
Run with ComfyUI's Python from the ComfyUI root: python dump_node_spec.py <out.json>
|
||||
"""
|
||||
import importlib
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
|
||||
sys.path[:0] = [os.getcwd(), os.path.join(os.getcwd(), "custom_nodes")]
|
||||
import torch # noqa: E402
|
||||
|
||||
if not torch.cuda.is_available():
|
||||
from comfy.cli_args import args # noqa: E402
|
||||
|
||||
args.cpu = True
|
||||
|
||||
pack = importlib.import_module("NH-Nodes")
|
||||
|
||||
|
||||
FILE_EXTENSIONS = (".png", ".jpg", ".jpeg", ".webp", ".bmp", ".tif", ".tiff", ".gif", ".safetensors", ".ckpt",
|
||||
".pt", ".pth", ".bin", ".gguf", ".onnx", ".sft")
|
||||
|
||||
|
||||
def is_file_combo(options, opts):
|
||||
"""Combos listing files from the user's folders (inputs, models): never publish those names."""
|
||||
if opts.get("image_upload") or opts.get("video_upload"):
|
||||
return True
|
||||
return any(str(option).lower().endswith(FILE_EXTENSIONS) for option in options)
|
||||
|
||||
|
||||
def jsonable(value):
|
||||
try:
|
||||
json.dumps(value)
|
||||
return value
|
||||
except TypeError:
|
||||
return str(value)
|
||||
|
||||
|
||||
spec = []
|
||||
for node_id, cls in sorted(pack.NODE_CLASS_MAPPINGS.items()):
|
||||
inputs = []
|
||||
for section, entries in (cls.INPUT_TYPES() or {}).items():
|
||||
if section == "hidden":
|
||||
continue
|
||||
for name, value in (entries or {}).items():
|
||||
spec_type = value[0] if isinstance(value, (list, tuple)) and value else value
|
||||
opts = value[1] if isinstance(value, (list, tuple)) and len(value) > 1 and isinstance(value[1], dict) else {}
|
||||
entry = {"name": name, "section": section}
|
||||
if isinstance(spec_type, (list, tuple)):
|
||||
entry["type"] = "COMBO"
|
||||
options = [str(o) for o in spec_type]
|
||||
if is_file_combo(options, opts):
|
||||
entry["options"] = ["(files from the user's folder)"]
|
||||
opts = {k: v for k, v in opts.items() if k != "default"}
|
||||
else:
|
||||
entry["options"] = options if len(options) <= 40 else options[:40] + [f"... ({len(options)} total)"]
|
||||
else:
|
||||
entry["type"] = str(spec_type)
|
||||
for key in ("default", "min", "max", "step", "multiline", "tooltip", "forceInput", "lazy"):
|
||||
if key in opts:
|
||||
entry[key] = jsonable(opts[key])
|
||||
inputs.append(entry)
|
||||
names = list(getattr(cls, "RETURN_NAMES", None) or getattr(cls, "RETURN_TYPES", ()))
|
||||
types = [str(t) for t in getattr(cls, "RETURN_TYPES", ())]
|
||||
is_list = list(getattr(cls, "OUTPUT_IS_LIST", ()) or [])
|
||||
outputs = [{"name": n, "type": t, "is_list": bool(is_list[i]) if i < len(is_list) else False}
|
||||
for i, (n, t) in enumerate(zip(names, types))]
|
||||
spec.append({
|
||||
"id": node_id,
|
||||
"name": pack.NODE_DISPLAY_NAME_MAPPINGS.get(node_id, node_id),
|
||||
"category": getattr(cls, "CATEGORY", ""),
|
||||
"module": cls.__module__.replace("NH-Nodes.", ""),
|
||||
"description": getattr(cls, "DESCRIPTION", "") or (cls.__doc__ or "").strip(),
|
||||
"output_node": bool(getattr(cls, "OUTPUT_NODE", False)),
|
||||
"deprecated": bool(getattr(cls, "DEPRECATED", False)),
|
||||
"inputs": inputs,
|
||||
"outputs": outputs,
|
||||
})
|
||||
|
||||
with open(sys.argv[1], "w", encoding="utf-8") as handle:
|
||||
json.dump(spec, handle, ensure_ascii=False, indent=1)
|
||||
print(f"{len(spec)} nodes -> {sys.argv[1]}")
|
||||
|
After Width: | Height: | Size: 147 KiB |
|
After Width: | Height: | Size: 159 KiB |
|
After Width: | Height: | Size: 114 KiB |
|
After Width: | Height: | Size: 136 KiB |
|
After Width: | Height: | Size: 173 KiB |
|
After Width: | Height: | Size: 100 KiB |
|
After Width: | Height: | Size: 131 KiB |
|
After Width: | Height: | Size: 123 KiB |
|
After Width: | Height: | Size: 136 KiB |
|
After Width: | Height: | Size: 179 KiB |
|
After Width: | Height: | Size: 81 KiB |
|
After Width: | Height: | Size: 147 KiB |
|
After Width: | Height: | Size: 77 KiB |
|
After Width: | Height: | Size: 193 KiB |
|
After Width: | Height: | Size: 80 KiB |
|
After Width: | Height: | Size: 84 KiB |
@@ -0,0 +1,42 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="360" height="287" viewBox="0 0 360 287" font-family="Inter, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" role="img" aria-label="NH Agnostic Image Generator node">
|
||||
<rect x="0.5" y="0.5" width="359" height="286" rx="10" fill="#26282c" stroke="#45484f"/>
|
||||
<path d="M0.5 10.5 a10 10 0 0 1 10 -10 h339 a10 10 0 0 1 10 10 v36 h-359 z" fill="#34373d"/>
|
||||
<circle cx="17" cy="17" r="4.5" fill="#8FD18F"/>
|
||||
<text x="28" y="22" font-size="14" font-weight="600" fill="#EEF0F3">NH Agnostic Image Generator</text>
|
||||
<text x="28" y="38" font-size="10.5" fill="#9AA0A8" font-family="'JetBrains Mono', Consolas, 'Courier New', monospace">NH_AgnosticImageGenerator · Image</text>
|
||||
<circle cx="12" cy="67.0" r="5" fill="#64B5F6" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="24" y="71.0" font-size="12" fill="#D7DADF">image</text>
|
||||
<text x="336" y="71.0" font-size="12" fill="#D7DADF" text-anchor="end">agnostic_img</text>
|
||||
<circle cx="348" cy="67.0" r="5" fill="#64B5F6" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<circle cx="12" cy="89.0" r="5" fill="#81C784" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="24" y="93.0" font-size="12" fill="#D7DADF">mask</text>
|
||||
<text x="336" y="93.0" font-size="12" fill="#D7DADF" text-anchor="end">masked_img</text>
|
||||
<circle cx="348" cy="89.0" r="5" fill="#64B5F6" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="336" y="115.0" font-size="12" fill="#D7DADF" text-anchor="end">composite</text>
|
||||
<circle cx="348" cy="111.0" r="5" fill="#64B5F6" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<rect x="12" y="130" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="146" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="146" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="146" font-size="11.5" fill="#9AA0A8">fill_mode</text>
|
||||
<text x="326" y="146" font-size="11.5" fill="#E6E8EB" text-anchor="end">gray</text>
|
||||
<rect x="12" y="159" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="175" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="175" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="175" font-size="11.5" fill="#9AA0A8">blur_radius</text>
|
||||
<text x="326" y="175" font-size="11.5" fill="#E6E8EB" text-anchor="end">65</text>
|
||||
<rect x="12" y="188" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="204" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="204" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="204" font-size="11.5" fill="#9AA0A8">noise_strength</text>
|
||||
<text x="326" y="204" font-size="11.5" fill="#E6E8EB" text-anchor="end">1.00</text>
|
||||
<rect x="12" y="217" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="233" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="233" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="233" font-size="11.5" fill="#9AA0A8">gray_value</text>
|
||||
<text x="326" y="233" font-size="11.5" fill="#E6E8EB" text-anchor="end">0.50</text>
|
||||
<rect x="12" y="246" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="262" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="262" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="262" font-size="11.5" fill="#9AA0A8">feathering</text>
|
||||
<text x="326" y="262" font-size="11.5" fill="#E6E8EB" text-anchor="end">0</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.3 KiB |
@@ -0,0 +1,36 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="360" height="347" viewBox="0 0 360 347" font-family="Inter, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" role="img" aria-label="Any List Split (NH) node">
|
||||
<rect x="0.5" y="0.5" width="359" height="346" rx="10" fill="#26282c" stroke="#45484f"/>
|
||||
<path d="M0.5 10.5 a10 10 0 0 1 10 -10 h339 a10 10 0 0 1 10 10 v36 h-359 z" fill="#34373d"/>
|
||||
<circle cx="17" cy="17" r="4.5" fill="#8FD18F"/>
|
||||
<text x="28" y="22" font-size="14" font-weight="600" fill="#EEF0F3">Any List Split (NH)</text>
|
||||
<text x="28" y="38" font-size="10.5" fill="#9AA0A8" font-family="'JetBrains Mono', Consolas, 'Courier New', monospace">NH_AnyListSplit · Batch</text>
|
||||
<circle cx="12" cy="67.0" r="5" fill="#CFD8DC" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="24" y="71.0" font-size="12" fill="#D7DADF">items</text>
|
||||
<text x="336" y="71.0" font-size="12" fill="#D7DADF" text-anchor="end">All ≡</text>
|
||||
<circle cx="348" cy="67.0" r="5" fill="#CFD8DC" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="336" y="93.0" font-size="12" fill="#D7DADF" text-anchor="end">index 1</text>
|
||||
<circle cx="348" cy="89.0" r="5" fill="#CFD8DC" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="336" y="115.0" font-size="12" fill="#D7DADF" text-anchor="end">index 2</text>
|
||||
<circle cx="348" cy="111.0" r="5" fill="#CFD8DC" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="336" y="137.0" font-size="12" fill="#D7DADF" text-anchor="end">index 3</text>
|
||||
<circle cx="348" cy="133.0" r="5" fill="#CFD8DC" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="336" y="159.0" font-size="12" fill="#D7DADF" text-anchor="end">index 4</text>
|
||||
<circle cx="348" cy="155.0" r="5" fill="#CFD8DC" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="336" y="181.0" font-size="12" fill="#D7DADF" text-anchor="end">index 5</text>
|
||||
<circle cx="348" cy="177.0" r="5" fill="#CFD8DC" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="336" y="203.0" font-size="12" fill="#D7DADF" text-anchor="end">index 6</text>
|
||||
<circle cx="348" cy="199.0" r="5" fill="#CFD8DC" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="336" y="225.0" font-size="12" fill="#D7DADF" text-anchor="end">index 7</text>
|
||||
<circle cx="348" cy="221.0" r="5" fill="#CFD8DC" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="336" y="247.0" font-size="12" fill="#D7DADF" text-anchor="end">index 8</text>
|
||||
<circle cx="348" cy="243.0" r="5" fill="#CFD8DC" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="336" y="269.0" font-size="12" fill="#D7DADF" text-anchor="end">index 9</text>
|
||||
<circle cx="348" cy="265.0" r="5" fill="#CFD8DC" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="336" y="291.0" font-size="12" fill="#D7DADF" text-anchor="end">index 10</text>
|
||||
<circle cx="348" cy="287.0" r="5" fill="#CFD8DC" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<rect x="12" y="306" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="322" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="322" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="322" font-size="11.5" fill="#9AA0A8">index_count</text>
|
||||
<text x="326" y="322" font-size="11.5" fill="#E6E8EB" text-anchor="end">4</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.0 KiB |
@@ -0,0 +1,26 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="360" height="237" viewBox="0 0 360 237" font-family="Inter, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" role="img" aria-label="Any Branch Switch (NH) node">
|
||||
<rect x="0.5" y="0.5" width="359" height="236" rx="10" fill="#26282c" stroke="#45484f"/>
|
||||
<path d="M0.5 10.5 a10 10 0 0 1 10 -10 h339 a10 10 0 0 1 10 10 v36 h-359 z" fill="#34373d"/>
|
||||
<circle cx="17" cy="17" r="4.5" fill="#8FD18F"/>
|
||||
<text x="28" y="22" font-size="14" font-weight="600" fill="#EEF0F3">Any Branch Switch (NH)</text>
|
||||
<text x="28" y="38" font-size="10.5" fill="#9AA0A8" font-family="'JetBrains Mono', Consolas, 'Courier New', monospace">NH_AnySwitch · Logic</text>
|
||||
<circle cx="12" cy="67.0" r="5" fill="#CFD8DC" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="24" y="71.0" font-size="12" fill="#D7DADF">input</text>
|
||||
<text x="336" y="71.0" font-size="12" fill="#D7DADF" text-anchor="end">output 0</text>
|
||||
<circle cx="348" cy="67.0" r="5" fill="#CFD8DC" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="336" y="93.0" font-size="12" fill="#D7DADF" text-anchor="end">output 1</text>
|
||||
<circle cx="348" cy="89.0" r="5" fill="#CFD8DC" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="336" y="115.0" font-size="12" fill="#D7DADF" text-anchor="end">output 2</text>
|
||||
<circle cx="348" cy="111.0" r="5" fill="#CFD8DC" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="336" y="137.0" font-size="12" fill="#D7DADF" text-anchor="end">output 3</text>
|
||||
<circle cx="348" cy="133.0" r="5" fill="#CFD8DC" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="336" y="159.0" font-size="12" fill="#D7DADF" text-anchor="end">output 4</text>
|
||||
<circle cx="348" cy="155.0" r="5" fill="#CFD8DC" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="336" y="181.0" font-size="12" fill="#D7DADF" text-anchor="end">index</text>
|
||||
<circle cx="348" cy="177.0" r="5" fill="#9CC2E5" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<rect x="12" y="196" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="212" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="212" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="212" font-size="11.5" fill="#9AA0A8">index</text>
|
||||
<text x="326" y="212" font-size="11.5" fill="#E6E8EB" text-anchor="end">0</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.2 KiB |
@@ -0,0 +1,19 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="360" height="149" viewBox="0 0 360 149" font-family="Inter, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" role="img" aria-label="Any Switch (NH) node">
|
||||
<rect x="0.5" y="0.5" width="359" height="148" rx="10" fill="#26282c" stroke="#45484f"/>
|
||||
<path d="M0.5 10.5 a10 10 0 0 1 10 -10 h339 a10 10 0 0 1 10 10 v36 h-359 z" fill="#34373d"/>
|
||||
<circle cx="17" cy="17" r="4.5" fill="#8FD18F"/>
|
||||
<text x="28" y="22" font-size="14" font-weight="600" fill="#EEF0F3">Any Switch (NH)</text>
|
||||
<text x="28" y="38" font-size="10.5" fill="#9AA0A8" font-family="'JetBrains Mono', Consolas, 'Courier New', monospace">NH_AnySwitchBoolean · Logic</text>
|
||||
<circle cx="12" cy="67.0" r="5" fill="#CFD8DC" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="24" y="71.0" font-size="12" fill="#D7DADF">input_true</text>
|
||||
<text x="336" y="71.0" font-size="12" fill="#D7DADF" text-anchor="end">result</text>
|
||||
<circle cx="348" cy="67.0" r="5" fill="#CFD8DC" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<circle cx="12" cy="89.0" r="5" fill="#CFD8DC" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="24" y="93.0" font-size="12" fill="#D7DADF">input_false</text>
|
||||
<text x="336" y="93.0" font-size="12" fill="#D7DADF" text-anchor="end">condition</text>
|
||||
<circle cx="348" cy="89.0" r="5" fill="#F48FB1" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<rect x="12" y="108" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="22" y="124" font-size="11.5" fill="#9AA0A8">condition</text>
|
||||
<rect x="308" y="114" width="30" height="13" rx="6.5" fill="#4C8BF5"/>
|
||||
<circle cx="331" cy="120.5" r="5" fill="#F5F6F7"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.6 KiB |
@@ -0,0 +1,28 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="360" height="207" viewBox="0 0 360 207" font-family="Inter, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" role="img" aria-label="Batch Index (NH) node">
|
||||
<rect x="0.5" y="0.5" width="359" height="206" rx="10" fill="#26282c" stroke="#45484f"/>
|
||||
<path d="M0.5 10.5 a10 10 0 0 1 10 -10 h339 a10 10 0 0 1 10 10 v36 h-359 z" fill="#34373d"/>
|
||||
<circle cx="17" cy="17" r="4.5" fill="#8FD18F"/>
|
||||
<text x="28" y="22" font-size="14" font-weight="600" fill="#EEF0F3">Batch Index (NH)</text>
|
||||
<text x="28" y="38" font-size="10.5" fill="#9AA0A8" font-family="'JetBrains Mono', Consolas, 'Courier New', monospace">NH_BatchIndex · Batch</text>
|
||||
<circle cx="12" cy="67.0" r="5" fill="#64B5F6" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="24" y="71.0" font-size="12" fill="#D7DADF">batch</text>
|
||||
<text x="336" y="71.0" font-size="12" fill="#D7DADF" text-anchor="end">result</text>
|
||||
<circle cx="348" cy="67.0" r="5" fill="#64B5F6" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="336" y="93.0" font-size="12" fill="#D7DADF" text-anchor="end">original_count</text>
|
||||
<circle cx="348" cy="89.0" r="5" fill="#9CC2E5" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<rect x="12" y="108" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="124" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="124" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="124" font-size="11.5" fill="#9AA0A8">index</text>
|
||||
<text x="326" y="124" font-size="11.5" fill="#E6E8EB" text-anchor="end">0</text>
|
||||
<rect x="12" y="137" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="153" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="153" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="153" font-size="11.5" fill="#9AA0A8">end_index <tspan fill="#6f747c" font-style="italic">opt</tspan></text>
|
||||
<text x="326" y="153" font-size="11.5" fill="#E6E8EB" text-anchor="end">-1</text>
|
||||
<rect x="12" y="166" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="182" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="182" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="182" font-size="11.5" fill="#9AA0A8">step <tspan fill="#6f747c" font-style="italic">opt</tspan></text>
|
||||
<text x="326" y="182" font-size="11.5" fill="#E6E8EB" text-anchor="end">1</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.3 KiB |
@@ -0,0 +1,22 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="360" height="171" viewBox="0 0 360 171" font-family="Inter, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" role="img" aria-label="Batch Merge (NH) node">
|
||||
<rect x="0.5" y="0.5" width="359" height="170" rx="10" fill="#26282c" stroke="#45484f"/>
|
||||
<path d="M0.5 10.5 a10 10 0 0 1 10 -10 h339 a10 10 0 0 1 10 10 v36 h-359 z" fill="#34373d"/>
|
||||
<circle cx="17" cy="17" r="4.5" fill="#8FD18F"/>
|
||||
<text x="28" y="22" font-size="14" font-weight="600" fill="#EEF0F3">Batch Merge (NH)</text>
|
||||
<text x="28" y="38" font-size="10.5" fill="#9AA0A8" font-family="'JetBrains Mono', Consolas, 'Courier New', monospace">NH_BatchMerge · Batch</text>
|
||||
<circle cx="12" cy="67.0" r="5" fill="#64B5F6" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="24" y="71.0" font-size="12" fill="#D7DADF">batch_a</text>
|
||||
<text x="336" y="71.0" font-size="12" fill="#D7DADF" text-anchor="end">result</text>
|
||||
<circle cx="348" cy="67.0" r="5" fill="#64B5F6" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<circle cx="12" cy="89.0" r="5" fill="#64B5F6" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="24" y="93.0" font-size="12" fill="#D7DADF">batch_b <tspan fill="#7d838c" font-style="italic">(optional)</tspan></text>
|
||||
<text x="336" y="93.0" font-size="12" fill="#D7DADF" text-anchor="end">count</text>
|
||||
<circle cx="348" cy="89.0" r="5" fill="#9CC2E5" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<circle cx="12" cy="111.0" r="5" fill="#64B5F6" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="24" y="115.0" font-size="12" fill="#D7DADF">batch_c <tspan fill="#7d838c" font-style="italic">(optional)</tspan></text>
|
||||
<rect x="12" y="130" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="146" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="146" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="146" font-size="11.5" fill="#9AA0A8">resize_mode <tspan fill="#6f747c" font-style="italic">opt</tspan></text>
|
||||
<text x="326" y="146" font-size="11.5" fill="#E6E8EB" text-anchor="end">stretch</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.0 KiB |
@@ -0,0 +1,13 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="360" height="127" viewBox="0 0 360 127" font-family="Inter, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" role="img" aria-label="Boolean Switch (NH) node">
|
||||
<rect x="0.5" y="0.5" width="359" height="126" rx="10" fill="#26282c" stroke="#45484f"/>
|
||||
<path d="M0.5 10.5 a10 10 0 0 1 10 -10 h339 a10 10 0 0 1 10 10 v36 h-359 z" fill="#34373d"/>
|
||||
<circle cx="17" cy="17" r="4.5" fill="#8FD18F"/>
|
||||
<text x="28" y="22" font-size="14" font-weight="600" fill="#EEF0F3">Boolean Switch (NH)</text>
|
||||
<text x="28" y="38" font-size="10.5" fill="#9AA0A8" font-family="'JetBrains Mono', Consolas, 'Courier New', monospace">NH_BooleanSwitch · Utils</text>
|
||||
<text x="336" y="71.0" font-size="12" fill="#D7DADF" text-anchor="end">boolean</text>
|
||||
<circle cx="348" cy="67.0" r="5" fill="#F48FB1" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<rect x="12" y="86" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="22" y="102" font-size="11.5" fill="#9AA0A8">boolean_switch</text>
|
||||
<rect x="308" y="92" width="30" height="13" rx="6.5" fill="#4C8BF5"/>
|
||||
<circle cx="331" cy="98.5" r="5" fill="#F5F6F7"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1,55 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="360" height="432" viewBox="0 0 360 432" font-family="Inter, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" role="img" aria-label="Load Clip Index (NH) node">
|
||||
<rect x="0.5" y="0.5" width="359" height="431" rx="10" fill="#26282c" stroke="#45484f"/>
|
||||
<path d="M0.5 10.5 a10 10 0 0 1 10 -10 h339 a10 10 0 0 1 10 10 v36 h-359 z" fill="#34373d"/>
|
||||
<circle cx="17" cy="17" r="4.5" fill="#8FD18F"/>
|
||||
<text x="28" y="22" font-size="14" font-weight="600" fill="#EEF0F3">Load Clip Index (NH)</text>
|
||||
<text x="28" y="38" font-size="10.5" fill="#9AA0A8" font-family="'JetBrains Mono', Consolas, 'Courier New', monospace">NH_ClipIndex · Loaders</text>
|
||||
<text x="336" y="71.0" font-size="12" fill="#D7DADF" text-anchor="end">clip</text>
|
||||
<circle cx="348" cy="67.0" r="5" fill="#FFD500" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="336" y="93.0" font-size="12" fill="#D7DADF" text-anchor="end">filename</text>
|
||||
<circle cx="348" cy="89.0" r="5" fill="#C5E1A5" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="336" y="115.0" font-size="12" fill="#D7DADF" text-anchor="end">index</text>
|
||||
<circle cx="348" cy="111.0" r="5" fill="#9CC2E5" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<rect x="12" y="130" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="146" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="146" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="146" font-size="11.5" fill="#9AA0A8">select_mode</text>
|
||||
<text x="326" y="146" font-size="11.5" fill="#E6E8EB" text-anchor="end">index</text>
|
||||
<rect x="12" y="159" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="175" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="175" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="175" font-size="11.5" fill="#9AA0A8">type</text>
|
||||
<text x="326" y="175" font-size="11.5" fill="#E6E8EB" text-anchor="end">stable_diffusion</text>
|
||||
<rect x="12" y="188" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="204" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="204" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="204" font-size="11.5" fill="#9AA0A8">index</text>
|
||||
<text x="326" y="204" font-size="11.5" fill="#E6E8EB" text-anchor="end">1</text>
|
||||
<rect x="12" y="217" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="233" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="233" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="233" font-size="11.5" fill="#9AA0A8">clip_count</text>
|
||||
<text x="326" y="233" font-size="11.5" fill="#E6E8EB" text-anchor="end">5</text>
|
||||
<rect x="12" y="246" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="262" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="262" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="262" font-size="11.5" fill="#9AA0A8">clip_1 <tspan fill="#6f747c" font-style="italic">opt</tspan></text>
|
||||
<text x="326" y="262" font-size="11.5" fill="#E6E8EB" text-anchor="end">(choose a file)</text>
|
||||
<rect x="12" y="275" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="22" y="291" font-size="11.5" fill="#9AA0A8">enable_1 <tspan fill="#6f747c" font-style="italic">opt</tspan></text>
|
||||
<rect x="308" y="281" width="30" height="13" rx="6.5" fill="#4a4d53"/>
|
||||
<circle cx="315" cy="287.5" r="5" fill="#F5F6F7"/>
|
||||
<rect x="12" y="304" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="320" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="320" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="320" font-size="11.5" fill="#9AA0A8">clip_2 <tspan fill="#6f747c" font-style="italic">opt</tspan></text>
|
||||
<text x="326" y="320" font-size="11.5" fill="#E6E8EB" text-anchor="end">(choose a file)</text>
|
||||
<rect x="12" y="333" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="22" y="349" font-size="11.5" fill="#9AA0A8">enable_2 <tspan fill="#6f747c" font-style="italic">opt</tspan></text>
|
||||
<rect x="308" y="339" width="30" height="13" rx="6.5" fill="#4a4d53"/>
|
||||
<circle cx="315" cy="345.5" r="5" fill="#F5F6F7"/>
|
||||
<rect x="12" y="362" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="180.0" y="378" font-size="11" fill="#9AA0A8" text-anchor="middle" font-style="italic">clip_3 … clip_64</text>
|
||||
<rect x="12" y="391" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="180.0" y="407" font-size="11" fill="#9AA0A8" text-anchor="middle" font-style="italic">enable_3 … enable_64</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.5 KiB |
@@ -0,0 +1,27 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="360" height="200" viewBox="0 0 360 200" font-family="Inter, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" role="img" aria-label="Compare (NH) node">
|
||||
<rect x="0.5" y="0.5" width="359" height="199" rx="10" fill="#26282c" stroke="#45484f"/>
|
||||
<path d="M0.5 10.5 a10 10 0 0 1 10 -10 h339 a10 10 0 0 1 10 10 v36 h-359 z" fill="#34373d"/>
|
||||
<circle cx="17" cy="17" r="4.5" fill="#8FD18F"/>
|
||||
<text x="28" y="22" font-size="14" font-weight="600" fill="#EEF0F3">Compare (NH)</text>
|
||||
<text x="28" y="38" font-size="10.5" fill="#9AA0A8" font-family="'JetBrains Mono', Consolas, 'Courier New', monospace">NH_Compare · Logic</text>
|
||||
<circle cx="12" cy="67.0" r="5" fill="#CFD8DC" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="24" y="71.0" font-size="12" fill="#D7DADF">a</text>
|
||||
<text x="336" y="71.0" font-size="12" fill="#D7DADF" text-anchor="end">result</text>
|
||||
<circle cx="348" cy="67.0" r="5" fill="#F48FB1" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<circle cx="12" cy="89.0" r="5" fill="#CFD8DC" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="24" y="93.0" font-size="12" fill="#D7DADF">b</text>
|
||||
<text x="336" y="93.0" font-size="12" fill="#D7DADF" text-anchor="end">a_passthrough</text>
|
||||
<circle cx="348" cy="89.0" r="5" fill="#CFD8DC" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="336" y="115.0" font-size="12" fill="#D7DADF" text-anchor="end">b_passthrough</text>
|
||||
<circle cx="348" cy="111.0" r="5" fill="#CFD8DC" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<rect x="12" y="130" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="146" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="146" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="146" font-size="11.5" fill="#9AA0A8">op</text>
|
||||
<text x="326" y="146" font-size="11.5" fill="#E6E8EB" text-anchor="end">is</text>
|
||||
<rect x="12" y="159" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="175" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="175" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="175" font-size="11.5" fill="#9AA0A8">type_cast <tspan fill="#6f747c" font-style="italic">opt</tspan></text>
|
||||
<text x="326" y="175" font-size="11.5" fill="#E6E8EB" text-anchor="end">auto</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.2 KiB |
@@ -0,0 +1,34 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="360" height="280" viewBox="0 0 360 280" font-family="Inter, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" role="img" aria-label="Counter (NH) node">
|
||||
<rect x="0.5" y="0.5" width="359" height="279" rx="10" fill="#26282c" stroke="#45484f"/>
|
||||
<path d="M0.5 10.5 a10 10 0 0 1 10 -10 h339 a10 10 0 0 1 10 10 v36 h-359 z" fill="#34373d"/>
|
||||
<circle cx="17" cy="17" r="4.5" fill="#8FD18F"/>
|
||||
<text x="28" y="22" font-size="14" font-weight="600" fill="#EEF0F3">Counter (NH)</text>
|
||||
<text x="28" y="38" font-size="10.5" fill="#9AA0A8" font-family="'JetBrains Mono', Consolas, 'Courier New', monospace">NH_Counter · Batch</text>
|
||||
<text x="336" y="71.0" font-size="12" fill="#D7DADF" text-anchor="end">current</text>
|
||||
<circle cx="348" cy="67.0" r="5" fill="#9CC2E5" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="336" y="93.0" font-size="12" fill="#D7DADF" text-anchor="end">is_done</text>
|
||||
<circle cx="348" cy="89.0" r="5" fill="#F48FB1" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="336" y="115.0" font-size="12" fill="#D7DADF" text-anchor="end">progress</text>
|
||||
<circle cx="348" cy="111.0" r="5" fill="#9CC2E5" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="336" y="137.0" font-size="12" fill="#D7DADF" text-anchor="end">remaining</text>
|
||||
<circle cx="348" cy="133.0" r="5" fill="#9CC2E5" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<rect x="12" y="152" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="168" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="168" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="168" font-size="11.5" fill="#9AA0A8">start</text>
|
||||
<text x="326" y="168" font-size="11.5" fill="#E6E8EB" text-anchor="end">0</text>
|
||||
<rect x="12" y="181" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="197" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="197" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="197" font-size="11.5" fill="#9AA0A8">step</text>
|
||||
<text x="326" y="197" font-size="11.5" fill="#E6E8EB" text-anchor="end">1</text>
|
||||
<rect x="12" y="210" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="226" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="226" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="226" font-size="11.5" fill="#9AA0A8">max_value</text>
|
||||
<text x="326" y="226" font-size="11.5" fill="#E6E8EB" text-anchor="end">9</text>
|
||||
<rect x="12" y="239" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="22" y="255" font-size="11.5" fill="#9AA0A8">reset</text>
|
||||
<rect x="308" y="245" width="30" height="13" rx="6.5" fill="#4a4d53"/>
|
||||
<circle cx="315" cy="251.5" r="5" fill="#F5F6F7"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.7 KiB |
@@ -0,0 +1,55 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="360" height="432" viewBox="0 0 360 432" font-family="Inter, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" role="img" aria-label="Load Diffusion Model Index (NH) node">
|
||||
<rect x="0.5" y="0.5" width="359" height="431" rx="10" fill="#26282c" stroke="#45484f"/>
|
||||
<path d="M0.5 10.5 a10 10 0 0 1 10 -10 h339 a10 10 0 0 1 10 10 v36 h-359 z" fill="#34373d"/>
|
||||
<circle cx="17" cy="17" r="4.5" fill="#8FD18F"/>
|
||||
<text x="28" y="22" font-size="14" font-weight="600" fill="#EEF0F3">Load Diffusion Model Index (NH)</text>
|
||||
<text x="28" y="38" font-size="10.5" fill="#9AA0A8" font-family="'JetBrains Mono', Consolas, 'Courier New', monospace">NH_DiffusionModelIndex · Loaders</text>
|
||||
<text x="336" y="71.0" font-size="12" fill="#D7DADF" text-anchor="end">model</text>
|
||||
<circle cx="348" cy="67.0" r="5" fill="#B39DDB" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="336" y="93.0" font-size="12" fill="#D7DADF" text-anchor="end">filename</text>
|
||||
<circle cx="348" cy="89.0" r="5" fill="#C5E1A5" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="336" y="115.0" font-size="12" fill="#D7DADF" text-anchor="end">index</text>
|
||||
<circle cx="348" cy="111.0" r="5" fill="#9CC2E5" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<rect x="12" y="130" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="146" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="146" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="146" font-size="11.5" fill="#9AA0A8">select_mode</text>
|
||||
<text x="326" y="146" font-size="11.5" fill="#E6E8EB" text-anchor="end">index</text>
|
||||
<rect x="12" y="159" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="175" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="175" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="175" font-size="11.5" fill="#9AA0A8">index</text>
|
||||
<text x="326" y="175" font-size="11.5" fill="#E6E8EB" text-anchor="end">1</text>
|
||||
<rect x="12" y="188" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="204" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="204" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="204" font-size="11.5" fill="#9AA0A8">model_count</text>
|
||||
<text x="326" y="204" font-size="11.5" fill="#E6E8EB" text-anchor="end">5</text>
|
||||
<rect x="12" y="217" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="233" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="233" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="233" font-size="11.5" fill="#9AA0A8">weight_dtype</text>
|
||||
<text x="326" y="233" font-size="11.5" fill="#E6E8EB" text-anchor="end">default</text>
|
||||
<rect x="12" y="246" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="262" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="262" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="262" font-size="11.5" fill="#9AA0A8">diffusion_model_1 <tspan fill="#6f747c" font-style="italic">opt</tspan></text>
|
||||
<text x="326" y="262" font-size="11.5" fill="#E6E8EB" text-anchor="end">—</text>
|
||||
<rect x="12" y="275" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="22" y="291" font-size="11.5" fill="#9AA0A8">enable_1 <tspan fill="#6f747c" font-style="italic">opt</tspan></text>
|
||||
<rect x="308" y="281" width="30" height="13" rx="6.5" fill="#4a4d53"/>
|
||||
<circle cx="315" cy="287.5" r="5" fill="#F5F6F7"/>
|
||||
<rect x="12" y="304" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="320" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="320" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="320" font-size="11.5" fill="#9AA0A8">diffusion_model_2 <tspan fill="#6f747c" font-style="italic">opt</tspan></text>
|
||||
<text x="326" y="320" font-size="11.5" fill="#E6E8EB" text-anchor="end">—</text>
|
||||
<rect x="12" y="333" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="22" y="349" font-size="11.5" fill="#9AA0A8">enable_2 <tspan fill="#6f747c" font-style="italic">opt</tspan></text>
|
||||
<rect x="308" y="339" width="30" height="13" rx="6.5" fill="#4a4d53"/>
|
||||
<circle cx="315" cy="345.5" r="5" fill="#F5F6F7"/>
|
||||
<rect x="12" y="362" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="180.0" y="378" font-size="11" fill="#9AA0A8" text-anchor="middle" font-style="italic">diffusion_model_3 … diffusion_model_64</text>
|
||||
<rect x="12" y="391" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="180.0" y="407" font-size="11" fill="#9AA0A8" text-anchor="middle" font-style="italic">enable_3 … enable_64</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.5 KiB |
@@ -0,0 +1,43 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="360" height="316" viewBox="0 0 360 316" font-family="Inter, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" role="img" aria-label="Garment Segment (NH) node">
|
||||
<rect x="0.5" y="0.5" width="359" height="315" rx="10" fill="#26282c" stroke="#45484f"/>
|
||||
<path d="M0.5 10.5 a10 10 0 0 1 10 -10 h339 a10 10 0 0 1 10 10 v36 h-359 z" fill="#34373d"/>
|
||||
<circle cx="17" cy="17" r="4.5" fill="#8FD18F"/>
|
||||
<text x="28" y="22" font-size="14" font-weight="600" fill="#EEF0F3">Garment Segment (NH)</text>
|
||||
<text x="28" y="38" font-size="10.5" fill="#9AA0A8" font-family="'JetBrains Mono', Consolas, 'Courier New', monospace">NH_GarmentSegment · Vision</text>
|
||||
<circle cx="12" cy="67.0" r="5" fill="#64B5F6" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="24" y="71.0" font-size="12" fill="#D7DADF">image</text>
|
||||
<text x="336" y="71.0" font-size="12" fill="#D7DADF" text-anchor="end">mask</text>
|
||||
<circle cx="348" cy="67.0" r="5" fill="#81C784" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="336" y="93.0" font-size="12" fill="#D7DADF" text-anchor="end">cutout_rgba</text>
|
||||
<circle cx="348" cy="89.0" r="5" fill="#64B5F6" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="336" y="115.0" font-size="12" fill="#D7DADF" text-anchor="end">mask_image</text>
|
||||
<circle cx="348" cy="111.0" r="5" fill="#64B5F6" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<rect x="12" y="130" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="146" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="146" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="146" font-size="11.5" fill="#9AA0A8">part</text>
|
||||
<text x="326" y="146" font-size="11.5" fill="#E6E8EB" text-anchor="end">upper_garment</text>
|
||||
<rect x="12" y="159" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="22" y="175" font-size="11.5" fill="#9AA0A8">attach_parts</text>
|
||||
<rect x="308" y="165" width="30" height="13" rx="6.5" fill="#4C8BF5"/>
|
||||
<circle cx="331" cy="171.5" r="5" fill="#F5F6F7"/>
|
||||
<rect x="12" y="188" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="204" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="204" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="204" font-size="11.5" fill="#9AA0A8">mask_expand</text>
|
||||
<text x="326" y="204" font-size="11.5" fill="#E6E8EB" text-anchor="end">0</text>
|
||||
<rect x="12" y="217" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="233" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="233" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="233" font-size="11.5" fill="#9AA0A8">mask_blur</text>
|
||||
<text x="326" y="233" font-size="11.5" fill="#E6E8EB" text-anchor="end">0</text>
|
||||
<rect x="12" y="246" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="22" y="262" font-size="11.5" fill="#9AA0A8">fill_holes</text>
|
||||
<rect x="308" y="252" width="30" height="13" rx="6.5" fill="#4a4d53"/>
|
||||
<circle cx="315" cy="258.5" r="5" fill="#F5F6F7"/>
|
||||
<rect x="12" y="275" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="291" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="291" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="291" font-size="11.5" fill="#9AA0A8">device</text>
|
||||
<text x="326" y="291" font-size="11.5" fill="#E6E8EB" text-anchor="end">cuda</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.3 KiB |
@@ -0,0 +1,15 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="360" height="127" viewBox="0 0 360 127" font-family="Inter, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" role="img" aria-label="Gate Switch (NH) node">
|
||||
<rect x="0.5" y="0.5" width="359" height="126" rx="10" fill="#26282c" stroke="#45484f"/>
|
||||
<path d="M0.5 10.5 a10 10 0 0 1 10 -10 h339 a10 10 0 0 1 10 10 v36 h-359 z" fill="#34373d"/>
|
||||
<circle cx="17" cy="17" r="4.5" fill="#8FD18F"/>
|
||||
<text x="28" y="22" font-size="14" font-weight="600" fill="#EEF0F3">Gate Switch (NH)</text>
|
||||
<text x="28" y="38" font-size="10.5" fill="#9AA0A8" font-family="'JetBrains Mono', Consolas, 'Courier New', monospace">NH_GateSwitch · Logic</text>
|
||||
<circle cx="12" cy="67.0" r="5" fill="#CFD8DC" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="24" y="71.0" font-size="12" fill="#D7DADF">input</text>
|
||||
<text x="336" y="71.0" font-size="12" fill="#D7DADF" text-anchor="end">output</text>
|
||||
<circle cx="348" cy="67.0" r="5" fill="#CFD8DC" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<rect x="12" y="86" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="22" y="102" font-size="11.5" fill="#9AA0A8">enabled</text>
|
||||
<rect x="308" y="92" width="30" height="13" rx="6.5" fill="#4C8BF5"/>
|
||||
<circle cx="331" cy="98.5" r="5" fill="#F5F6F7"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
@@ -0,0 +1,19 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="360" height="149" viewBox="0 0 360 149" font-family="Inter, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" role="img" aria-label="If/Else (NH) node">
|
||||
<rect x="0.5" y="0.5" width="359" height="148" rx="10" fill="#26282c" stroke="#45484f"/>
|
||||
<path d="M0.5 10.5 a10 10 0 0 1 10 -10 h339 a10 10 0 0 1 10 10 v36 h-359 z" fill="#34373d"/>
|
||||
<circle cx="17" cy="17" r="4.5" fill="#8FD18F"/>
|
||||
<text x="28" y="22" font-size="14" font-weight="600" fill="#EEF0F3">If/Else (NH)</text>
|
||||
<text x="28" y="38" font-size="10.5" fill="#9AA0A8" font-family="'JetBrains Mono', Consolas, 'Courier New', monospace">NH_IfElse · Logic</text>
|
||||
<circle cx="12" cy="67.0" r="5" fill="#CFD8DC" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="24" y="71.0" font-size="12" fill="#D7DADF">if_true</text>
|
||||
<text x="336" y="71.0" font-size="12" fill="#D7DADF" text-anchor="end">result</text>
|
||||
<circle cx="348" cy="67.0" r="5" fill="#CFD8DC" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<circle cx="12" cy="89.0" r="5" fill="#CFD8DC" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="24" y="93.0" font-size="12" fill="#D7DADF">if_false</text>
|
||||
<text x="336" y="93.0" font-size="12" fill="#D7DADF" text-anchor="end">condition_out</text>
|
||||
<circle cx="348" cy="89.0" r="5" fill="#F48FB1" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<rect x="12" y="108" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="22" y="124" font-size="11.5" fill="#9AA0A8">condition</text>
|
||||
<rect x="308" y="114" width="30" height="13" rx="6.5" fill="#4C8BF5"/>
|
||||
<circle cx="331" cy="120.5" r="5" fill="#F5F6F7"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.6 KiB |
@@ -0,0 +1,33 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="360" height="236" viewBox="0 0 360 236" font-family="Inter, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" role="img" aria-label="Image Compare (NH) node">
|
||||
<rect x="0.5" y="0.5" width="359" height="235" rx="10" fill="#26282c" stroke="#45484f"/>
|
||||
<path d="M0.5 10.5 a10 10 0 0 1 10 -10 h339 a10 10 0 0 1 10 10 v36 h-359 z" fill="#34373d"/>
|
||||
<circle cx="17" cy="17" r="4.5" fill="#8FD18F"/>
|
||||
<text x="28" y="22" font-size="14" font-weight="600" fill="#EEF0F3">Image Compare (NH)</text>
|
||||
<text x="28" y="38" font-size="10.5" fill="#9AA0A8" font-family="'JetBrains Mono', Consolas, 'Courier New', monospace">NH_ImageCompare · Image</text>
|
||||
<circle cx="12" cy="67.0" r="5" fill="#64B5F6" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="24" y="71.0" font-size="12" fill="#D7DADF">image_a</text>
|
||||
<text x="336" y="71.0" font-size="12" fill="#D7DADF" text-anchor="end">image</text>
|
||||
<circle cx="348" cy="67.0" r="5" fill="#64B5F6" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<circle cx="12" cy="89.0" r="5" fill="#64B5F6" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="24" y="93.0" font-size="12" fill="#D7DADF">image_b</text>
|
||||
<rect x="12" y="108" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="124" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="124" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="124" font-size="11.5" fill="#9AA0A8">compare_mode</text>
|
||||
<text x="326" y="124" font-size="11.5" fill="#E6E8EB" text-anchor="end">side_by_side</text>
|
||||
<rect x="12" y="137" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="153" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="153" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="153" font-size="11.5" fill="#9AA0A8">split_position</text>
|
||||
<text x="326" y="153" font-size="11.5" fill="#E6E8EB" text-anchor="end">0.50</text>
|
||||
<rect x="12" y="166" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="182" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="182" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="182" font-size="11.5" fill="#9AA0A8">overlay_opacity</text>
|
||||
<text x="326" y="182" font-size="11.5" fill="#E6E8EB" text-anchor="end">0.50</text>
|
||||
<rect x="12" y="195" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="211" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="211" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="211" font-size="11.5" fill="#9AA0A8">resize_method</text>
|
||||
<text x="326" y="211" font-size="11.5" fill="#E6E8EB" text-anchor="end">lanczos</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.6 KiB |
@@ -0,0 +1,60 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="360" height="425" viewBox="0 0 360 425" font-family="Inter, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" role="img" aria-label="Image Grid Composite (NH) node">
|
||||
<rect x="0.5" y="0.5" width="359" height="424" rx="10" fill="#26282c" stroke="#45484f"/>
|
||||
<path d="M0.5 10.5 a10 10 0 0 1 10 -10 h339 a10 10 0 0 1 10 10 v36 h-359 z" fill="#34373d"/>
|
||||
<circle cx="17" cy="17" r="4.5" fill="#8FD18F"/>
|
||||
<text x="28" y="22" font-size="14" font-weight="600" fill="#EEF0F3">Image Grid Composite (NH)</text>
|
||||
<text x="28" y="38" font-size="10.5" fill="#9AA0A8" font-family="'JetBrains Mono', Consolas, 'Courier New', monospace">NH_ImageGridComposite · Image</text>
|
||||
<circle cx="12" cy="67.0" r="5" fill="#64B5F6" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="24" y="71.0" font-size="12" fill="#D7DADF">images</text>
|
||||
<text x="336" y="71.0" font-size="12" fill="#D7DADF" text-anchor="end">image</text>
|
||||
<circle cx="348" cy="67.0" r="5" fill="#64B5F6" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="336" y="93.0" font-size="12" fill="#D7DADF" text-anchor="end">slot_count</text>
|
||||
<circle cx="348" cy="89.0" r="5" fill="#9CC2E5" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="336" y="115.0" font-size="12" fill="#D7DADF" text-anchor="end">used_count</text>
|
||||
<circle cx="348" cy="111.0" r="5" fill="#9CC2E5" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="336" y="137.0" font-size="12" fill="#D7DADF" text-anchor="end">grid_info</text>
|
||||
<circle cx="348" cy="133.0" r="5" fill="#C5E1A5" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<rect x="12" y="152" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="168" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="168" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="168" font-size="11.5" fill="#9AA0A8">columns</text>
|
||||
<text x="326" y="168" font-size="11.5" fill="#E6E8EB" text-anchor="end">2</text>
|
||||
<rect x="12" y="181" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="197" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="197" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="197" font-size="11.5" fill="#9AA0A8">rows</text>
|
||||
<text x="326" y="197" font-size="11.5" fill="#E6E8EB" text-anchor="end">2</text>
|
||||
<rect x="12" y="210" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="226" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="226" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="226" font-size="11.5" fill="#9AA0A8">cell_size_mode</text>
|
||||
<text x="326" y="226" font-size="11.5" fill="#E6E8EB" text-anchor="end">manual</text>
|
||||
<rect x="12" y="239" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="255" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="255" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="255" font-size="11.5" fill="#9AA0A8">cell_width</text>
|
||||
<text x="326" y="255" font-size="11.5" fill="#E6E8EB" text-anchor="end">512</text>
|
||||
<rect x="12" y="268" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="284" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="284" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="284" font-size="11.5" fill="#9AA0A8">cell_height</text>
|
||||
<text x="326" y="284" font-size="11.5" fill="#E6E8EB" text-anchor="end">512</text>
|
||||
<rect x="12" y="297" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="313" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="313" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="313" font-size="11.5" fill="#9AA0A8">resize_mode</text>
|
||||
<text x="326" y="313" font-size="11.5" fill="#E6E8EB" text-anchor="end">stretch</text>
|
||||
<rect x="12" y="326" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="342" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="342" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="342" font-size="11.5" fill="#9AA0A8">gap_x</text>
|
||||
<text x="326" y="342" font-size="11.5" fill="#E6E8EB" text-anchor="end">0</text>
|
||||
<rect x="12" y="355" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="371" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="371" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="371" font-size="11.5" fill="#9AA0A8">gap_y</text>
|
||||
<text x="326" y="371" font-size="11.5" fill="#E6E8EB" text-anchor="end">0</text>
|
||||
<rect x="12" y="384" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="22" y="400" font-size="11.5" fill="#9AA0A8">background_color_hex</text>
|
||||
<text x="338" y="400" font-size="11.5" fill="#E6E8EB" text-anchor="end">#000000</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.6 KiB |
@@ -0,0 +1,70 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="360" height="526" viewBox="0 0 360 526" font-family="Inter, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" role="img" aria-label="Image Label (NH) node">
|
||||
<rect x="0.5" y="0.5" width="359" height="525" rx="10" fill="#26282c" stroke="#45484f"/>
|
||||
<path d="M0.5 10.5 a10 10 0 0 1 10 -10 h339 a10 10 0 0 1 10 10 v36 h-359 z" fill="#34373d"/>
|
||||
<circle cx="17" cy="17" r="4.5" fill="#8FD18F"/>
|
||||
<text x="28" y="22" font-size="14" font-weight="600" fill="#EEF0F3">Image Label (NH)</text>
|
||||
<text x="28" y="38" font-size="10.5" fill="#9AA0A8" font-family="'JetBrains Mono', Consolas, 'Courier New', monospace">NH_ImageLabel · Image</text>
|
||||
<circle cx="12" cy="67.0" r="5" fill="#64B5F6" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="24" y="71.0" font-size="12" fill="#D7DADF">image</text>
|
||||
<text x="336" y="71.0" font-size="12" fill="#D7DADF" text-anchor="end">image</text>
|
||||
<circle cx="348" cy="67.0" r="5" fill="#64B5F6" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<rect x="12" y="86" width="336" height="46" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="22" y="102" font-size="11.5" fill="#9AA0A8">label_text</text>
|
||||
<text x="22" y="122" font-size="11.5" fill="#E6E8EB" font-family="'JetBrains Mono', Consolas, 'Courier New', monospace">NH Label</text>
|
||||
<rect x="12" y="137" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="153" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="153" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="153" font-size="11.5" fill="#9AA0A8">position</text>
|
||||
<text x="326" y="153" font-size="11.5" fill="#E6E8EB" text-anchor="end">header</text>
|
||||
<rect x="12" y="166" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="182" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="182" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="182" font-size="11.5" fill="#9AA0A8">frame_height</text>
|
||||
<text x="326" y="182" font-size="11.5" fill="#E6E8EB" text-anchor="end">80</text>
|
||||
<rect x="12" y="195" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="22" y="211" font-size="11.5" fill="#9AA0A8">fill_enabled</text>
|
||||
<rect x="308" y="201" width="30" height="13" rx="6.5" fill="#4C8BF5"/>
|
||||
<circle cx="331" cy="207.5" r="5" fill="#F5F6F7"/>
|
||||
<rect x="12" y="224" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="22" y="240" font-size="11.5" fill="#9AA0A8">frame_color_hex</text>
|
||||
<text x="338" y="240" font-size="11.5" fill="#E6E8EB" text-anchor="end">#111111</text>
|
||||
<rect x="12" y="253" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="22" y="269" font-size="11.5" fill="#9AA0A8">text_color_hex</text>
|
||||
<text x="338" y="269" font-size="11.5" fill="#E6E8EB" text-anchor="end">#FFFFFF</text>
|
||||
<rect x="12" y="282" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="298" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="298" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="298" font-size="11.5" fill="#9AA0A8">font_name</text>
|
||||
<text x="326" y="298" font-size="11.5" fill="#E6E8EB" text-anchor="end">default</text>
|
||||
<rect x="12" y="311" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="327" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="327" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="327" font-size="11.5" fill="#9AA0A8">font_size</text>
|
||||
<text x="326" y="327" font-size="11.5" fill="#E6E8EB" text-anchor="end">32</text>
|
||||
<rect x="12" y="340" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="22" y="356" font-size="11.5" fill="#9AA0A8">fit_text</text>
|
||||
<rect x="308" y="346" width="30" height="13" rx="6.5" fill="#4C8BF5"/>
|
||||
<circle cx="331" cy="352.5" r="5" fill="#F5F6F7"/>
|
||||
<rect x="12" y="369" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="22" y="385" font-size="11.5" fill="#9AA0A8">overlay_on_image</text>
|
||||
<rect x="308" y="375" width="30" height="13" rx="6.5" fill="#4a4d53"/>
|
||||
<circle cx="315" cy="381.5" r="5" fill="#F5F6F7"/>
|
||||
<rect x="12" y="398" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="414" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="414" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="414" font-size="11.5" fill="#9AA0A8">align</text>
|
||||
<text x="326" y="414" font-size="11.5" fill="#E6E8EB" text-anchor="end">left</text>
|
||||
<rect x="12" y="427" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="443" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="443" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="443" font-size="11.5" fill="#9AA0A8">padding_x</text>
|
||||
<text x="326" y="443" font-size="11.5" fill="#E6E8EB" text-anchor="end">20</text>
|
||||
<rect x="12" y="456" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="472" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="472" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="472" font-size="11.5" fill="#9AA0A8">outline_width</text>
|
||||
<text x="326" y="472" font-size="11.5" fill="#E6E8EB" text-anchor="end">0</text>
|
||||
<rect x="12" y="485" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="22" y="501" font-size="11.5" fill="#9AA0A8">outline_color_hex</text>
|
||||
<text x="338" y="501" font-size="11.5" fill="#E6E8EB" text-anchor="end">#000000</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 5.3 KiB |
@@ -0,0 +1,48 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="360" height="345" viewBox="0 0 360 345" font-family="Inter, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" role="img" aria-label="Image Resize Unit (NH) node">
|
||||
<rect x="0.5" y="0.5" width="359" height="344" rx="10" fill="#26282c" stroke="#45484f"/>
|
||||
<path d="M0.5 10.5 a10 10 0 0 1 10 -10 h339 a10 10 0 0 1 10 10 v36 h-359 z" fill="#34373d"/>
|
||||
<circle cx="17" cy="17" r="4.5" fill="#8FD18F"/>
|
||||
<text x="28" y="22" font-size="14" font-weight="600" fill="#EEF0F3">Image Resize Unit (NH)</text>
|
||||
<text x="28" y="38" font-size="10.5" fill="#9AA0A8" font-family="'JetBrains Mono', Consolas, 'Courier New', monospace">NH_ImageResizeByUnit · Image</text>
|
||||
<circle cx="12" cy="67.0" r="5" fill="#64B5F6" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="24" y="71.0" font-size="12" fill="#D7DADF">image</text>
|
||||
<text x="336" y="71.0" font-size="12" fill="#D7DADF" text-anchor="end">image</text>
|
||||
<circle cx="348" cy="67.0" r="5" fill="#64B5F6" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="336" y="93.0" font-size="12" fill="#D7DADF" text-anchor="end">width_px</text>
|
||||
<circle cx="348" cy="89.0" r="5" fill="#9CC2E5" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="336" y="115.0" font-size="12" fill="#D7DADF" text-anchor="end">height_px</text>
|
||||
<circle cx="348" cy="111.0" r="5" fill="#9CC2E5" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<rect x="12" y="130" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="146" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="146" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="146" font-size="11.5" fill="#9AA0A8">unit</text>
|
||||
<text x="326" y="146" font-size="11.5" fill="#E6E8EB" text-anchor="end">pixel</text>
|
||||
<rect x="12" y="159" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="175" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="175" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="175" font-size="11.5" fill="#9AA0A8">width_value</text>
|
||||
<text x="326" y="175" font-size="11.5" fill="#E6E8EB" text-anchor="end">1024.0</text>
|
||||
<rect x="12" y="188" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="204" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="204" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="204" font-size="11.5" fill="#9AA0A8">height_value</text>
|
||||
<text x="326" y="204" font-size="11.5" fill="#E6E8EB" text-anchor="end">1024.0</text>
|
||||
<rect x="12" y="217" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="233" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="233" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="233" font-size="11.5" fill="#9AA0A8">dpi</text>
|
||||
<text x="326" y="233" font-size="11.5" fill="#E6E8EB" text-anchor="end">300</text>
|
||||
<rect x="12" y="246" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="262" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="262" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="262" font-size="11.5" fill="#9AA0A8">resize_mode</text>
|
||||
<text x="326" y="262" font-size="11.5" fill="#E6E8EB" text-anchor="end">stretch</text>
|
||||
<rect x="12" y="275" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="291" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="291" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="291" font-size="11.5" fill="#9AA0A8">resize_method</text>
|
||||
<text x="326" y="291" font-size="11.5" fill="#E6E8EB" text-anchor="end">nearest</text>
|
||||
<rect x="12" y="304" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="22" y="320" font-size="11.5" fill="#9AA0A8">pad_color_hex</text>
|
||||
<text x="338" y="320" font-size="11.5" fill="#E6E8EB" text-anchor="end">#000000</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.7 KiB |
@@ -0,0 +1,41 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="360" height="324" viewBox="0 0 360 324" font-family="Inter, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" role="img" aria-label="Image Tile (NH) node">
|
||||
<rect x="0.5" y="0.5" width="359" height="323" rx="10" fill="#26282c" stroke="#45484f"/>
|
||||
<path d="M0.5 10.5 a10 10 0 0 1 10 -10 h339 a10 10 0 0 1 10 10 v36 h-359 z" fill="#34373d"/>
|
||||
<circle cx="17" cy="17" r="4.5" fill="#8FD18F"/>
|
||||
<text x="28" y="22" font-size="14" font-weight="600" fill="#EEF0F3">Image Tile (NH)</text>
|
||||
<text x="28" y="38" font-size="10.5" fill="#9AA0A8" font-family="'JetBrains Mono', Consolas, 'Courier New', monospace">NH_ImageTile · Image</text>
|
||||
<circle cx="12" cy="67.0" r="5" fill="#64B5F6" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="24" y="71.0" font-size="12" fill="#D7DADF">image</text>
|
||||
<text x="336" y="71.0" font-size="12" fill="#D7DADF" text-anchor="end">tiles</text>
|
||||
<circle cx="348" cy="67.0" r="5" fill="#64B5F6" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="336" y="93.0" font-size="12" fill="#D7DADF" text-anchor="end">tile_data</text>
|
||||
<circle cx="348" cy="89.0" r="5" fill="#E0B272" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="336" y="115.0" font-size="12" fill="#D7DADF" text-anchor="end">tile_count</text>
|
||||
<circle cx="348" cy="111.0" r="5" fill="#9CC2E5" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="336" y="137.0" font-size="12" fill="#D7DADF" text-anchor="end">overlap_x</text>
|
||||
<circle cx="348" cy="133.0" r="5" fill="#9CC2E5" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="336" y="159.0" font-size="12" fill="#D7DADF" text-anchor="end">overlap_y</text>
|
||||
<circle cx="348" cy="155.0" r="5" fill="#9CC2E5" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="336" y="181.0" font-size="12" fill="#D7DADF" text-anchor="end">tile_info</text>
|
||||
<circle cx="348" cy="177.0" r="5" fill="#C5E1A5" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<rect x="12" y="196" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="212" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="212" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="212" font-size="11.5" fill="#9AA0A8">tile_mode</text>
|
||||
<text x="326" y="212" font-size="11.5" fill="#E6E8EB" text-anchor="end">original_ratio</text>
|
||||
<rect x="12" y="225" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="241" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="241" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="241" font-size="11.5" fill="#9AA0A8">tile_size</text>
|
||||
<text x="326" y="241" font-size="11.5" fill="#E6E8EB" text-anchor="end">1024</text>
|
||||
<rect x="12" y="254" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="270" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="270" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="270" font-size="11.5" fill="#9AA0A8">tile_width</text>
|
||||
<text x="326" y="270" font-size="11.5" fill="#E6E8EB" text-anchor="end">1024</text>
|
||||
<rect x="12" y="283" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="299" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="299" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="299" font-size="11.5" fill="#9AA0A8">tile_height</text>
|
||||
<text x="326" y="299" font-size="11.5" fill="#E6E8EB" text-anchor="end">1024</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.3 KiB |
@@ -0,0 +1,15 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="360" height="112" viewBox="0 0 360 112" font-family="Inter, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" role="img" aria-label="Image Untile (NH) node">
|
||||
<rect x="0.5" y="0.5" width="359" height="111" rx="10" fill="#26282c" stroke="#45484f"/>
|
||||
<path d="M0.5 10.5 a10 10 0 0 1 10 -10 h339 a10 10 0 0 1 10 10 v36 h-359 z" fill="#34373d"/>
|
||||
<circle cx="17" cy="17" r="4.5" fill="#8FD18F"/>
|
||||
<text x="28" y="22" font-size="14" font-weight="600" fill="#EEF0F3">Image Untile (NH)</text>
|
||||
<text x="28" y="38" font-size="10.5" fill="#9AA0A8" font-family="'JetBrains Mono', Consolas, 'Courier New', monospace">NH_ImageUntile · Image</text>
|
||||
<circle cx="12" cy="67.0" r="5" fill="#64B5F6" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="24" y="71.0" font-size="12" fill="#D7DADF">tiles</text>
|
||||
<text x="336" y="71.0" font-size="12" fill="#D7DADF" text-anchor="end">image</text>
|
||||
<circle cx="348" cy="67.0" r="5" fill="#64B5F6" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<circle cx="12" cy="89.0" r="5" fill="#E0B272" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="24" y="93.0" font-size="12" fill="#D7DADF">tile_data</text>
|
||||
<text x="336" y="93.0" font-size="12" fill="#D7DADF" text-anchor="end">untile_info</text>
|
||||
<circle cx="348" cy="89.0" r="5" fill="#C5E1A5" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
@@ -0,0 +1,64 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="360" height="432" viewBox="0 0 360 432" font-family="Inter, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" role="img" aria-label="NH Large Image Compare node">
|
||||
<rect x="0.5" y="0.5" width="359" height="431" rx="10" fill="#26282c" stroke="#45484f"/>
|
||||
<path d="M0.5 10.5 a10 10 0 0 1 10 -10 h339 a10 10 0 0 1 10 10 v36 h-359 z" fill="#34373d"/>
|
||||
<circle cx="17" cy="17" r="4.5" fill="#F6AD55"/>
|
||||
<text x="28" y="22" font-size="14" font-weight="600" fill="#EEF0F3">NH Large Image Compare</text>
|
||||
<text x="28" y="38" font-size="10.5" fill="#9AA0A8" font-family="'JetBrains Mono', Consolas, 'Courier New', monospace">NH_LargeImageCompare · Image</text>
|
||||
<circle cx="12" cy="67.0" r="5" fill="#64B5F6" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="24" y="71.0" font-size="12" fill="#D7DADF">image_a</text>
|
||||
<text x="336" y="71.0" font-size="12" fill="#D7DADF" text-anchor="end">image_a</text>
|
||||
<circle cx="348" cy="67.0" r="5" fill="#64B5F6" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<circle cx="12" cy="89.0" r="5" fill="#64B5F6" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="24" y="93.0" font-size="12" fill="#D7DADF">image_b</text>
|
||||
<text x="336" y="93.0" font-size="12" fill="#D7DADF" text-anchor="end">image_b</text>
|
||||
<circle cx="348" cy="89.0" r="5" fill="#64B5F6" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="336" y="115.0" font-size="12" fill="#D7DADF" text-anchor="end">manifest</text>
|
||||
<circle cx="348" cy="111.0" r="5" fill="#C5E1A5" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<rect x="12" y="130" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="146" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="146" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="146" font-size="11.5" fill="#9AA0A8">compare_mode</text>
|
||||
<text x="326" y="146" font-size="11.5" fill="#E6E8EB" text-anchor="end">slider</text>
|
||||
<rect x="12" y="159" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="22" y="175" font-size="11.5" fill="#9AA0A8">sync_pan_zoom</text>
|
||||
<rect x="308" y="165" width="30" height="13" rx="6.5" fill="#4C8BF5"/>
|
||||
<circle cx="331" cy="171.5" r="5" fill="#F5F6F7"/>
|
||||
<rect x="12" y="188" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="204" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="204" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="204" font-size="11.5" fill="#9AA0A8">preview_megapixels</text>
|
||||
<text x="326" y="204" font-size="11.5" fill="#E6E8EB" text-anchor="end">1.00</text>
|
||||
<rect x="12" y="217" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="233" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="233" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="233" font-size="11.5" fill="#9AA0A8">preview_format</text>
|
||||
<text x="326" y="233" font-size="11.5" fill="#E6E8EB" text-anchor="end">jpg</text>
|
||||
<rect x="12" y="246" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="262" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="262" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="262" font-size="11.5" fill="#9AA0A8">preview_quality</text>
|
||||
<text x="326" y="262" font-size="11.5" fill="#E6E8EB" text-anchor="end">90</text>
|
||||
<rect x="12" y="275" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="22" y="291" font-size="11.5" fill="#9AA0A8">title</text>
|
||||
<text x="338" y="291" font-size="11.5" fill="#E6E8EB" text-anchor="end">NH Large Image Compare</text>
|
||||
<rect x="12" y="304" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="320" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="320" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="320" font-size="11.5" fill="#9AA0A8">tile_size <tspan fill="#6f747c" font-style="italic">opt</tspan></text>
|
||||
<text x="326" y="320" font-size="11.5" fill="#E6E8EB" text-anchor="end">512</text>
|
||||
<rect x="12" y="333" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="349" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="349" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="349" font-size="11.5" fill="#9AA0A8">tile_format <tspan fill="#6f747c" font-style="italic">opt</tspan></text>
|
||||
<text x="326" y="349" font-size="11.5" fill="#E6E8EB" text-anchor="end">jpg</text>
|
||||
<rect x="12" y="362" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="378" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="378" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="378" font-size="11.5" fill="#9AA0A8">tile_quality <tspan fill="#6f747c" font-style="italic">opt</tspan></text>
|
||||
<text x="326" y="378" font-size="11.5" fill="#E6E8EB" text-anchor="end">90</text>
|
||||
<rect x="12" y="391" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="407" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="407" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="407" font-size="11.5" fill="#9AA0A8">cache_policy <tspan fill="#6f747c" font-style="italic">opt</tspan></text>
|
||||
<text x="326" y="407" font-size="11.5" fill="#E6E8EB" text-anchor="end">temp</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 5.1 KiB |
@@ -0,0 +1,56 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="360" height="381" viewBox="0 0 360 381" font-family="Inter, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" role="img" aria-label="NH Large Image Preview node">
|
||||
<rect x="0.5" y="0.5" width="359" height="380" rx="10" fill="#26282c" stroke="#45484f"/>
|
||||
<path d="M0.5 10.5 a10 10 0 0 1 10 -10 h339 a10 10 0 0 1 10 10 v36 h-359 z" fill="#34373d"/>
|
||||
<circle cx="17" cy="17" r="4.5" fill="#F6AD55"/>
|
||||
<text x="28" y="22" font-size="14" font-weight="600" fill="#EEF0F3">NH Large Image Preview</text>
|
||||
<text x="28" y="38" font-size="10.5" fill="#9AA0A8" font-family="'JetBrains Mono', Consolas, 'Courier New', monospace">NH_LargeImagePreview · Image</text>
|
||||
<circle cx="12" cy="67.0" r="5" fill="#64B5F6" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="24" y="71.0" font-size="12" fill="#D7DADF">image</text>
|
||||
<text x="336" y="71.0" font-size="12" fill="#D7DADF" text-anchor="end">image</text>
|
||||
<circle cx="348" cy="67.0" r="5" fill="#64B5F6" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="336" y="93.0" font-size="12" fill="#D7DADF" text-anchor="end">manifest</text>
|
||||
<circle cx="348" cy="89.0" r="5" fill="#C5E1A5" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<rect x="12" y="108" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="124" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="124" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="124" font-size="11.5" fill="#9AA0A8">preview_megapixels</text>
|
||||
<text x="326" y="124" font-size="11.5" fill="#E6E8EB" text-anchor="end">1.00</text>
|
||||
<rect x="12" y="137" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="153" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="153" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="153" font-size="11.5" fill="#9AA0A8">preview_format</text>
|
||||
<text x="326" y="153" font-size="11.5" fill="#E6E8EB" text-anchor="end">jpg</text>
|
||||
<rect x="12" y="166" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="182" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="182" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="182" font-size="11.5" fill="#9AA0A8">preview_quality</text>
|
||||
<text x="326" y="182" font-size="11.5" fill="#E6E8EB" text-anchor="end">90</text>
|
||||
<rect x="12" y="195" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="211" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="211" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="211" font-size="11.5" fill="#9AA0A8">viewer_mode</text>
|
||||
<text x="326" y="211" font-size="11.5" fill="#E6E8EB" text-anchor="end">modal</text>
|
||||
<rect x="12" y="224" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="22" y="240" font-size="11.5" fill="#9AA0A8">title</text>
|
||||
<text x="338" y="240" font-size="11.5" fill="#E6E8EB" text-anchor="end">NH Large Image Preview</text>
|
||||
<rect x="12" y="253" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="269" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="269" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="269" font-size="11.5" fill="#9AA0A8">tile_size <tspan fill="#6f747c" font-style="italic">opt</tspan></text>
|
||||
<text x="326" y="269" font-size="11.5" fill="#E6E8EB" text-anchor="end">512</text>
|
||||
<rect x="12" y="282" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="298" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="298" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="298" font-size="11.5" fill="#9AA0A8">tile_format <tspan fill="#6f747c" font-style="italic">opt</tspan></text>
|
||||
<text x="326" y="298" font-size="11.5" fill="#E6E8EB" text-anchor="end">jpg</text>
|
||||
<rect x="12" y="311" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="327" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="327" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="327" font-size="11.5" fill="#9AA0A8">tile_quality <tspan fill="#6f747c" font-style="italic">opt</tspan></text>
|
||||
<text x="326" y="327" font-size="11.5" fill="#E6E8EB" text-anchor="end">90</text>
|
||||
<rect x="12" y="340" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="356" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="356" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="356" font-size="11.5" fill="#9AA0A8">cache_policy <tspan fill="#6f747c" font-style="italic">opt</tspan></text>
|
||||
<text x="326" y="356" font-size="11.5" fill="#E6E8EB" text-anchor="end">temp</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.5 KiB |
@@ -0,0 +1,33 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="360" height="280" viewBox="0 0 360 280" font-family="Inter, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" role="img" aria-label="NH Large Preview Cache Manager node">
|
||||
<rect x="0.5" y="0.5" width="359" height="279" rx="10" fill="#26282c" stroke="#45484f"/>
|
||||
<path d="M0.5 10.5 a10 10 0 0 1 10 -10 h339 a10 10 0 0 1 10 10 v36 h-359 z" fill="#34373d"/>
|
||||
<circle cx="17" cy="17" r="4.5" fill="#F6AD55"/>
|
||||
<text x="28" y="22" font-size="14" font-weight="600" fill="#EEF0F3">NH Large Preview Cache Manager</text>
|
||||
<text x="28" y="38" font-size="10.5" fill="#9AA0A8" font-family="'JetBrains Mono', Consolas, 'Courier New', monospace">NH_LargePreviewCacheManager · Image</text>
|
||||
<text x="336" y="71.0" font-size="12" fill="#D7DADF" text-anchor="end">summary</text>
|
||||
<circle cx="348" cy="67.0" r="5" fill="#C5E1A5" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="336" y="93.0" font-size="12" fill="#D7DADF" text-anchor="end">removed</text>
|
||||
<circle cx="348" cy="89.0" r="5" fill="#9CC2E5" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="336" y="115.0" font-size="12" fill="#D7DADF" text-anchor="end">freed_mb</text>
|
||||
<circle cx="348" cy="111.0" r="5" fill="#9CC2E5" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="336" y="137.0" font-size="12" fill="#D7DADF" text-anchor="end">cache_root</text>
|
||||
<circle cx="348" cy="133.0" r="5" fill="#C5E1A5" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<rect x="12" y="152" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="168" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="168" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="168" font-size="11.5" fill="#9AA0A8">action</text>
|
||||
<text x="326" y="168" font-size="11.5" fill="#E6E8EB" text-anchor="end">info</text>
|
||||
<rect x="12" y="181" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="197" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="197" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="197" font-size="11.5" fill="#9AA0A8">max_age_hours</text>
|
||||
<text x="326" y="197" font-size="11.5" fill="#E6E8EB" text-anchor="end">24.0</text>
|
||||
<rect x="12" y="210" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="226" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="226" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="226" font-size="11.5" fill="#9AA0A8">max_total_mb</text>
|
||||
<text x="326" y="226" font-size="11.5" fill="#E6E8EB" text-anchor="end">2048.0</text>
|
||||
<rect x="12" y="239" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="22" y="255" font-size="11.5" fill="#9AA0A8">cache_id</text>
|
||||
<text x="338" y="255" font-size="11.5" fill="#E6E8EB" text-anchor="end"></text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.7 KiB |
@@ -0,0 +1,90 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="360" height="621" viewBox="0 0 360 621" font-family="Inter, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" role="img" aria-label="Layer Layout Composite (NH) node">
|
||||
<rect x="0.5" y="0.5" width="359" height="620" rx="10" fill="#26282c" stroke="#45484f"/>
|
||||
<path d="M0.5 10.5 a10 10 0 0 1 10 -10 h339 a10 10 0 0 1 10 10 v36 h-359 z" fill="#34373d"/>
|
||||
<circle cx="17" cy="17" r="4.5" fill="#8FD18F"/>
|
||||
<text x="28" y="22" font-size="14" font-weight="600" fill="#EEF0F3">Layer Layout Composite (NH)</text>
|
||||
<text x="28" y="38" font-size="10.5" fill="#9AA0A8" font-family="'JetBrains Mono', Consolas, 'Courier New', monospace">NH_LayerLayoutComposite · Image</text>
|
||||
<circle cx="12" cy="67.0" r="5" fill="#64B5F6" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="24" y="71.0" font-size="12" fill="#D7DADF">images</text>
|
||||
<text x="336" y="71.0" font-size="12" fill="#D7DADF" text-anchor="end">image</text>
|
||||
<circle cx="348" cy="67.0" r="5" fill="#64B5F6" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<circle cx="12" cy="89.0" r="5" fill="#64B5F6" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="24" y="93.0" font-size="12" fill="#D7DADF">background_image <tspan fill="#7d838c" font-style="italic">(optional)</tspan></text>
|
||||
<text x="336" y="93.0" font-size="12" fill="#D7DADF" text-anchor="end">slot_count</text>
|
||||
<circle cx="348" cy="89.0" r="5" fill="#9CC2E5" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="336" y="115.0" font-size="12" fill="#D7DADF" text-anchor="end">used_count</text>
|
||||
<circle cx="348" cy="111.0" r="5" fill="#9CC2E5" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="336" y="137.0" font-size="12" fill="#D7DADF" text-anchor="end">layout_info</text>
|
||||
<circle cx="348" cy="133.0" r="5" fill="#C5E1A5" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<rect x="12" y="152" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="168" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="168" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="168" font-size="11.5" fill="#9AA0A8">canvas_width</text>
|
||||
<text x="326" y="168" font-size="11.5" fill="#E6E8EB" text-anchor="end">2480</text>
|
||||
<rect x="12" y="181" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="197" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="197" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="197" font-size="11.5" fill="#9AA0A8">canvas_height</text>
|
||||
<text x="326" y="197" font-size="11.5" fill="#E6E8EB" text-anchor="end">3508</text>
|
||||
<rect x="12" y="210" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="22" y="226" font-size="11.5" fill="#9AA0A8">background_color</text>
|
||||
<text x="338" y="226" font-size="11.5" fill="#E6E8EB" text-anchor="end">#FFFFFF</text>
|
||||
<rect x="12" y="239" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="255" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="255" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="255" font-size="11.5" fill="#9AA0A8">layout_mode</text>
|
||||
<text x="326" y="255" font-size="11.5" fill="#E6E8EB" text-anchor="end">preset</text>
|
||||
<rect x="12" y="268" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="284" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="284" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="284" font-size="11.5" fill="#9AA0A8">layout_preset</text>
|
||||
<text x="326" y="284" font-size="11.5" fill="#E6E8EB" text-anchor="end">2x3</text>
|
||||
<rect x="12" y="297" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="313" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="313" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="313" font-size="11.5" fill="#9AA0A8">resize_mode</text>
|
||||
<text x="326" y="313" font-size="11.5" fill="#E6E8EB" text-anchor="end">cover</text>
|
||||
<rect x="12" y="326" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="342" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="342" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="342" font-size="11.5" fill="#9AA0A8">columns</text>
|
||||
<text x="326" y="342" font-size="11.5" fill="#E6E8EB" text-anchor="end">2</text>
|
||||
<rect x="12" y="355" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="371" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="371" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="371" font-size="11.5" fill="#9AA0A8">rows</text>
|
||||
<text x="326" y="371" font-size="11.5" fill="#E6E8EB" text-anchor="end">3</text>
|
||||
<rect x="12" y="384" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="400" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="400" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="400" font-size="11.5" fill="#9AA0A8">margin_x</text>
|
||||
<text x="326" y="400" font-size="11.5" fill="#E6E8EB" text-anchor="end">150</text>
|
||||
<rect x="12" y="413" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="429" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="429" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="429" font-size="11.5" fill="#9AA0A8">margin_y</text>
|
||||
<text x="326" y="429" font-size="11.5" fill="#E6E8EB" text-anchor="end">150</text>
|
||||
<rect x="12" y="442" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="458" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="458" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="458" font-size="11.5" fill="#9AA0A8">gap_x</text>
|
||||
<text x="326" y="458" font-size="11.5" fill="#E6E8EB" text-anchor="end">150</text>
|
||||
<rect x="12" y="471" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="487" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="487" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="487" font-size="11.5" fill="#9AA0A8">gap_y</text>
|
||||
<text x="326" y="487" font-size="11.5" fill="#E6E8EB" text-anchor="end">150</text>
|
||||
<rect x="12" y="500" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="516" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="516" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="516" font-size="11.5" fill="#9AA0A8">custom_units</text>
|
||||
<text x="326" y="516" font-size="11.5" fill="#E6E8EB" text-anchor="end">auto</text>
|
||||
<rect x="12" y="529" width="336" height="46" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="22" y="545" font-size="11.5" fill="#9AA0A8">custom_layout</text>
|
||||
<text x="22" y="565" font-size="11.5" fill="#E6E8EB" font-family="'JetBrains Mono', Consolas, 'Courier New', monospace">0.06,0.04,0.39,0.20 ⏎ 0.55,0.…</text>
|
||||
<rect x="12" y="580" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="596" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="596" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="596" font-size="11.5" fill="#9AA0A8">background_fit</text>
|
||||
<text x="326" y="596" font-size="11.5" fill="#E6E8EB" text-anchor="end">stretch</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 6.9 KiB |
@@ -0,0 +1,29 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="360" height="251" viewBox="0 0 360 251" font-family="Inter, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" role="img" aria-label="Layer Stack Composite (NH) node">
|
||||
<rect x="0.5" y="0.5" width="359" height="250" rx="10" fill="#26282c" stroke="#45484f"/>
|
||||
<path d="M0.5 10.5 a10 10 0 0 1 10 -10 h339 a10 10 0 0 1 10 10 v36 h-359 z" fill="#34373d"/>
|
||||
<circle cx="17" cy="17" r="4.5" fill="#8FD18F"/>
|
||||
<text x="28" y="22" font-size="14" font-weight="600" fill="#EEF0F3">Layer Stack Composite (NH)</text>
|
||||
<text x="28" y="38" font-size="10.5" fill="#9AA0A8" font-family="'JetBrains Mono', Consolas, 'Courier New', monospace">NH_LayerStackComposite · Image</text>
|
||||
<circle cx="12" cy="67.0" r="5" fill="#64B5F6" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="24" y="71.0" font-size="12" fill="#D7DADF">background_image</text>
|
||||
<text x="336" y="71.0" font-size="12" fill="#D7DADF" text-anchor="end">image</text>
|
||||
<circle cx="348" cy="67.0" r="5" fill="#64B5F6" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<circle cx="12" cy="89.0" r="5" fill="#64B5F6" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="24" y="93.0" font-size="12" fill="#D7DADF">layer_1 <tspan fill="#7d838c" font-style="italic">(optional)</tspan></text>
|
||||
<text x="336" y="93.0" font-size="12" fill="#D7DADF" text-anchor="end">used_count</text>
|
||||
<circle cx="348" cy="89.0" r="5" fill="#9CC2E5" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<circle cx="12" cy="111.0" r="5" fill="#64B5F6" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="24" y="115.0" font-size="12" fill="#D7DADF">layer_2 <tspan fill="#7d838c" font-style="italic">(optional)</tspan></text>
|
||||
<text x="336" y="115.0" font-size="12" fill="#D7DADF" text-anchor="end">layer_info</text>
|
||||
<circle cx="348" cy="111.0" r="5" fill="#C5E1A5" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<rect x="12" y="130" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="146" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="146" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="146" font-size="11.5" fill="#9AA0A8">layer_count</text>
|
||||
<text x="326" y="146" font-size="11.5" fill="#E6E8EB" text-anchor="end">1</text>
|
||||
<rect x="12" y="159" width="336" height="46" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="22" y="175" font-size="11.5" fill="#9AA0A8">positions</text>
|
||||
<text x="22" y="195" font-size="11.5" fill="#E6E8EB" font-family="'JetBrains Mono', Consolas, 'Courier New', monospace">0,0</text>
|
||||
<rect x="12" y="210" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="180.0" y="226" font-size="11" fill="#9AA0A8" text-anchor="middle" font-style="italic">layer_3 … layer_64</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.6 KiB |
@@ -0,0 +1,21 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="360" height="244" viewBox="0 0 360 244" font-family="Inter, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" role="img" aria-label="List Create (NH) node">
|
||||
<rect x="0.5" y="0.5" width="359" height="243" rx="10" fill="#26282c" stroke="#45484f"/>
|
||||
<path d="M0.5 10.5 a10 10 0 0 1 10 -10 h339 a10 10 0 0 1 10 10 v36 h-359 z" fill="#34373d"/>
|
||||
<circle cx="17" cy="17" r="4.5" fill="#8FD18F"/>
|
||||
<text x="28" y="22" font-size="14" font-weight="600" fill="#EEF0F3">List Create (NH)</text>
|
||||
<text x="28" y="38" font-size="10.5" fill="#9AA0A8" font-family="'JetBrains Mono', Consolas, 'Courier New', monospace">NH_ListCreate · Batch</text>
|
||||
<text x="336" y="71.0" font-size="12" fill="#D7DADF" text-anchor="end">items</text>
|
||||
<circle cx="348" cy="67.0" r="5" fill="#E0B272" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="336" y="93.0" font-size="12" fill="#D7DADF" text-anchor="end">count</text>
|
||||
<circle cx="348" cy="89.0" r="5" fill="#9CC2E5" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="336" y="115.0" font-size="12" fill="#D7DADF" text-anchor="end">first</text>
|
||||
<circle cx="348" cy="111.0" r="5" fill="#C5E1A5" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="336" y="137.0" font-size="12" fill="#D7DADF" text-anchor="end">last</text>
|
||||
<circle cx="348" cy="133.0" r="5" fill="#C5E1A5" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<rect x="12" y="152" width="336" height="46" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="22" y="168" font-size="11.5" fill="#9AA0A8">text</text>
|
||||
<text x="22" y="188" font-size="11.5" fill="#E6E8EB" font-family="'JetBrains Mono', Consolas, 'Courier New', monospace">item 1 ⏎ item 2 ⏎ item 3</text>
|
||||
<rect x="12" y="203" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="22" y="219" font-size="11.5" fill="#9AA0A8">delimiter <tspan fill="#6f747c" font-style="italic">opt</tspan></text>
|
||||
<text x="338" y="219" font-size="11.5" fill="#E6E8EB" text-anchor="end">\n</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.9 KiB |
@@ -0,0 +1,25 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="360" height="222" viewBox="0 0 360 222" font-family="Inter, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" role="img" aria-label="List Filter (NH) node">
|
||||
<rect x="0.5" y="0.5" width="359" height="221" rx="10" fill="#26282c" stroke="#45484f"/>
|
||||
<path d="M0.5 10.5 a10 10 0 0 1 10 -10 h339 a10 10 0 0 1 10 10 v36 h-359 z" fill="#34373d"/>
|
||||
<circle cx="17" cy="17" r="4.5" fill="#8FD18F"/>
|
||||
<text x="28" y="22" font-size="14" font-weight="600" fill="#EEF0F3">List Filter (NH)</text>
|
||||
<text x="28" y="38" font-size="10.5" fill="#9AA0A8" font-family="'JetBrains Mono', Consolas, 'Courier New', monospace">NH_ListFilter · Batch</text>
|
||||
<circle cx="12" cy="67.0" r="5" fill="#E0B272" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="24" y="71.0" font-size="12" fill="#D7DADF">items</text>
|
||||
<text x="336" y="71.0" font-size="12" fill="#D7DADF" text-anchor="end">passed</text>
|
||||
<circle cx="348" cy="67.0" r="5" fill="#E0B272" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="336" y="93.0" font-size="12" fill="#D7DADF" text-anchor="end">rejected</text>
|
||||
<circle cx="348" cy="89.0" r="5" fill="#E0B272" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="336" y="115.0" font-size="12" fill="#D7DADF" text-anchor="end">passed_count</text>
|
||||
<circle cx="348" cy="111.0" r="5" fill="#9CC2E5" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="336" y="137.0" font-size="12" fill="#D7DADF" text-anchor="end">rejected_count</text>
|
||||
<circle cx="348" cy="133.0" r="5" fill="#9CC2E5" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<rect x="12" y="152" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="22" y="168" font-size="11.5" fill="#9AA0A8">condition</text>
|
||||
<text x="338" y="168" font-size="11.5" fill="#E6E8EB" text-anchor="end">contains:dress</text>
|
||||
<rect x="12" y="181" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="197" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="197" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="197" font-size="11.5" fill="#9AA0A8">mode</text>
|
||||
<text x="326" y="197" font-size="11.5" fill="#E6E8EB" text-anchor="end">include</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.1 KiB |
@@ -0,0 +1,26 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="360" height="222" viewBox="0 0 360 222" font-family="Inter, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" role="img" aria-label="List Index (NH) node">
|
||||
<rect x="0.5" y="0.5" width="359" height="221" rx="10" fill="#26282c" stroke="#45484f"/>
|
||||
<path d="M0.5 10.5 a10 10 0 0 1 10 -10 h339 a10 10 0 0 1 10 10 v36 h-359 z" fill="#34373d"/>
|
||||
<circle cx="17" cy="17" r="4.5" fill="#8FD18F"/>
|
||||
<text x="28" y="22" font-size="14" font-weight="600" fill="#EEF0F3">List Index (NH)</text>
|
||||
<text x="28" y="38" font-size="10.5" fill="#9AA0A8" font-family="'JetBrains Mono', Consolas, 'Courier New', monospace">NH_ListIndex · Batch</text>
|
||||
<circle cx="12" cy="67.0" r="5" fill="#E0B272" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="24" y="71.0" font-size="12" fill="#D7DADF">items</text>
|
||||
<text x="336" y="71.0" font-size="12" fill="#D7DADF" text-anchor="end">item</text>
|
||||
<circle cx="348" cy="67.0" r="5" fill="#C5E1A5" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="336" y="93.0" font-size="12" fill="#D7DADF" text-anchor="end">is_first</text>
|
||||
<circle cx="348" cy="89.0" r="5" fill="#F48FB1" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="336" y="115.0" font-size="12" fill="#D7DADF" text-anchor="end">is_last</text>
|
||||
<circle cx="348" cy="111.0" r="5" fill="#F48FB1" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="336" y="137.0" font-size="12" fill="#D7DADF" text-anchor="end">count</text>
|
||||
<circle cx="348" cy="133.0" r="5" fill="#9CC2E5" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<rect x="12" y="152" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="168" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="168" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="168" font-size="11.5" fill="#9AA0A8">index</text>
|
||||
<text x="326" y="168" font-size="11.5" fill="#E6E8EB" text-anchor="end">0</text>
|
||||
<rect x="12" y="181" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="22" y="197" font-size="11.5" fill="#9AA0A8">wrap <tspan fill="#6f747c" font-style="italic">opt</tspan></text>
|
||||
<rect x="308" y="187" width="30" height="13" rx="6.5" fill="#4C8BF5"/>
|
||||
<circle cx="331" cy="193.5" r="5" fill="#F5F6F7"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.2 KiB |
@@ -0,0 +1,28 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="360" height="251" viewBox="0 0 360 251" font-family="Inter, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" role="img" aria-label="Load Image Info (NH) node">
|
||||
<rect x="0.5" y="0.5" width="359" height="250" rx="10" fill="#26282c" stroke="#45484f"/>
|
||||
<path d="M0.5 10.5 a10 10 0 0 1 10 -10 h339 a10 10 0 0 1 10 10 v36 h-359 z" fill="#34373d"/>
|
||||
<circle cx="17" cy="17" r="4.5" fill="#8FD18F"/>
|
||||
<text x="28" y="22" font-size="14" font-weight="600" fill="#EEF0F3">Load Image Info (NH)</text>
|
||||
<text x="28" y="38" font-size="10.5" fill="#9AA0A8" font-family="'JetBrains Mono', Consolas, 'Courier New', monospace">NH_LoadImageInfo · Image</text>
|
||||
<text x="336" y="71.0" font-size="12" fill="#D7DADF" text-anchor="end">image</text>
|
||||
<circle cx="348" cy="67.0" r="5" fill="#64B5F6" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="336" y="93.0" font-size="12" fill="#D7DADF" text-anchor="end">mask</text>
|
||||
<circle cx="348" cy="89.0" r="5" fill="#81C784" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="336" y="115.0" font-size="12" fill="#D7DADF" text-anchor="end">file_name</text>
|
||||
<circle cx="348" cy="111.0" r="5" fill="#C5E1A5" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="336" y="137.0" font-size="12" fill="#D7DADF" text-anchor="end">path</text>
|
||||
<circle cx="348" cy="133.0" r="5" fill="#C5E1A5" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<rect x="12" y="152" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="168" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="168" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="168" font-size="11.5" fill="#9AA0A8">image</text>
|
||||
<text x="326" y="168" font-size="11.5" fill="#E6E8EB" text-anchor="end">(choose a file)</text>
|
||||
<rect x="12" y="181" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="22" y="197" font-size="11.5" fill="#9AA0A8">filename_extension</text>
|
||||
<rect x="308" y="187" width="30" height="13" rx="6.5" fill="#4C8BF5"/>
|
||||
<circle cx="331" cy="193.5" r="5" fill="#F5F6F7"/>
|
||||
<rect x="12" y="210" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="22" y="226" font-size="11.5" fill="#9AA0A8">path_include_filename</text>
|
||||
<rect x="308" y="216" width="30" height="13" rx="6.5" fill="#4C8BF5"/>
|
||||
<circle cx="331" cy="222.5" r="5" fill="#F5F6F7"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.3 KiB |
@@ -0,0 +1,44 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="360" height="345" viewBox="0 0 360 345" font-family="Inter, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" role="img" aria-label="Load Images Folder (NH) node">
|
||||
<rect x="0.5" y="0.5" width="359" height="344" rx="10" fill="#26282c" stroke="#45484f"/>
|
||||
<path d="M0.5 10.5 a10 10 0 0 1 10 -10 h339 a10 10 0 0 1 10 10 v36 h-359 z" fill="#34373d"/>
|
||||
<circle cx="17" cy="17" r="4.5" fill="#8FD18F"/>
|
||||
<text x="28" y="22" font-size="14" font-weight="600" fill="#EEF0F3">Load Images Folder (NH)</text>
|
||||
<text x="28" y="38" font-size="10.5" fill="#9AA0A8" font-family="'JetBrains Mono', Consolas, 'Courier New', monospace">NH_LoadImagesFromFolder · Image</text>
|
||||
<text x="336" y="71.0" font-size="12" fill="#D7DADF" text-anchor="end">images</text>
|
||||
<circle cx="348" cy="67.0" r="5" fill="#64B5F6" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="336" y="93.0" font-size="12" fill="#D7DADF" text-anchor="end">filename</text>
|
||||
<circle cx="348" cy="89.0" r="5" fill="#C5E1A5" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="336" y="115.0" font-size="12" fill="#D7DADF" text-anchor="end">path</text>
|
||||
<circle cx="348" cy="111.0" r="5" fill="#C5E1A5" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<rect x="12" y="130" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="22" y="146" font-size="11.5" fill="#9AA0A8">folder_path</text>
|
||||
<text x="338" y="146" font-size="11.5" fill="#E6E8EB" text-anchor="end"></text>
|
||||
<rect x="12" y="159" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="175" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="175" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="175" font-size="11.5" fill="#9AA0A8">index</text>
|
||||
<text x="326" y="175" font-size="11.5" fill="#E6E8EB" text-anchor="end">0</text>
|
||||
<rect x="12" y="188" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="204" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="204" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="204" font-size="11.5" fill="#9AA0A8">step</text>
|
||||
<text x="326" y="204" font-size="11.5" fill="#E6E8EB" text-anchor="end">1</text>
|
||||
<rect x="12" y="217" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="233" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="233" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="233" font-size="11.5" fill="#9AA0A8">seed_mode</text>
|
||||
<text x="326" y="233" font-size="11.5" fill="#E6E8EB" text-anchor="end">fixed</text>
|
||||
<rect x="12" y="246" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="22" y="262" font-size="11.5" fill="#9AA0A8">recursive</text>
|
||||
<rect x="308" y="252" width="30" height="13" rx="6.5" fill="#4a4d53"/>
|
||||
<circle cx="315" cy="258.5" r="5" fill="#F5F6F7"/>
|
||||
<rect x="12" y="275" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="22" y="291" font-size="11.5" fill="#9AA0A8">filename_extension</text>
|
||||
<rect x="308" y="281" width="30" height="13" rx="6.5" fill="#4C8BF5"/>
|
||||
<circle cx="331" cy="287.5" r="5" fill="#F5F6F7"/>
|
||||
<rect x="12" y="304" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="320" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="320" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="320" font-size="11.5" fill="#9AA0A8">path_output</text>
|
||||
<text x="326" y="320" font-size="11.5" fill="#E6E8EB" text-anchor="end">file_path</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.4 KiB |
@@ -0,0 +1,58 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="360" height="520" viewBox="0 0 360 520" font-family="Inter, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" role="img" aria-label="Load images matching node">
|
||||
<rect x="0.5" y="0.5" width="359" height="519" rx="10" fill="#26282c" stroke="#45484f"/>
|
||||
<path d="M0.5 10.5 a10 10 0 0 1 10 -10 h339 a10 10 0 0 1 10 10 v36 h-359 z" fill="#34373d"/>
|
||||
<circle cx="17" cy="17" r="4.5" fill="#8FD18F"/>
|
||||
<text x="28" y="22" font-size="14" font-weight="600" fill="#EEF0F3">Load images matching</text>
|
||||
<text x="28" y="38" font-size="10.5" fill="#9AA0A8" font-family="'JetBrains Mono', Consolas, 'Courier New', monospace">NH_LoadImagesMatching · Image</text>
|
||||
<circle cx="12" cy="67.0" r="5" fill="#64B5F6" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="24" y="71.0" font-size="12" fill="#D7DADF">fallback_images <tspan fill="#7d838c" font-style="italic">(optional)</tspan></text>
|
||||
<text x="336" y="71.0" font-size="12" fill="#D7DADF" text-anchor="end">images</text>
|
||||
<circle cx="348" cy="67.0" r="5" fill="#64B5F6" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="336" y="93.0" font-size="12" fill="#D7DADF" text-anchor="end">filename</text>
|
||||
<circle cx="348" cy="89.0" r="5" fill="#C5E1A5" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="336" y="115.0" font-size="12" fill="#D7DADF" text-anchor="end">path</text>
|
||||
<circle cx="348" cy="111.0" r="5" fill="#C5E1A5" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="336" y="137.0" font-size="12" fill="#D7DADF" text-anchor="end">matched_text</text>
|
||||
<circle cx="348" cy="133.0" r="5" fill="#C5E1A5" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="336" y="159.0" font-size="12" fill="#D7DADF" text-anchor="end">found</text>
|
||||
<circle cx="348" cy="155.0" r="5" fill="#F48FB1" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<rect x="12" y="174" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="22" y="190" font-size="11.5" fill="#9AA0A8">source_folder_path</text>
|
||||
<text x="338" y="190" font-size="11.5" fill="#E6E8EB" text-anchor="end"></text>
|
||||
<rect x="12" y="203" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="22" y="219" font-size="11.5" fill="#9AA0A8">matching_root_path</text>
|
||||
<text x="338" y="219" font-size="11.5" fill="#E6E8EB" text-anchor="end"></text>
|
||||
<rect x="12" y="232" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="22" y="248" font-size="11.5" fill="#9AA0A8">match_text</text>
|
||||
<text x="338" y="248" font-size="11.5" fill="#E6E8EB" text-anchor="end"></text>
|
||||
<rect x="12" y="261" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="277" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="277" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="277" font-size="11.5" fill="#9AA0A8">match_mode</text>
|
||||
<text x="326" y="277" font-size="11.5" fill="#E6E8EB" text-anchor="end">contains</text>
|
||||
<rect x="12" y="290" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="22" y="306" font-size="11.5" fill="#9AA0A8">recursive</text>
|
||||
<rect x="308" y="296" width="30" height="13" rx="6.5" fill="#4C8BF5"/>
|
||||
<circle cx="331" cy="302.5" r="5" fill="#F5F6F7"/>
|
||||
<rect x="12" y="319" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="335" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="335" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="335" font-size="11.5" fill="#9AA0A8">index</text>
|
||||
<text x="326" y="335" font-size="11.5" fill="#E6E8EB" text-anchor="end">0</text>
|
||||
<rect x="12" y="348" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="364" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="364" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="364" font-size="11.5" fill="#9AA0A8">step</text>
|
||||
<text x="326" y="364" font-size="11.5" fill="#E6E8EB" text-anchor="end">1</text>
|
||||
<rect x="12" y="377" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="393" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="393" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="393" font-size="11.5" fill="#9AA0A8">repeat_count</text>
|
||||
<text x="326" y="393" font-size="11.5" fill="#E6E8EB" text-anchor="end">1</text>
|
||||
<rect x="12" y="406" width="336" height="46" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="22" y="422" font-size="11.5" fill="#9AA0A8">context_folder_names</text>
|
||||
<text x="22" y="442" font-size="11.5" fill="#E6E8EB" font-family="'JetBrains Mono', Consolas, 'Courier New', monospace">Nam ⏎ Nu</text>
|
||||
<rect x="12" y="457" width="336" height="46" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="22" y="473" font-size="11.5" fill="#9AA0A8">source_file_names <tspan fill="#6f747c" font-style="italic">opt</tspan></text>
|
||||
<text x="22" y="493" font-size="11.5" fill="#E6E8EB" font-family="'JetBrains Mono', Consolas, 'Courier New', monospace">…</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.8 KiB |
@@ -0,0 +1,22 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="360" height="185" viewBox="0 0 360 185" font-family="Inter, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" role="img" aria-label="Logic Gate (NH) node">
|
||||
<rect x="0.5" y="0.5" width="359" height="184" rx="10" fill="#26282c" stroke="#45484f"/>
|
||||
<path d="M0.5 10.5 a10 10 0 0 1 10 -10 h339 a10 10 0 0 1 10 10 v36 h-359 z" fill="#34373d"/>
|
||||
<circle cx="17" cy="17" r="4.5" fill="#8FD18F"/>
|
||||
<text x="28" y="22" font-size="14" font-weight="600" fill="#EEF0F3">Logic Gate (NH)</text>
|
||||
<text x="28" y="38" font-size="10.5" fill="#9AA0A8" font-family="'JetBrains Mono', Consolas, 'Courier New', monospace">NH_LogicGate · Logic</text>
|
||||
<text x="336" y="71.0" font-size="12" fill="#D7DADF" text-anchor="end">result</text>
|
||||
<circle cx="348" cy="67.0" r="5" fill="#F48FB1" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<rect x="12" y="86" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="22" y="102" font-size="11.5" fill="#9AA0A8">a</text>
|
||||
<rect x="308" y="92" width="30" height="13" rx="6.5" fill="#4a4d53"/>
|
||||
<circle cx="315" cy="98.5" r="5" fill="#F5F6F7"/>
|
||||
<rect x="12" y="115" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="131" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="131" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="131" font-size="11.5" fill="#9AA0A8">op</text>
|
||||
<text x="326" y="131" font-size="11.5" fill="#E6E8EB" text-anchor="end">AND</text>
|
||||
<rect x="12" y="144" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="22" y="160" font-size="11.5" fill="#9AA0A8">b <tspan fill="#6f747c" font-style="italic">opt</tspan></text>
|
||||
<rect x="308" y="150" width="30" height="13" rx="6.5" fill="#4a4d53"/>
|
||||
<circle cx="315" cy="156.5" r="5" fill="#F5F6F7"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.8 KiB |
@@ -0,0 +1,66 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="360" height="483" viewBox="0 0 360 483" font-family="Inter, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" role="img" aria-label="Load LoRA Clip Index (NH) node">
|
||||
<rect x="0.5" y="0.5" width="359" height="482" rx="10" fill="#26282c" stroke="#45484f"/>
|
||||
<path d="M0.5 10.5 a10 10 0 0 1 10 -10 h339 a10 10 0 0 1 10 10 v36 h-359 z" fill="#34373d"/>
|
||||
<circle cx="17" cy="17" r="4.5" fill="#8FD18F"/>
|
||||
<text x="28" y="22" font-size="14" font-weight="600" fill="#EEF0F3">Load LoRA Clip Index (NH)</text>
|
||||
<text x="28" y="38" font-size="10.5" fill="#9AA0A8" font-family="'JetBrains Mono', Consolas, 'Courier New', monospace">NH_LoraClipIndex · Loaders</text>
|
||||
<circle cx="12" cy="67.0" r="5" fill="#B39DDB" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="24" y="71.0" font-size="12" fill="#D7DADF">model</text>
|
||||
<text x="336" y="71.0" font-size="12" fill="#D7DADF" text-anchor="end">model</text>
|
||||
<circle cx="348" cy="67.0" r="5" fill="#B39DDB" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<circle cx="12" cy="89.0" r="5" fill="#FFD500" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="24" y="93.0" font-size="12" fill="#D7DADF">clip</text>
|
||||
<text x="336" y="93.0" font-size="12" fill="#D7DADF" text-anchor="end">clip</text>
|
||||
<circle cx="348" cy="89.0" r="5" fill="#FFD500" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="336" y="115.0" font-size="12" fill="#D7DADF" text-anchor="end">filename</text>
|
||||
<circle cx="348" cy="111.0" r="5" fill="#C5E1A5" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="336" y="137.0" font-size="12" fill="#D7DADF" text-anchor="end">index</text>
|
||||
<circle cx="348" cy="133.0" r="5" fill="#9CC2E5" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<rect x="12" y="152" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="168" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="168" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="168" font-size="11.5" fill="#9AA0A8">select_mode</text>
|
||||
<text x="326" y="168" font-size="11.5" fill="#E6E8EB" text-anchor="end">index</text>
|
||||
<rect x="12" y="181" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="197" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="197" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="197" font-size="11.5" fill="#9AA0A8">index</text>
|
||||
<text x="326" y="197" font-size="11.5" fill="#E6E8EB" text-anchor="end">1</text>
|
||||
<rect x="12" y="210" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="226" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="226" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="226" font-size="11.5" fill="#9AA0A8">lora_count</text>
|
||||
<text x="326" y="226" font-size="11.5" fill="#E6E8EB" text-anchor="end">5</text>
|
||||
<rect x="12" y="239" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="255" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="255" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="255" font-size="11.5" fill="#9AA0A8">strength_model</text>
|
||||
<text x="326" y="255" font-size="11.5" fill="#E6E8EB" text-anchor="end">1.00</text>
|
||||
<rect x="12" y="268" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="284" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="284" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="284" font-size="11.5" fill="#9AA0A8">strength_clip</text>
|
||||
<text x="326" y="284" font-size="11.5" fill="#E6E8EB" text-anchor="end">1.00</text>
|
||||
<rect x="12" y="297" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="313" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="313" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="313" font-size="11.5" fill="#9AA0A8">lora_1 <tspan fill="#6f747c" font-style="italic">opt</tspan></text>
|
||||
<text x="326" y="313" font-size="11.5" fill="#E6E8EB" text-anchor="end">(choose a file)</text>
|
||||
<rect x="12" y="326" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="22" y="342" font-size="11.5" fill="#9AA0A8">enable_1 <tspan fill="#6f747c" font-style="italic">opt</tspan></text>
|
||||
<rect x="308" y="332" width="30" height="13" rx="6.5" fill="#4a4d53"/>
|
||||
<circle cx="315" cy="338.5" r="5" fill="#F5F6F7"/>
|
||||
<rect x="12" y="355" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="371" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="371" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="371" font-size="11.5" fill="#9AA0A8">lora_2 <tspan fill="#6f747c" font-style="italic">opt</tspan></text>
|
||||
<text x="326" y="371" font-size="11.5" fill="#E6E8EB" text-anchor="end">(choose a file)</text>
|
||||
<rect x="12" y="384" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="22" y="400" font-size="11.5" fill="#9AA0A8">enable_2 <tspan fill="#6f747c" font-style="italic">opt</tspan></text>
|
||||
<rect x="308" y="390" width="30" height="13" rx="6.5" fill="#4a4d53"/>
|
||||
<circle cx="315" cy="396.5" r="5" fill="#F5F6F7"/>
|
||||
<rect x="12" y="413" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="180.0" y="429" font-size="11" fill="#9AA0A8" text-anchor="middle" font-style="italic">lora_3 … lora_64</text>
|
||||
<rect x="12" y="442" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="180.0" y="458" font-size="11" fill="#9AA0A8" text-anchor="middle" font-style="italic">enable_3 … enable_64</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 5.3 KiB |
@@ -0,0 +1,57 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="360" height="432" viewBox="0 0 360 432" font-family="Inter, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" role="img" aria-label="Load LoRA Model Index (NH) node">
|
||||
<rect x="0.5" y="0.5" width="359" height="431" rx="10" fill="#26282c" stroke="#45484f"/>
|
||||
<path d="M0.5 10.5 a10 10 0 0 1 10 -10 h339 a10 10 0 0 1 10 10 v36 h-359 z" fill="#34373d"/>
|
||||
<circle cx="17" cy="17" r="4.5" fill="#8FD18F"/>
|
||||
<text x="28" y="22" font-size="14" font-weight="600" fill="#EEF0F3">Load LoRA Model Index (NH)</text>
|
||||
<text x="28" y="38" font-size="10.5" fill="#9AA0A8" font-family="'JetBrains Mono', Consolas, 'Courier New', monospace">NH_LoraModelIndex · Loaders</text>
|
||||
<circle cx="12" cy="67.0" r="5" fill="#B39DDB" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="24" y="71.0" font-size="12" fill="#D7DADF">model</text>
|
||||
<text x="336" y="71.0" font-size="12" fill="#D7DADF" text-anchor="end">model</text>
|
||||
<circle cx="348" cy="67.0" r="5" fill="#B39DDB" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="336" y="93.0" font-size="12" fill="#D7DADF" text-anchor="end">filename</text>
|
||||
<circle cx="348" cy="89.0" r="5" fill="#C5E1A5" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="336" y="115.0" font-size="12" fill="#D7DADF" text-anchor="end">index</text>
|
||||
<circle cx="348" cy="111.0" r="5" fill="#9CC2E5" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<rect x="12" y="130" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="146" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="146" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="146" font-size="11.5" fill="#9AA0A8">select_mode</text>
|
||||
<text x="326" y="146" font-size="11.5" fill="#E6E8EB" text-anchor="end">index</text>
|
||||
<rect x="12" y="159" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="175" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="175" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="175" font-size="11.5" fill="#9AA0A8">index</text>
|
||||
<text x="326" y="175" font-size="11.5" fill="#E6E8EB" text-anchor="end">1</text>
|
||||
<rect x="12" y="188" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="204" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="204" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="204" font-size="11.5" fill="#9AA0A8">lora_count</text>
|
||||
<text x="326" y="204" font-size="11.5" fill="#E6E8EB" text-anchor="end">5</text>
|
||||
<rect x="12" y="217" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="233" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="233" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="233" font-size="11.5" fill="#9AA0A8">strength_model</text>
|
||||
<text x="326" y="233" font-size="11.5" fill="#E6E8EB" text-anchor="end">1.00</text>
|
||||
<rect x="12" y="246" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="262" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="262" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="262" font-size="11.5" fill="#9AA0A8">lora_1 <tspan fill="#6f747c" font-style="italic">opt</tspan></text>
|
||||
<text x="326" y="262" font-size="11.5" fill="#E6E8EB" text-anchor="end">(choose a file)</text>
|
||||
<rect x="12" y="275" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="22" y="291" font-size="11.5" fill="#9AA0A8">enable_1 <tspan fill="#6f747c" font-style="italic">opt</tspan></text>
|
||||
<rect x="308" y="281" width="30" height="13" rx="6.5" fill="#4a4d53"/>
|
||||
<circle cx="315" cy="287.5" r="5" fill="#F5F6F7"/>
|
||||
<rect x="12" y="304" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="320" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="320" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="320" font-size="11.5" fill="#9AA0A8">lora_2 <tspan fill="#6f747c" font-style="italic">opt</tspan></text>
|
||||
<text x="326" y="320" font-size="11.5" fill="#E6E8EB" text-anchor="end">(choose a file)</text>
|
||||
<rect x="12" y="333" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="22" y="349" font-size="11.5" fill="#9AA0A8">enable_2 <tspan fill="#6f747c" font-style="italic">opt</tspan></text>
|
||||
<rect x="308" y="339" width="30" height="13" rx="6.5" fill="#4a4d53"/>
|
||||
<circle cx="315" cy="345.5" r="5" fill="#F5F6F7"/>
|
||||
<rect x="12" y="362" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="180.0" y="378" font-size="11" fill="#9AA0A8" text-anchor="middle" font-style="italic">lora_3 … lora_64</text>
|
||||
<rect x="12" y="391" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="180.0" y="407" font-size="11" fill="#9AA0A8" text-anchor="middle" font-style="italic">enable_3 … enable_64</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.7 KiB |
@@ -0,0 +1,30 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="360" height="214" viewBox="0 0 360 214" font-family="Inter, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" role="img" aria-label="Mask Morphology (NH) node">
|
||||
<rect x="0.5" y="0.5" width="359" height="213" rx="10" fill="#26282c" stroke="#45484f"/>
|
||||
<path d="M0.5 10.5 a10 10 0 0 1 10 -10 h339 a10 10 0 0 1 10 10 v36 h-359 z" fill="#34373d"/>
|
||||
<circle cx="17" cy="17" r="4.5" fill="#8FD18F"/>
|
||||
<text x="28" y="22" font-size="14" font-weight="600" fill="#EEF0F3">Mask Morphology (NH)</text>
|
||||
<text x="28" y="38" font-size="10.5" fill="#9AA0A8" font-family="'JetBrains Mono', Consolas, 'Courier New', monospace">NH_MaskAdvancedMorphology · Mask</text>
|
||||
<circle cx="12" cy="67.0" r="5" fill="#81C784" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="24" y="71.0" font-size="12" fill="#D7DADF">mask</text>
|
||||
<text x="336" y="71.0" font-size="12" fill="#D7DADF" text-anchor="end">MASK</text>
|
||||
<circle cx="348" cy="67.0" r="5" fill="#81C784" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<rect x="12" y="86" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="102" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="102" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="102" font-size="11.5" fill="#9AA0A8">horizontal_expand</text>
|
||||
<text x="326" y="102" font-size="11.5" fill="#E6E8EB" text-anchor="end">0</text>
|
||||
<rect x="12" y="115" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="131" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="131" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="131" font-size="11.5" fill="#9AA0A8">vertical_expand</text>
|
||||
<text x="326" y="131" font-size="11.5" fill="#E6E8EB" text-anchor="end">0</text>
|
||||
<rect x="12" y="144" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="22" y="160" font-size="11.5" fill="#9AA0A8">fill_holes</text>
|
||||
<rect x="308" y="150" width="30" height="13" rx="6.5" fill="#4a4d53"/>
|
||||
<circle cx="315" cy="156.5" r="5" fill="#F5F6F7"/>
|
||||
<rect x="12" y="173" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="189" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="189" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="189" font-size="11.5" fill="#9AA0A8">blur_radius</text>
|
||||
<text x="326" y="189" font-size="11.5" fill="#E6E8EB" text-anchor="end">0.0</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.3 KiB |
@@ -0,0 +1,18 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="360" height="149" viewBox="0 0 360 149" font-family="Inter, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" role="img" aria-label="NH Mask Aspect Ratio Match node">
|
||||
<rect x="0.5" y="0.5" width="359" height="148" rx="10" fill="#26282c" stroke="#45484f"/>
|
||||
<path d="M0.5 10.5 a10 10 0 0 1 10 -10 h339 a10 10 0 0 1 10 10 v36 h-359 z" fill="#34373d"/>
|
||||
<circle cx="17" cy="17" r="4.5" fill="#8FD18F"/>
|
||||
<text x="28" y="22" font-size="14" font-weight="600" fill="#EEF0F3">NH Mask Aspect Ratio Match</text>
|
||||
<text x="28" y="38" font-size="10.5" fill="#9AA0A8" font-family="'JetBrains Mono', Consolas, 'Courier New', monospace">NH_MaskAspectRatioMatch · Mask</text>
|
||||
<circle cx="12" cy="67.0" r="5" fill="#81C784" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="24" y="71.0" font-size="12" fill="#D7DADF">target_ratio_mask</text>
|
||||
<text x="336" y="71.0" font-size="12" fill="#D7DADF" text-anchor="end">MASK</text>
|
||||
<circle cx="348" cy="67.0" r="5" fill="#81C784" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<circle cx="12" cy="89.0" r="5" fill="#81C784" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="24" y="93.0" font-size="12" fill="#D7DADF">mask_to_adjust</text>
|
||||
<rect x="12" y="108" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="124" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="124" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="124" font-size="11.5" fill="#9AA0A8">mode</text>
|
||||
<text x="326" y="124" font-size="11.5" fill="#E6E8EB" text-anchor="end">stretch</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
@@ -0,0 +1,23 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="360" height="222" viewBox="0 0 360 222" font-family="Inter, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" role="img" aria-label="Mask Properties (NH) node">
|
||||
<rect x="0.5" y="0.5" width="359" height="221" rx="10" fill="#26282c" stroke="#45484f"/>
|
||||
<path d="M0.5 10.5 a10 10 0 0 1 10 -10 h339 a10 10 0 0 1 10 10 v36 h-359 z" fill="#34373d"/>
|
||||
<circle cx="17" cy="17" r="4.5" fill="#8FD18F"/>
|
||||
<text x="28" y="22" font-size="14" font-weight="600" fill="#EEF0F3">Mask Properties (NH)</text>
|
||||
<text x="28" y="38" font-size="10.5" fill="#9AA0A8" font-family="'JetBrains Mono', Consolas, 'Courier New', monospace">NH_MaskProperties · Mask</text>
|
||||
<circle cx="12" cy="67.0" r="5" fill="#81C784" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="24" y="71.0" font-size="12" fill="#D7DADF">mask</text>
|
||||
<text x="336" y="71.0" font-size="12" fill="#D7DADF" text-anchor="end">width</text>
|
||||
<circle cx="348" cy="67.0" r="5" fill="#9CC2E5" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="336" y="93.0" font-size="12" fill="#D7DADF" text-anchor="end">height</text>
|
||||
<circle cx="348" cy="89.0" r="5" fill="#9CC2E5" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="336" y="115.0" font-size="12" fill="#D7DADF" text-anchor="end">x1</text>
|
||||
<circle cx="348" cy="111.0" r="5" fill="#9CC2E5" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="336" y="137.0" font-size="12" fill="#D7DADF" text-anchor="end">y1</text>
|
||||
<circle cx="348" cy="133.0" r="5" fill="#9CC2E5" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="336" y="159.0" font-size="12" fill="#D7DADF" text-anchor="end">x2</text>
|
||||
<circle cx="348" cy="155.0" r="5" fill="#9CC2E5" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="336" y="181.0" font-size="12" fill="#D7DADF" text-anchor="end">y2</text>
|
||||
<circle cx="348" cy="177.0" r="5" fill="#9CC2E5" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="336" y="203.0" font-size="12" fill="#D7DADF" text-anchor="end">bbox</text>
|
||||
<circle cx="348" cy="199.0" r="5" fill="#E0B272" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.0 KiB |
@@ -0,0 +1,33 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="360" height="236" viewBox="0 0 360 236" font-family="Inter, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" role="img" aria-label="NH Mask-Aware Resize node">
|
||||
<rect x="0.5" y="0.5" width="359" height="235" rx="10" fill="#26282c" stroke="#45484f"/>
|
||||
<path d="M0.5 10.5 a10 10 0 0 1 10 -10 h339 a10 10 0 0 1 10 10 v36 h-359 z" fill="#34373d"/>
|
||||
<circle cx="17" cy="17" r="4.5" fill="#8FD18F"/>
|
||||
<text x="28" y="22" font-size="14" font-weight="600" fill="#EEF0F3">NH Mask-Aware Resize</text>
|
||||
<text x="28" y="38" font-size="10.5" fill="#9AA0A8" font-family="'JetBrains Mono', Consolas, 'Courier New', monospace">NH_MaskResizeImage · Image</text>
|
||||
<circle cx="12" cy="67.0" r="5" fill="#64B5F6" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="24" y="71.0" font-size="12" fill="#D7DADF">image</text>
|
||||
<text x="336" y="71.0" font-size="12" fill="#D7DADF" text-anchor="end">image</text>
|
||||
<circle cx="348" cy="67.0" r="5" fill="#64B5F6" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<circle cx="12" cy="89.0" r="5" fill="#81C784" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="24" y="93.0" font-size="12" fill="#D7DADF">mask</text>
|
||||
<text x="336" y="93.0" font-size="12" fill="#D7DADF" text-anchor="end">mask</text>
|
||||
<circle cx="348" cy="89.0" r="5" fill="#81C784" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<rect x="12" y="108" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="124" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="124" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="124" font-size="11.5" fill="#9AA0A8">width</text>
|
||||
<text x="326" y="124" font-size="11.5" fill="#E6E8EB" text-anchor="end">512</text>
|
||||
<rect x="12" y="137" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="153" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="153" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="153" font-size="11.5" fill="#9AA0A8">height</text>
|
||||
<text x="326" y="153" font-size="11.5" fill="#E6E8EB" text-anchor="end">512</text>
|
||||
<rect x="12" y="166" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="182" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="182" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="182" font-size="11.5" fill="#9AA0A8">mode</text>
|
||||
<text x="326" y="182" font-size="11.5" fill="#E6E8EB" text-anchor="end">crop</text>
|
||||
<rect x="12" y="195" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="22" y="211" font-size="11.5" fill="#9AA0A8">pad_color_hex</text>
|
||||
<text x="338" y="211" font-size="11.5" fill="#E6E8EB" text-anchor="end">#000000</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.6 KiB |
@@ -0,0 +1,11 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="360" height="90" viewBox="0 0 360 90" font-family="Inter, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" role="img" aria-label="NH Create Box Mask node">
|
||||
<rect x="0.5" y="0.5" width="359" height="89" rx="10" fill="#26282c" stroke="#45484f"/>
|
||||
<path d="M0.5 10.5 a10 10 0 0 1 10 -10 h339 a10 10 0 0 1 10 10 v36 h-359 z" fill="#34373d"/>
|
||||
<circle cx="17" cy="17" r="4.5" fill="#8FD18F"/>
|
||||
<text x="28" y="22" font-size="14" font-weight="600" fill="#EEF0F3">NH Create Box Mask</text>
|
||||
<text x="28" y="38" font-size="10.5" fill="#9AA0A8" font-family="'JetBrains Mono', Consolas, 'Courier New', monospace">NH_MaskToBBox · Mask</text>
|
||||
<circle cx="12" cy="67.0" r="5" fill="#81C784" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="24" y="71.0" font-size="12" fill="#D7DADF">mask</text>
|
||||
<text x="336" y="71.0" font-size="12" fill="#D7DADF" text-anchor="end">box_mask</text>
|
||||
<circle cx="348" cy="67.0" r="5" fill="#81C784" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1005 B |
@@ -0,0 +1,41 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="360" height="316" viewBox="0 0 360 316" font-family="Inter, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" role="img" aria-label="Math Eval (NH) node">
|
||||
<rect x="0.5" y="0.5" width="359" height="315" rx="10" fill="#26282c" stroke="#45484f"/>
|
||||
<path d="M0.5 10.5 a10 10 0 0 1 10 -10 h339 a10 10 0 0 1 10 10 v36 h-359 z" fill="#34373d"/>
|
||||
<circle cx="17" cy="17" r="4.5" fill="#8FD18F"/>
|
||||
<text x="28" y="22" font-size="14" font-weight="600" fill="#EEF0F3">Math Eval (NH)</text>
|
||||
<text x="28" y="38" font-size="10.5" fill="#9AA0A8" font-family="'JetBrains Mono', Consolas, 'Courier New', monospace">NH_MathEval · Logic</text>
|
||||
<text x="336" y="71.0" font-size="12" fill="#D7DADF" text-anchor="end">result_float</text>
|
||||
<circle cx="348" cy="67.0" r="5" fill="#9CC2E5" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="336" y="93.0" font-size="12" fill="#D7DADF" text-anchor="end">result_int</text>
|
||||
<circle cx="348" cy="89.0" r="5" fill="#9CC2E5" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="336" y="115.0" font-size="12" fill="#D7DADF" text-anchor="end">result_string</text>
|
||||
<circle cx="348" cy="111.0" r="5" fill="#C5E1A5" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<rect x="12" y="130" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="22" y="146" font-size="11.5" fill="#9AA0A8">expression</text>
|
||||
<text x="338" y="146" font-size="11.5" fill="#E6E8EB" text-anchor="end">a + b</text>
|
||||
<rect x="12" y="159" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="175" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="175" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="175" font-size="11.5" fill="#9AA0A8">a <tspan fill="#6f747c" font-style="italic">opt</tspan></text>
|
||||
<text x="326" y="175" font-size="11.5" fill="#E6E8EB" text-anchor="end">0.00</text>
|
||||
<rect x="12" y="188" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="204" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="204" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="204" font-size="11.5" fill="#9AA0A8">b <tspan fill="#6f747c" font-style="italic">opt</tspan></text>
|
||||
<text x="326" y="204" font-size="11.5" fill="#E6E8EB" text-anchor="end">0.00</text>
|
||||
<rect x="12" y="217" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="233" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="233" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="233" font-size="11.5" fill="#9AA0A8">c <tspan fill="#6f747c" font-style="italic">opt</tspan></text>
|
||||
<text x="326" y="233" font-size="11.5" fill="#E6E8EB" text-anchor="end">0.00</text>
|
||||
<rect x="12" y="246" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="262" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="262" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="262" font-size="11.5" fill="#9AA0A8">d <tspan fill="#6f747c" font-style="italic">opt</tspan></text>
|
||||
<text x="326" y="262" font-size="11.5" fill="#E6E8EB" text-anchor="end">0.00</text>
|
||||
<rect x="12" y="275" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="291" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="291" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="291" font-size="11.5" fill="#9AA0A8">round_to <tspan fill="#6f747c" font-style="italic">opt</tspan></text>
|
||||
<text x="326" y="291" font-size="11.5" fill="#E6E8EB" text-anchor="end">-1</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.4 KiB |
@@ -0,0 +1,29 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="360" height="273" viewBox="0 0 360 273" font-family="Inter, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" role="img" aria-label="Multi-Slider (FLOAT) node">
|
||||
<rect x="0.5" y="0.5" width="359" height="272" rx="10" fill="#26282c" stroke="#45484f"/>
|
||||
<path d="M0.5 10.5 a10 10 0 0 1 10 -10 h339 a10 10 0 0 1 10 10 v36 h-359 z" fill="#34373d"/>
|
||||
<circle cx="17" cy="17" r="4.5" fill="#8FD18F"/>
|
||||
<text x="28" y="22" font-size="14" font-weight="600" fill="#EEF0F3">Multi-Slider (FLOAT)</text>
|
||||
<text x="28" y="38" font-size="10.5" fill="#9AA0A8" font-family="'JetBrains Mono', Consolas, 'Courier New', monospace">NH_MultiSliderFloat · Utils</text>
|
||||
<text x="336" y="71.0" font-size="12" fill="#D7DADF" text-anchor="end">Float 1</text>
|
||||
<circle cx="348" cy="67.0" r="5" fill="#9CC2E5" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="336" y="93.0" font-size="12" fill="#D7DADF" text-anchor="end">Float 2</text>
|
||||
<circle cx="348" cy="89.0" r="5" fill="#9CC2E5" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="336" y="115.0" font-size="12" fill="#D7DADF" text-anchor="end">Float 3</text>
|
||||
<circle cx="348" cy="111.0" r="5" fill="#9CC2E5" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="336" y="137.0" font-size="12" fill="#D7DADF" text-anchor="end">Float 4</text>
|
||||
<circle cx="348" cy="133.0" r="5" fill="#9CC2E5" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="336" y="159.0" font-size="12" fill="#D7DADF" text-anchor="end">Float 5</text>
|
||||
<circle cx="348" cy="155.0" r="5" fill="#9CC2E5" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<rect x="12" y="174" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="190" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="190" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="190" font-size="11.5" fill="#9AA0A8">float_1</text>
|
||||
<text x="326" y="190" font-size="11.5" fill="#E6E8EB" text-anchor="end">0.00</text>
|
||||
<rect x="12" y="203" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="219" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="219" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="219" font-size="11.5" fill="#9AA0A8">float_2</text>
|
||||
<text x="326" y="219" font-size="11.5" fill="#E6E8EB" text-anchor="end">0.00</text>
|
||||
<rect x="12" y="232" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="180.0" y="248" font-size="11" fill="#9AA0A8" text-anchor="middle" font-style="italic">float_3 … float_5</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.4 KiB |
@@ -0,0 +1,29 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="360" height="273" viewBox="0 0 360 273" font-family="Inter, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" role="img" aria-label="Multi-Slider (INT) node">
|
||||
<rect x="0.5" y="0.5" width="359" height="272" rx="10" fill="#26282c" stroke="#45484f"/>
|
||||
<path d="M0.5 10.5 a10 10 0 0 1 10 -10 h339 a10 10 0 0 1 10 10 v36 h-359 z" fill="#34373d"/>
|
||||
<circle cx="17" cy="17" r="4.5" fill="#8FD18F"/>
|
||||
<text x="28" y="22" font-size="14" font-weight="600" fill="#EEF0F3">Multi-Slider (INT)</text>
|
||||
<text x="28" y="38" font-size="10.5" fill="#9AA0A8" font-family="'JetBrains Mono', Consolas, 'Courier New', monospace">NH_MultiSliderInt · Utils</text>
|
||||
<text x="336" y="71.0" font-size="12" fill="#D7DADF" text-anchor="end">Int 1</text>
|
||||
<circle cx="348" cy="67.0" r="5" fill="#9CC2E5" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="336" y="93.0" font-size="12" fill="#D7DADF" text-anchor="end">Int 2</text>
|
||||
<circle cx="348" cy="89.0" r="5" fill="#9CC2E5" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="336" y="115.0" font-size="12" fill="#D7DADF" text-anchor="end">Int 3</text>
|
||||
<circle cx="348" cy="111.0" r="5" fill="#9CC2E5" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="336" y="137.0" font-size="12" fill="#D7DADF" text-anchor="end">Int 4</text>
|
||||
<circle cx="348" cy="133.0" r="5" fill="#9CC2E5" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="336" y="159.0" font-size="12" fill="#D7DADF" text-anchor="end">Int 5</text>
|
||||
<circle cx="348" cy="155.0" r="5" fill="#9CC2E5" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<rect x="12" y="174" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="190" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="190" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="190" font-size="11.5" fill="#9AA0A8">int_1</text>
|
||||
<text x="326" y="190" font-size="11.5" fill="#E6E8EB" text-anchor="end">0</text>
|
||||
<rect x="12" y="203" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="219" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="219" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="219" font-size="11.5" fill="#9AA0A8">int_2</text>
|
||||
<text x="326" y="219" font-size="11.5" fill="#E6E8EB" text-anchor="end">0</text>
|
||||
<rect x="12" y="232" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="180.0" y="248" font-size="11" fill="#9AA0A8" text-anchor="middle" font-style="italic">int_3 … int_5</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.4 KiB |
@@ -0,0 +1,17 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="360" height="171" viewBox="0 0 360 171" font-family="Inter, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" role="img" aria-label="Pack Universal (NH) node">
|
||||
<rect x="0.5" y="0.5" width="359" height="170" rx="10" fill="#26282c" stroke="#45484f"/>
|
||||
<path d="M0.5 10.5 a10 10 0 0 1 10 -10 h339 a10 10 0 0 1 10 10 v36 h-359 z" fill="#34373d"/>
|
||||
<circle cx="17" cy="17" r="4.5" fill="#8FD18F"/>
|
||||
<text x="28" y="22" font-size="14" font-weight="600" fill="#EEF0F3">Pack Universal (NH)</text>
|
||||
<text x="28" y="38" font-size="10.5" fill="#9AA0A8" font-family="'JetBrains Mono', Consolas, 'Courier New', monospace">NH_PackUniversal · Utils/Pipe</text>
|
||||
<circle cx="12" cy="67.0" r="5" fill="#CFD8DC" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="24" y="71.0" font-size="12" fill="#D7DADF">input_0 <tspan fill="#7d838c" font-style="italic">(optional)</tspan></text>
|
||||
<text x="336" y="71.0" font-size="12" fill="#D7DADF" text-anchor="end">package</text>
|
||||
<circle cx="348" cy="67.0" r="5" fill="#E0B272" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<circle cx="12" cy="89.0" r="5" fill="#CFD8DC" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="24" y="93.0" font-size="12" fill="#D7DADF">input_1 <tspan fill="#7d838c" font-style="italic">(optional)</tspan></text>
|
||||
<circle cx="12" cy="111.0" r="5" fill="#CFD8DC" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="24" y="115.0" font-size="12" fill="#D7DADF">input_2 <tspan fill="#7d838c" font-style="italic">(optional)</tspan></text>
|
||||
<rect x="12" y="130" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="180.0" y="146" font-size="11" fill="#9AA0A8" text-anchor="middle" font-style="italic">input_3 … input_9</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.7 KiB |
@@ -0,0 +1,70 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="360" height="519" viewBox="0 0 360 519" font-family="Inter, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" role="img" aria-label="Portrait Segment (NH) node">
|
||||
<rect x="0.5" y="0.5" width="359" height="518" rx="10" fill="#26282c" stroke="#45484f"/>
|
||||
<path d="M0.5 10.5 a10 10 0 0 1 10 -10 h339 a10 10 0 0 1 10 10 v36 h-359 z" fill="#34373d"/>
|
||||
<circle cx="17" cy="17" r="4.5" fill="#8FD18F"/>
|
||||
<text x="28" y="22" font-size="14" font-weight="600" fill="#EEF0F3">Portrait Segment (NH)</text>
|
||||
<text x="28" y="38" font-size="10.5" fill="#9AA0A8" font-family="'JetBrains Mono', Consolas, 'Courier New', monospace">NH_PortraitSegment · Vision</text>
|
||||
<circle cx="12" cy="67.0" r="5" fill="#64B5F6" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="24" y="71.0" font-size="12" fill="#D7DADF">image</text>
|
||||
<text x="336" y="71.0" font-size="12" fill="#D7DADF" text-anchor="end">mask</text>
|
||||
<circle cx="348" cy="67.0" r="5" fill="#81C784" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="336" y="93.0" font-size="12" fill="#D7DADF" text-anchor="end">cutout_rgba</text>
|
||||
<circle cx="348" cy="89.0" r="5" fill="#64B5F6" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="336" y="115.0" font-size="12" fill="#D7DADF" text-anchor="end">mask_image</text>
|
||||
<circle cx="348" cy="111.0" r="5" fill="#64B5F6" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<rect x="12" y="130" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="22" y="146" font-size="11.5" fill="#9AA0A8">face</text>
|
||||
<rect x="308" y="136" width="30" height="13" rx="6.5" fill="#4C8BF5"/>
|
||||
<circle cx="331" cy="142.5" r="5" fill="#F5F6F7"/>
|
||||
<rect x="12" y="159" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="22" y="175" font-size="11.5" fill="#9AA0A8">neck</text>
|
||||
<rect x="308" y="165" width="30" height="13" rx="6.5" fill="#4C8BF5"/>
|
||||
<circle cx="331" cy="171.5" r="5" fill="#F5F6F7"/>
|
||||
<rect x="12" y="188" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="22" y="204" font-size="11.5" fill="#9AA0A8">hair</text>
|
||||
<rect x="308" y="194" width="30" height="13" rx="6.5" fill="#4C8BF5"/>
|
||||
<circle cx="331" cy="200.5" r="5" fill="#F5F6F7"/>
|
||||
<rect x="12" y="217" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="22" y="233" font-size="11.5" fill="#9AA0A8">eyes</text>
|
||||
<rect x="308" y="223" width="30" height="13" rx="6.5" fill="#4C8BF5"/>
|
||||
<circle cx="331" cy="229.5" r="5" fill="#F5F6F7"/>
|
||||
<rect x="12" y="246" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="22" y="262" font-size="11.5" fill="#9AA0A8">lips</text>
|
||||
<rect x="308" y="252" width="30" height="13" rx="6.5" fill="#4C8BF5"/>
|
||||
<circle cx="331" cy="258.5" r="5" fill="#F5F6F7"/>
|
||||
<rect x="12" y="275" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="22" y="291" font-size="11.5" fill="#9AA0A8">nose</text>
|
||||
<rect x="308" y="281" width="30" height="13" rx="6.5" fill="#4C8BF5"/>
|
||||
<circle cx="331" cy="287.5" r="5" fill="#F5F6F7"/>
|
||||
<rect x="12" y="304" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="22" y="320" font-size="11.5" fill="#9AA0A8">eyebrows</text>
|
||||
<rect x="308" y="310" width="30" height="13" rx="6.5" fill="#4C8BF5"/>
|
||||
<circle cx="331" cy="316.5" r="5" fill="#F5F6F7"/>
|
||||
<rect x="12" y="333" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="22" y="349" font-size="11.5" fill="#9AA0A8">ears</text>
|
||||
<rect x="308" y="339" width="30" height="13" rx="6.5" fill="#4C8BF5"/>
|
||||
<circle cx="331" cy="345.5" r="5" fill="#F5F6F7"/>
|
||||
<rect x="12" y="362" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="22" y="378" font-size="11.5" fill="#9AA0A8">mouth_interior</text>
|
||||
<rect x="308" y="368" width="30" height="13" rx="6.5" fill="#4a4d53"/>
|
||||
<circle cx="315" cy="374.5" r="5" fill="#F5F6F7"/>
|
||||
<rect x="12" y="391" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="407" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="407" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="407" font-size="11.5" fill="#9AA0A8">mask_expand</text>
|
||||
<text x="326" y="407" font-size="11.5" fill="#E6E8EB" text-anchor="end">0</text>
|
||||
<rect x="12" y="420" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="436" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="436" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="436" font-size="11.5" fill="#9AA0A8">mask_blur</text>
|
||||
<text x="326" y="436" font-size="11.5" fill="#E6E8EB" text-anchor="end">0</text>
|
||||
<rect x="12" y="449" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="22" y="465" font-size="11.5" fill="#9AA0A8">fill_holes</text>
|
||||
<rect x="308" y="455" width="30" height="13" rx="6.5" fill="#4C8BF5"/>
|
||||
<circle cx="331" cy="461.5" r="5" fill="#F5F6F7"/>
|
||||
<rect x="12" y="478" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="494" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="494" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="494" font-size="11.5" fill="#9AA0A8">device</text>
|
||||
<text x="326" y="494" font-size="11.5" fill="#E6E8EB" text-anchor="end">cuda</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 5.1 KiB |
@@ -0,0 +1,24 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="360" height="207" viewBox="0 0 360 207" font-family="Inter, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" role="img" aria-label="Prompt Join (NH) node">
|
||||
<rect x="0.5" y="0.5" width="359" height="206" rx="10" fill="#26282c" stroke="#45484f"/>
|
||||
<path d="M0.5 10.5 a10 10 0 0 1 10 -10 h339 a10 10 0 0 1 10 10 v36 h-359 z" fill="#34373d"/>
|
||||
<circle cx="17" cy="17" r="4.5" fill="#8FD18F"/>
|
||||
<text x="28" y="22" font-size="14" font-weight="600" fill="#EEF0F3">Prompt Join (NH)</text>
|
||||
<text x="28" y="38" font-size="10.5" fill="#9AA0A8" font-family="'JetBrains Mono', Consolas, 'Courier New', monospace">NH_PromptJoin · Text</text>
|
||||
<circle cx="12" cy="67.0" r="5" fill="#C5E1A5" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="24" y="71.0" font-size="12" fill="#D7DADF">text_1 <tspan fill="#7d838c" font-style="italic">(optional)</tspan></text>
|
||||
<text x="336" y="71.0" font-size="12" fill="#D7DADF" text-anchor="end">result</text>
|
||||
<circle cx="348" cy="67.0" r="5" fill="#C5E1A5" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<circle cx="12" cy="89.0" r="5" fill="#C5E1A5" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="24" y="93.0" font-size="12" fill="#D7DADF">text_2 <tspan fill="#7d838c" font-style="italic">(optional)</tspan></text>
|
||||
<text x="336" y="93.0" font-size="12" fill="#D7DADF" text-anchor="end">count</text>
|
||||
<circle cx="348" cy="89.0" r="5" fill="#9CC2E5" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<rect x="12" y="108" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="22" y="124" font-size="11.5" fill="#9AA0A8">separator</text>
|
||||
<text x="338" y="124" font-size="11.5" fill="#E6E8EB" text-anchor="end">, </text>
|
||||
<rect x="12" y="137" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="22" y="153" font-size="11.5" fill="#9AA0A8">skip_empty</text>
|
||||
<rect x="308" y="143" width="30" height="13" rx="6.5" fill="#4C8BF5"/>
|
||||
<circle cx="331" cy="149.5" r="5" fill="#F5F6F7"/>
|
||||
<rect x="12" y="166" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="180.0" y="182" font-size="11" fill="#9AA0A8" text-anchor="middle" font-style="italic">text_3 … text_5</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.1 KiB |
@@ -0,0 +1,36 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="360" height="309" viewBox="0 0 360 309" font-family="Inter, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" role="img" aria-label="Prompt Scheduler (NH) node">
|
||||
<rect x="0.5" y="0.5" width="359" height="308" rx="10" fill="#26282c" stroke="#45484f"/>
|
||||
<path d="M0.5 10.5 a10 10 0 0 1 10 -10 h339 a10 10 0 0 1 10 10 v36 h-359 z" fill="#34373d"/>
|
||||
<circle cx="17" cy="17" r="4.5" fill="#8FD18F"/>
|
||||
<text x="28" y="22" font-size="14" font-weight="600" fill="#EEF0F3">Prompt Scheduler (NH)</text>
|
||||
<text x="28" y="38" font-size="10.5" fill="#9AA0A8" font-family="'JetBrains Mono', Consolas, 'Courier New', monospace">NH_PromptScheduler · Text</text>
|
||||
<text x="336" y="71.0" font-size="12" fill="#D7DADF" text-anchor="end">current_prompt</text>
|
||||
<circle cx="348" cy="67.0" r="5" fill="#C5E1A5" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="336" y="93.0" font-size="12" fill="#D7DADF" text-anchor="end">progress</text>
|
||||
<circle cx="348" cy="89.0" r="5" fill="#9CC2E5" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="336" y="115.0" font-size="12" fill="#D7DADF" text-anchor="end">step_index</text>
|
||||
<circle cx="348" cy="111.0" r="5" fill="#9CC2E5" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<rect x="12" y="130" width="336" height="46" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="22" y="146" font-size="11.5" fill="#9AA0A8">prompts</text>
|
||||
<text x="22" y="166" font-size="11.5" fill="#E6E8EB" font-family="'JetBrains Mono', Consolas, 'Courier New', monospace">prompt 1 ⏎ prompt 2 ⏎ prompt 3</text>
|
||||
<rect x="12" y="181" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="197" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="197" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="197" font-size="11.5" fill="#9AA0A8">current_step</text>
|
||||
<text x="326" y="197" font-size="11.5" fill="#E6E8EB" text-anchor="end">0</text>
|
||||
<rect x="12" y="210" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="226" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="226" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="226" font-size="11.5" fill="#9AA0A8">total_steps</text>
|
||||
<text x="326" y="226" font-size="11.5" fill="#E6E8EB" text-anchor="end">10</text>
|
||||
<rect x="12" y="239" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="255" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="255" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="255" font-size="11.5" fill="#9AA0A8">mode</text>
|
||||
<text x="326" y="255" font-size="11.5" fill="#E6E8EB" text-anchor="end">sequential</text>
|
||||
<rect x="12" y="268" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="284" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="284" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="284" font-size="11.5" fill="#9AA0A8">seed <tspan fill="#6f747c" font-style="italic">opt</tspan></text>
|
||||
<text x="326" y="284" font-size="11.5" fill="#E6E8EB" text-anchor="end">0</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.0 KiB |
@@ -0,0 +1,23 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="360" height="229" viewBox="0 0 360 229" font-family="Inter, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" role="img" aria-label="Prompt Template (NH) node">
|
||||
<rect x="0.5" y="0.5" width="359" height="228" rx="10" fill="#26282c" stroke="#45484f"/>
|
||||
<path d="M0.5 10.5 a10 10 0 0 1 10 -10 h339 a10 10 0 0 1 10 10 v36 h-359 z" fill="#34373d"/>
|
||||
<circle cx="17" cy="17" r="4.5" fill="#8FD18F"/>
|
||||
<text x="28" y="22" font-size="14" font-weight="600" fill="#EEF0F3">Prompt Template (NH)</text>
|
||||
<text x="28" y="38" font-size="10.5" fill="#9AA0A8" font-family="'JetBrains Mono', Consolas, 'Courier New', monospace">NH_PromptTemplate · Text</text>
|
||||
<circle cx="12" cy="67.0" r="5" fill="#C5E1A5" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="24" y="71.0" font-size="12" fill="#D7DADF">var1 <tspan fill="#7d838c" font-style="italic">(optional)</tspan></text>
|
||||
<text x="336" y="71.0" font-size="12" fill="#D7DADF" text-anchor="end">result</text>
|
||||
<circle cx="348" cy="67.0" r="5" fill="#C5E1A5" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<circle cx="12" cy="89.0" r="5" fill="#C5E1A5" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="24" y="93.0" font-size="12" fill="#D7DADF">var2 <tspan fill="#7d838c" font-style="italic">(optional)</tspan></text>
|
||||
<text x="336" y="93.0" font-size="12" fill="#D7DADF" text-anchor="end">missing_vars</text>
|
||||
<circle cx="348" cy="89.0" r="5" fill="#C5E1A5" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<rect x="12" y="108" width="336" height="46" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="22" y="124" font-size="11.5" fill="#9AA0A8">template</text>
|
||||
<text x="22" y="144" font-size="11.5" fill="#E6E8EB" font-family="'JetBrains Mono', Consolas, 'Courier New', monospace">a {color} {garment}, {style},…</text>
|
||||
<rect x="12" y="159" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="22" y="175" font-size="11.5" fill="#9AA0A8">var_names</text>
|
||||
<text x="338" y="175" font-size="11.5" fill="#E6E8EB" text-anchor="end">color,garment,style</text>
|
||||
<rect x="12" y="188" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="180.0" y="204" font-size="11" fill="#9AA0A8" text-anchor="middle" font-style="italic">var3 … var6</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.2 KiB |
@@ -0,0 +1,29 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="360" height="229" viewBox="0 0 360 229" font-family="Inter, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" role="img" aria-label="Random Choice (NH) node">
|
||||
<rect x="0.5" y="0.5" width="359" height="228" rx="10" fill="#26282c" stroke="#45484f"/>
|
||||
<path d="M0.5 10.5 a10 10 0 0 1 10 -10 h339 a10 10 0 0 1 10 10 v36 h-359 z" fill="#34373d"/>
|
||||
<circle cx="17" cy="17" r="4.5" fill="#8FD18F"/>
|
||||
<text x="28" y="22" font-size="14" font-weight="600" fill="#EEF0F3">Random Choice (NH)</text>
|
||||
<text x="28" y="38" font-size="10.5" fill="#9AA0A8" font-family="'JetBrains Mono', Consolas, 'Courier New', monospace">NH_RandomChoice · Logic</text>
|
||||
<circle cx="12" cy="67.0" r="5" fill="#CFD8DC" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="24" y="71.0" font-size="12" fill="#D7DADF">input_0 <tspan fill="#7d838c" font-style="italic">(optional)</tspan></text>
|
||||
<text x="336" y="71.0" font-size="12" fill="#D7DADF" text-anchor="end">result</text>
|
||||
<circle cx="348" cy="67.0" r="5" fill="#CFD8DC" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<circle cx="12" cy="89.0" r="5" fill="#CFD8DC" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="24" y="93.0" font-size="12" fill="#D7DADF">input_1 <tspan fill="#7d838c" font-style="italic">(optional)</tspan></text>
|
||||
<text x="336" y="93.0" font-size="12" fill="#D7DADF" text-anchor="end">picked_index</text>
|
||||
<circle cx="348" cy="89.0" r="5" fill="#9CC2E5" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<circle cx="12" cy="111.0" r="5" fill="#CFD8DC" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="24" y="115.0" font-size="12" fill="#D7DADF">input_2 <tspan fill="#7d838c" font-style="italic">(optional)</tspan></text>
|
||||
<text x="336" y="115.0" font-size="12" fill="#D7DADF" text-anchor="end">probabilities</text>
|
||||
<circle cx="348" cy="111.0" r="5" fill="#C5E1A5" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<rect x="12" y="130" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="146" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="146" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="146" font-size="11.5" fill="#9AA0A8">seed</text>
|
||||
<text x="326" y="146" font-size="11.5" fill="#E6E8EB" text-anchor="end">0</text>
|
||||
<rect x="12" y="159" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="22" y="175" font-size="11.5" fill="#9AA0A8">weights</text>
|
||||
<text x="338" y="175" font-size="11.5" fill="#E6E8EB" text-anchor="end">1,1,1,1,1,1</text>
|
||||
<rect x="12" y="188" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="180.0" y="204" font-size="11" fill="#9AA0A8" text-anchor="middle" font-style="italic">input_3 … input_5</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.6 KiB |
@@ -0,0 +1,53 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="360" height="426" viewBox="0 0 360 426" font-family="Inter, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" role="img" aria-label="NH Ratio Preset Image Resize node">
|
||||
<rect x="0.5" y="0.5" width="359" height="425" rx="10" fill="#26282c" stroke="#45484f"/>
|
||||
<path d="M0.5 10.5 a10 10 0 0 1 10 -10 h339 a10 10 0 0 1 10 10 v36 h-359 z" fill="#34373d"/>
|
||||
<circle cx="17" cy="17" r="4.5" fill="#8FD18F"/>
|
||||
<text x="28" y="22" font-size="14" font-weight="600" fill="#EEF0F3">NH Ratio Preset Image Resize</text>
|
||||
<text x="28" y="38" font-size="10.5" fill="#9AA0A8" font-family="'JetBrains Mono', Consolas, 'Courier New', monospace">NH_RatioPresetImageResize · Resolution</text>
|
||||
<circle cx="12" cy="67.0" r="5" fill="#64B5F6" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="24" y="71.0" font-size="12" fill="#D7DADF">image</text>
|
||||
<text x="336" y="71.0" font-size="12" fill="#D7DADF" text-anchor="end">image</text>
|
||||
<circle cx="348" cy="67.0" r="5" fill="#64B5F6" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="336" y="93.0" font-size="12" fill="#D7DADF" text-anchor="end">width</text>
|
||||
<circle cx="348" cy="89.0" r="5" fill="#9CC2E5" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="336" y="115.0" font-size="12" fill="#D7DADF" text-anchor="end">height</text>
|
||||
<circle cx="348" cy="111.0" r="5" fill="#9CC2E5" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="336" y="137.0" font-size="12" fill="#D7DADF" text-anchor="end">info</text>
|
||||
<circle cx="348" cy="133.0" r="5" fill="#C5E1A5" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="336" y="159.0" font-size="12" fill="#D7DADF" text-anchor="end">source_aspect_ratio</text>
|
||||
<circle cx="348" cy="155.0" r="5" fill="#9CC2E5" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="336" y="181.0" font-size="12" fill="#D7DADF" text-anchor="end">target_aspect_ratio</text>
|
||||
<circle cx="348" cy="177.0" r="5" fill="#9CC2E5" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="336" y="203.0" font-size="12" fill="#D7DADF" text-anchor="end">selected_preset</text>
|
||||
<circle cx="348" cy="199.0" r="5" fill="#C5E1A5" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="336" y="225.0" font-size="12" fill="#D7DADF" text-anchor="end">resize_data</text>
|
||||
<circle cx="348" cy="221.0" r="5" fill="#E0B272" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<rect x="12" y="240" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="256" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="256" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="256" font-size="11.5" fill="#9AA0A8">model_family</text>
|
||||
<text x="326" y="256" font-size="11.5" fill="#E6E8EB" text-anchor="end">FLUX.2 Klein 9B</text>
|
||||
<rect x="12" y="269" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="285" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="285" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="285" font-size="11.5" fill="#9AA0A8">resolution_level</text>
|
||||
<text x="326" y="285" font-size="11.5" fill="#E6E8EB" text-anchor="end">1 MP</text>
|
||||
<rect x="12" y="298" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="314" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="314" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="314" font-size="11.5" fill="#9AA0A8">target_ratio</text>
|
||||
<text x="326" y="314" font-size="11.5" fill="#E6E8EB" text-anchor="end">1:1</text>
|
||||
<rect x="12" y="327" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="343" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="343" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="343" font-size="11.5" fill="#9AA0A8">resize_mode</text>
|
||||
<text x="326" y="343" font-size="11.5" fill="#E6E8EB" text-anchor="end">crop</text>
|
||||
<rect x="12" y="356" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="372" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="372" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="372" font-size="11.5" fill="#9AA0A8">resize_method</text>
|
||||
<text x="326" y="372" font-size="11.5" fill="#E6E8EB" text-anchor="end">lanczos</text>
|
||||
<rect x="12" y="385" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="22" y="401" font-size="11.5" fill="#9AA0A8">pad_color_hex</text>
|
||||
<text x="338" y="401" font-size="11.5" fill="#E6E8EB" text-anchor="end">#FFFFFF</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.3 KiB |
@@ -0,0 +1,29 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="360" height="302" viewBox="0 0 360 302" font-family="Inter, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" role="img" aria-label="Regex Extract (NH) node">
|
||||
<rect x="0.5" y="0.5" width="359" height="301" rx="10" fill="#26282c" stroke="#45484f"/>
|
||||
<path d="M0.5 10.5 a10 10 0 0 1 10 -10 h339 a10 10 0 0 1 10 10 v36 h-359 z" fill="#34373d"/>
|
||||
<circle cx="17" cy="17" r="4.5" fill="#8FD18F"/>
|
||||
<text x="28" y="22" font-size="14" font-weight="600" fill="#EEF0F3">Regex Extract (NH)</text>
|
||||
<text x="28" y="38" font-size="10.5" fill="#9AA0A8" font-family="'JetBrains Mono', Consolas, 'Courier New', monospace">NH_RegexExtract · Text</text>
|
||||
<text x="336" y="71.0" font-size="12" fill="#D7DADF" text-anchor="end">result</text>
|
||||
<circle cx="348" cy="67.0" r="5" fill="#C5E1A5" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="336" y="93.0" font-size="12" fill="#D7DADF" text-anchor="end">matched</text>
|
||||
<circle cx="348" cy="89.0" r="5" fill="#F48FB1" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="336" y="115.0" font-size="12" fill="#D7DADF" text-anchor="end">groups</text>
|
||||
<circle cx="348" cy="111.0" r="5" fill="#C5E1A5" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="336" y="137.0" font-size="12" fill="#D7DADF" text-anchor="end">count</text>
|
||||
<circle cx="348" cy="133.0" r="5" fill="#9CC2E5" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<rect x="12" y="152" width="336" height="46" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="22" y="168" font-size="11.5" fill="#9AA0A8">text</text>
|
||||
<text x="22" y="188" font-size="11.5" fill="#E6E8EB" font-family="'JetBrains Mono', Consolas, 'Courier New', monospace">…</text>
|
||||
<rect x="12" y="203" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="22" y="219" font-size="11.5" fill="#9AA0A8">pattern</text>
|
||||
<text x="338" y="219" font-size="11.5" fill="#E6E8EB" text-anchor="end">(\d+)</text>
|
||||
<rect x="12" y="232" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="248" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="248" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="248" font-size="11.5" fill="#9AA0A8">mode</text>
|
||||
<text x="326" y="248" font-size="11.5" fill="#E6E8EB" text-anchor="end">match</text>
|
||||
<rect x="12" y="261" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="22" y="277" font-size="11.5" fill="#9AA0A8">replacement <tspan fill="#6f747c" font-style="italic">opt</tspan></text>
|
||||
<text x="338" y="277" font-size="11.5" fill="#E6E8EB" text-anchor="end"></text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.5 KiB |
@@ -0,0 +1,29 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="360" height="222" viewBox="0 0 360 222" font-family="Inter, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" role="img" aria-label="NH Remove Resize Padding node">
|
||||
<rect x="0.5" y="0.5" width="359" height="221" rx="10" fill="#26282c" stroke="#45484f"/>
|
||||
<path d="M0.5 10.5 a10 10 0 0 1 10 -10 h339 a10 10 0 0 1 10 10 v36 h-359 z" fill="#34373d"/>
|
||||
<circle cx="17" cy="17" r="4.5" fill="#8FD18F"/>
|
||||
<text x="28" y="22" font-size="14" font-weight="600" fill="#EEF0F3">NH Remove Resize Padding</text>
|
||||
<text x="28" y="38" font-size="10.5" fill="#9AA0A8" font-family="'JetBrains Mono', Consolas, 'Courier New', monospace">NH_RemoveResizePadding · Resolution</text>
|
||||
<circle cx="12" cy="67.0" r="5" fill="#64B5F6" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="24" y="71.0" font-size="12" fill="#D7DADF">image</text>
|
||||
<text x="336" y="71.0" font-size="12" fill="#D7DADF" text-anchor="end">image</text>
|
||||
<circle cx="348" cy="67.0" r="5" fill="#64B5F6" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<circle cx="12" cy="89.0" r="5" fill="#E0B272" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="24" y="93.0" font-size="12" fill="#D7DADF">resize_data</text>
|
||||
<text x="336" y="93.0" font-size="12" fill="#D7DADF" text-anchor="end">width</text>
|
||||
<circle cx="348" cy="89.0" r="5" fill="#9CC2E5" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="336" y="115.0" font-size="12" fill="#D7DADF" text-anchor="end">height</text>
|
||||
<circle cx="348" cy="111.0" r="5" fill="#9CC2E5" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="336" y="137.0" font-size="12" fill="#D7DADF" text-anchor="end">info</text>
|
||||
<circle cx="348" cy="133.0" r="5" fill="#C5E1A5" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<rect x="12" y="152" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="168" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="168" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="168" font-size="11.5" fill="#9AA0A8">output_size</text>
|
||||
<text x="326" y="168" font-size="11.5" fill="#E6E8EB" text-anchor="end">content_region</text>
|
||||
<rect x="12" y="181" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="197" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="197" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="197" font-size="11.5" fill="#9AA0A8">resize_method</text>
|
||||
<text x="326" y="197" font-size="11.5" fill="#E6E8EB" text-anchor="end">lanczos</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.4 KiB |
@@ -0,0 +1,22 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="360" height="171" viewBox="0 0 360 171" font-family="Inter, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" role="img" aria-label="NH Restore Image Geometry node">
|
||||
<rect x="0.5" y="0.5" width="359" height="170" rx="10" fill="#26282c" stroke="#45484f"/>
|
||||
<path d="M0.5 10.5 a10 10 0 0 1 10 -10 h339 a10 10 0 0 1 10 10 v36 h-359 z" fill="#34373d"/>
|
||||
<circle cx="17" cy="17" r="4.5" fill="#8FD18F"/>
|
||||
<text x="28" y="22" font-size="14" font-weight="600" fill="#EEF0F3">NH Restore Image Geometry</text>
|
||||
<text x="28" y="38" font-size="10.5" fill="#9AA0A8" font-family="'JetBrains Mono', Consolas, 'Courier New', monospace">NH_RestoreImageGeometry · Resolution</text>
|
||||
<circle cx="12" cy="67.0" r="5" fill="#64B5F6" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="24" y="71.0" font-size="12" fill="#D7DADF">image</text>
|
||||
<text x="336" y="71.0" font-size="12" fill="#D7DADF" text-anchor="end">image</text>
|
||||
<circle cx="348" cy="67.0" r="5" fill="#64B5F6" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<circle cx="12" cy="89.0" r="5" fill="#64B5F6" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="24" y="93.0" font-size="12" fill="#D7DADF">original_image</text>
|
||||
<text x="336" y="93.0" font-size="12" fill="#D7DADF" text-anchor="end">mask</text>
|
||||
<circle cx="348" cy="89.0" r="5" fill="#81C784" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<circle cx="12" cy="111.0" r="5" fill="#E0B272" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="24" y="115.0" font-size="12" fill="#D7DADF">resize_data</text>
|
||||
<rect x="12" y="130" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="146" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="146" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="146" font-size="11.5" fill="#9AA0A8">resize_method</text>
|
||||
<text x="326" y="146" font-size="11.5" fill="#E6E8EB" text-anchor="end">bicubic</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.8 KiB |
@@ -0,0 +1,42 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="360" height="338" viewBox="0 0 360 338" font-family="Inter, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" role="img" aria-label="SAM3 Segment (NH) node">
|
||||
<rect x="0.5" y="0.5" width="359" height="337" rx="10" fill="#26282c" stroke="#45484f"/>
|
||||
<path d="M0.5 10.5 a10 10 0 0 1 10 -10 h339 a10 10 0 0 1 10 10 v36 h-359 z" fill="#34373d"/>
|
||||
<circle cx="17" cy="17" r="4.5" fill="#8FD18F"/>
|
||||
<text x="28" y="22" font-size="14" font-weight="600" fill="#EEF0F3">SAM3 Segment (NH)</text>
|
||||
<text x="28" y="38" font-size="10.5" fill="#9AA0A8" font-family="'JetBrains Mono', Consolas, 'Courier New', monospace">NH_SAM3Segment · Vision</text>
|
||||
<circle cx="12" cy="67.0" r="5" fill="#64B5F6" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="24" y="71.0" font-size="12" fill="#D7DADF">image</text>
|
||||
<text x="336" y="71.0" font-size="12" fill="#D7DADF" text-anchor="end">segs</text>
|
||||
<circle cx="348" cy="67.0" r="5" fill="#E0B272" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="336" y="93.0" font-size="12" fill="#D7DADF" text-anchor="end">mask</text>
|
||||
<circle cx="348" cy="89.0" r="5" fill="#81C784" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="336" y="115.0" font-size="12" fill="#D7DADF" text-anchor="end">count</text>
|
||||
<circle cx="348" cy="111.0" r="5" fill="#9CC2E5" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<rect x="12" y="130" width="336" height="46" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="22" y="146" font-size="11.5" fill="#9AA0A8">concepts</text>
|
||||
<text x="22" y="166" font-size="11.5" fill="#E6E8EB" font-family="'JetBrains Mono', Consolas, 'Courier New', monospace">person</text>
|
||||
<rect x="12" y="181" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="197" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="197" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="197" font-size="11.5" fill="#9AA0A8">threshold</text>
|
||||
<text x="326" y="197" font-size="11.5" fill="#E6E8EB" text-anchor="end">0.50</text>
|
||||
<rect x="12" y="210" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="226" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="226" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="226" font-size="11.5" fill="#9AA0A8">detail_level</text>
|
||||
<text x="326" y="226" font-size="11.5" fill="#E6E8EB" text-anchor="end">balanced</text>
|
||||
<rect x="12" y="239" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="255" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="255" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="255" font-size="11.5" fill="#9AA0A8">max_per_concept</text>
|
||||
<text x="326" y="255" font-size="11.5" fill="#E6E8EB" text-anchor="end">0</text>
|
||||
<rect x="12" y="268" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="284" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="284" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="284" font-size="11.5" fill="#9AA0A8">checkpoint</text>
|
||||
<text x="326" y="284" font-size="11.5" fill="#E6E8EB" text-anchor="end">(choose a file)</text>
|
||||
<rect x="12" y="297" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="22" y="313" font-size="11.5" fill="#9AA0A8">keep_model_loaded</text>
|
||||
<rect x="308" y="303" width="30" height="13" rx="6.5" fill="#4a4d53"/>
|
||||
<circle cx="315" cy="309.5" r="5" fill="#F5F6F7"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.3 KiB |
@@ -0,0 +1,46 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="360" height="374" viewBox="0 0 360 374" font-family="Inter, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" role="img" aria-label="Save Image Path (NH) node">
|
||||
<rect x="0.5" y="0.5" width="359" height="373" rx="10" fill="#26282c" stroke="#45484f"/>
|
||||
<path d="M0.5 10.5 a10 10 0 0 1 10 -10 h339 a10 10 0 0 1 10 10 v36 h-359 z" fill="#34373d"/>
|
||||
<circle cx="17" cy="17" r="4.5" fill="#F6AD55"/>
|
||||
<text x="28" y="22" font-size="14" font-weight="600" fill="#EEF0F3">Save Image Path (NH)</text>
|
||||
<text x="28" y="38" font-size="10.5" fill="#9AA0A8" font-family="'JetBrains Mono', Consolas, 'Courier New', monospace">NH_SaveImagePath · Image</text>
|
||||
<circle cx="12" cy="67.0" r="5" fill="#64B5F6" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="24" y="71.0" font-size="12" fill="#D7DADF">images</text>
|
||||
<text x="336" y="71.0" font-size="12" fill="#D7DADF" text-anchor="end">filenames</text>
|
||||
<circle cx="348" cy="67.0" r="5" fill="#C5E1A5" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="336" y="93.0" font-size="12" fill="#D7DADF" text-anchor="end">path</text>
|
||||
<circle cx="348" cy="89.0" r="5" fill="#C5E1A5" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="336" y="115.0" font-size="12" fill="#D7DADF" text-anchor="end">saved_count</text>
|
||||
<circle cx="348" cy="111.0" r="5" fill="#9CC2E5" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<rect x="12" y="130" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="22" y="146" font-size="11.5" fill="#9AA0A8">folder_path</text>
|
||||
<text x="338" y="146" font-size="11.5" fill="#E6E8EB" text-anchor="end"></text>
|
||||
<rect x="12" y="159" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="22" y="175" font-size="11.5" fill="#9AA0A8">create_new_folder</text>
|
||||
<rect x="308" y="165" width="30" height="13" rx="6.5" fill="#4a4d53"/>
|
||||
<circle cx="315" cy="171.5" r="5" fill="#F5F6F7"/>
|
||||
<rect x="12" y="188" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="22" y="204" font-size="11.5" fill="#9AA0A8">new_folder_name</text>
|
||||
<text x="338" y="204" font-size="11.5" fill="#E6E8EB" text-anchor="end"></text>
|
||||
<rect x="12" y="217" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="22" y="233" font-size="11.5" fill="#9AA0A8">filename_prefix</text>
|
||||
<text x="338" y="233" font-size="11.5" fill="#E6E8EB" text-anchor="end">NH</text>
|
||||
<rect x="12" y="246" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="22" y="262" font-size="11.5" fill="#9AA0A8">number_suffix</text>
|
||||
<rect x="308" y="252" width="30" height="13" rx="6.5" fill="#4C8BF5"/>
|
||||
<circle cx="331" cy="258.5" r="5" fill="#F5F6F7"/>
|
||||
<rect x="12" y="275" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="22" y="291" font-size="11.5" fill="#9AA0A8">preview_image</text>
|
||||
<rect x="308" y="281" width="30" height="13" rx="6.5" fill="#4a4d53"/>
|
||||
<circle cx="315" cy="287.5" r="5" fill="#F5F6F7"/>
|
||||
<rect x="12" y="304" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="320" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="320" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="320" font-size="11.5" fill="#9AA0A8">file_format</text>
|
||||
<text x="326" y="320" font-size="11.5" fill="#E6E8EB" text-anchor="end">png</text>
|
||||
<rect x="12" y="333" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="349" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="349" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="349" font-size="11.5" fill="#9AA0A8">dpi</text>
|
||||
<text x="326" y="349" font-size="11.5" fill="#E6E8EB" text-anchor="end">300</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.6 KiB |
@@ -0,0 +1,32 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="360" height="258" viewBox="0 0 360 258" font-family="Inter, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" role="img" aria-label="NH Save Large Image node">
|
||||
<rect x="0.5" y="0.5" width="359" height="257" rx="10" fill="#26282c" stroke="#45484f"/>
|
||||
<path d="M0.5 10.5 a10 10 0 0 1 10 -10 h339 a10 10 0 0 1 10 10 v36 h-359 z" fill="#34373d"/>
|
||||
<circle cx="17" cy="17" r="4.5" fill="#F6AD55"/>
|
||||
<text x="28" y="22" font-size="14" font-weight="600" fill="#EEF0F3">NH Save Large Image</text>
|
||||
<text x="28" y="38" font-size="10.5" fill="#9AA0A8" font-family="'JetBrains Mono', Consolas, 'Courier New', monospace">NH_SaveLargeImage · Image</text>
|
||||
<circle cx="12" cy="67.0" r="5" fill="#64B5F6" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="24" y="71.0" font-size="12" fill="#D7DADF">image</text>
|
||||
<text x="336" y="71.0" font-size="12" fill="#D7DADF" text-anchor="end">image</text>
|
||||
<circle cx="348" cy="67.0" r="5" fill="#64B5F6" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="336" y="93.0" font-size="12" fill="#D7DADF" text-anchor="end">file_path</text>
|
||||
<circle cx="348" cy="89.0" r="5" fill="#C5E1A5" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="336" y="115.0" font-size="12" fill="#D7DADF" text-anchor="end">saved_count</text>
|
||||
<circle cx="348" cy="111.0" r="5" fill="#9CC2E5" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<rect x="12" y="130" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="22" y="146" font-size="11.5" fill="#9AA0A8">filename_prefix</text>
|
||||
<text x="338" y="146" font-size="11.5" fill="#E6E8EB" text-anchor="end">NH_Large</text>
|
||||
<rect x="12" y="159" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="175" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="175" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="175" font-size="11.5" fill="#9AA0A8">file_format</text>
|
||||
<text x="326" y="175" font-size="11.5" fill="#E6E8EB" text-anchor="end">png</text>
|
||||
<rect x="12" y="188" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="204" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="204" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="204" font-size="11.5" fill="#9AA0A8">quality</text>
|
||||
<text x="326" y="204" font-size="11.5" fill="#E6E8EB" text-anchor="end">95</text>
|
||||
<rect x="12" y="217" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="22" y="233" font-size="11.5" fill="#9AA0A8">embed_metadata</text>
|
||||
<rect x="308" y="223" width="30" height="13" rx="6.5" fill="#4C8BF5"/>
|
||||
<circle cx="331" cy="229.5" r="5" fill="#F5F6F7"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.6 KiB |
@@ -0,0 +1,14 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="360" height="149" viewBox="0 0 360 149" font-family="Inter, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" role="img" aria-label="Segs Compose (NH) node">
|
||||
<rect x="0.5" y="0.5" width="359" height="148" rx="10" fill="#26282c" stroke="#45484f"/>
|
||||
<path d="M0.5 10.5 a10 10 0 0 1 10 -10 h339 a10 10 0 0 1 10 10 v36 h-359 z" fill="#34373d"/>
|
||||
<circle cx="17" cy="17" r="4.5" fill="#8FD18F"/>
|
||||
<text x="28" y="22" font-size="14" font-weight="600" fill="#EEF0F3">Segs Compose (NH)</text>
|
||||
<text x="28" y="38" font-size="10.5" fill="#9AA0A8" font-family="'JetBrains Mono', Consolas, 'Courier New', monospace">NH_SegsCompose · Vision</text>
|
||||
<circle cx="12" cy="67.0" r="5" fill="#E0B272" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="24" y="71.0" font-size="12" fill="#D7DADF">segs</text>
|
||||
<text x="336" y="71.0" font-size="12" fill="#D7DADF" text-anchor="end">mask</text>
|
||||
<circle cx="348" cy="67.0" r="5" fill="#81C784" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<rect x="12" y="86" width="336" height="46" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="22" y="102" font-size="11.5" fill="#9AA0A8">expression</text>
|
||||
<text x="22" y="122" font-size="11.5" fill="#E6E8EB" font-family="'JetBrains Mono', Consolas, 'Courier New', monospace">all</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
@@ -0,0 +1,13 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="360" height="112" viewBox="0 0 360 112" font-family="Inter, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" role="img" aria-label="Segs Preview (NH) node">
|
||||
<rect x="0.5" y="0.5" width="359" height="111" rx="10" fill="#26282c" stroke="#45484f"/>
|
||||
<path d="M0.5 10.5 a10 10 0 0 1 10 -10 h339 a10 10 0 0 1 10 10 v36 h-359 z" fill="#34373d"/>
|
||||
<circle cx="17" cy="17" r="4.5" fill="#8FD18F"/>
|
||||
<text x="28" y="22" font-size="14" font-weight="600" fill="#EEF0F3">Segs Preview (NH)</text>
|
||||
<text x="28" y="38" font-size="10.5" fill="#9AA0A8" font-family="'JetBrains Mono', Consolas, 'Courier New', monospace">NH_SegsPreview · Vision</text>
|
||||
<circle cx="12" cy="67.0" r="5" fill="#64B5F6" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="24" y="71.0" font-size="12" fill="#D7DADF">image</text>
|
||||
<text x="336" y="71.0" font-size="12" fill="#D7DADF" text-anchor="end">preview</text>
|
||||
<circle cx="348" cy="67.0" r="5" fill="#64B5F6" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<circle cx="12" cy="89.0" r="5" fill="#E0B272" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="24" y="93.0" font-size="12" fill="#D7DADF">segs</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1,48 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="360" height="345" viewBox="0 0 360 345" font-family="Inter, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" role="img" aria-label="Segs Select (NH) node">
|
||||
<rect x="0.5" y="0.5" width="359" height="344" rx="10" fill="#26282c" stroke="#45484f"/>
|
||||
<path d="M0.5 10.5 a10 10 0 0 1 10 -10 h339 a10 10 0 0 1 10 10 v36 h-359 z" fill="#34373d"/>
|
||||
<circle cx="17" cy="17" r="4.5" fill="#8FD18F"/>
|
||||
<text x="28" y="22" font-size="14" font-weight="600" fill="#EEF0F3">Segs Select (NH)</text>
|
||||
<text x="28" y="38" font-size="10.5" fill="#9AA0A8" font-family="'JetBrains Mono', Consolas, 'Courier New', monospace">NH_SegsSelect · Vision</text>
|
||||
<circle cx="12" cy="67.0" r="5" fill="#E0B272" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="24" y="71.0" font-size="12" fill="#D7DADF">segs</text>
|
||||
<text x="336" y="71.0" font-size="12" fill="#D7DADF" text-anchor="end">segs</text>
|
||||
<circle cx="348" cy="67.0" r="5" fill="#E0B272" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="336" y="93.0" font-size="12" fill="#D7DADF" text-anchor="end">mask</text>
|
||||
<circle cx="348" cy="89.0" r="5" fill="#81C784" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="336" y="115.0" font-size="12" fill="#D7DADF" text-anchor="end">count</text>
|
||||
<circle cx="348" cy="111.0" r="5" fill="#9CC2E5" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<rect x="12" y="130" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="22" y="146" font-size="11.5" fill="#9AA0A8">labels</text>
|
||||
<text x="338" y="146" font-size="11.5" fill="#E6E8EB" text-anchor="end"></text>
|
||||
<rect x="12" y="159" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="175" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="175" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="175" font-size="11.5" fill="#9AA0A8">min_score</text>
|
||||
<text x="326" y="175" font-size="11.5" fill="#E6E8EB" text-anchor="end">0.00</text>
|
||||
<rect x="12" y="188" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="204" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="204" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="204" font-size="11.5" fill="#9AA0A8">min_area</text>
|
||||
<text x="326" y="204" font-size="11.5" fill="#E6E8EB" text-anchor="end">0.0000</text>
|
||||
<rect x="12" y="217" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="233" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="233" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="233" font-size="11.5" fill="#9AA0A8">max_area</text>
|
||||
<text x="326" y="233" font-size="11.5" fill="#E6E8EB" text-anchor="end">1.0000</text>
|
||||
<rect x="12" y="246" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="262" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="262" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="262" font-size="11.5" fill="#9AA0A8">sort_by</text>
|
||||
<text x="326" y="262" font-size="11.5" fill="#E6E8EB" text-anchor="end">score</text>
|
||||
<rect x="12" y="275" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="291" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="291" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="291" font-size="11.5" fill="#9AA0A8">start</text>
|
||||
<text x="326" y="291" font-size="11.5" fill="#E6E8EB" text-anchor="end">0</text>
|
||||
<rect x="12" y="304" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="320" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="320" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="320" font-size="11.5" fill="#9AA0A8">count</text>
|
||||
<text x="326" y="320" font-size="11.5" fill="#E6E8EB" text-anchor="end">0</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.7 KiB |
@@ -0,0 +1,21 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="360" height="156" viewBox="0 0 360 156" font-family="Inter, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" role="img" aria-label="Segs To Impact SEGS (NH) node">
|
||||
<rect x="0.5" y="0.5" width="359" height="155" rx="10" fill="#26282c" stroke="#45484f"/>
|
||||
<path d="M0.5 10.5 a10 10 0 0 1 10 -10 h339 a10 10 0 0 1 10 10 v36 h-359 z" fill="#34373d"/>
|
||||
<circle cx="17" cy="17" r="4.5" fill="#8FD18F"/>
|
||||
<text x="28" y="22" font-size="14" font-weight="600" fill="#EEF0F3">Segs To Impact SEGS (NH)</text>
|
||||
<text x="28" y="38" font-size="10.5" fill="#9AA0A8" font-family="'JetBrains Mono', Consolas, 'Courier New', monospace">NH_SegsToImpactSEGS · Vision</text>
|
||||
<circle cx="12" cy="67.0" r="5" fill="#E0B272" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="24" y="71.0" font-size="12" fill="#D7DADF">segs</text>
|
||||
<text x="336" y="71.0" font-size="12" fill="#D7DADF" text-anchor="end">segs</text>
|
||||
<circle cx="348" cy="67.0" r="5" fill="#E0B272" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<rect x="12" y="86" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="102" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="102" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="102" font-size="11.5" fill="#9AA0A8">crop_factor</text>
|
||||
<text x="326" y="102" font-size="11.5" fill="#E6E8EB" text-anchor="end">3.0</text>
|
||||
<rect x="12" y="115" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="131" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="131" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="131" font-size="11.5" fill="#9AA0A8">batch_index</text>
|
||||
<text x="326" y="131" font-size="11.5" fill="#E6E8EB" text-anchor="end">0</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.7 KiB |
@@ -0,0 +1,17 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="360" height="156" viewBox="0 0 360 156" font-family="Inter, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" role="img" aria-label="Segs To Masks (NH) node">
|
||||
<rect x="0.5" y="0.5" width="359" height="155" rx="10" fill="#26282c" stroke="#45484f"/>
|
||||
<path d="M0.5 10.5 a10 10 0 0 1 10 -10 h339 a10 10 0 0 1 10 10 v36 h-359 z" fill="#34373d"/>
|
||||
<circle cx="17" cy="17" r="4.5" fill="#8FD18F"/>
|
||||
<text x="28" y="22" font-size="14" font-weight="600" fill="#EEF0F3">Segs To Masks (NH)</text>
|
||||
<text x="28" y="38" font-size="10.5" fill="#9AA0A8" font-family="'JetBrains Mono', Consolas, 'Courier New', monospace">NH_SegsToMasks · Vision</text>
|
||||
<circle cx="12" cy="67.0" r="5" fill="#E0B272" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="24" y="71.0" font-size="12" fill="#D7DADF">segs</text>
|
||||
<text x="336" y="71.0" font-size="12" fill="#D7DADF" text-anchor="end">masks</text>
|
||||
<circle cx="348" cy="67.0" r="5" fill="#81C784" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="336" y="93.0" font-size="12" fill="#D7DADF" text-anchor="end">union</text>
|
||||
<circle cx="348" cy="89.0" r="5" fill="#81C784" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="336" y="115.0" font-size="12" fill="#D7DADF" text-anchor="end">labels</text>
|
||||
<circle cx="348" cy="111.0" r="5" fill="#C5E1A5" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="336" y="137.0" font-size="12" fill="#D7DADF" text-anchor="end">count</text>
|
||||
<circle cx="348" cy="133.0" r="5" fill="#9CC2E5" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
@@ -0,0 +1,22 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="360" height="193" viewBox="0 0 360 193" font-family="Inter, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" role="img" aria-label="NH Simple Face Paste node">
|
||||
<rect x="0.5" y="0.5" width="359" height="192" rx="10" fill="#26282c" stroke="#45484f"/>
|
||||
<path d="M0.5 10.5 a10 10 0 0 1 10 -10 h339 a10 10 0 0 1 10 10 v36 h-359 z" fill="#34373d"/>
|
||||
<circle cx="17" cy="17" r="4.5" fill="#8FD18F"/>
|
||||
<text x="28" y="22" font-size="14" font-weight="600" fill="#EEF0F3">NH Simple Face Paste</text>
|
||||
<text x="28" y="38" font-size="10.5" fill="#9AA0A8" font-family="'JetBrains Mono', Consolas, 'Courier New', monospace">NH_SimpleFacePaste · Image</text>
|
||||
<circle cx="12" cy="67.0" r="5" fill="#64B5F6" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="24" y="71.0" font-size="12" fill="#D7DADF">dest_image</text>
|
||||
<text x="336" y="71.0" font-size="12" fill="#D7DADF" text-anchor="end">IMAGE</text>
|
||||
<circle cx="348" cy="67.0" r="5" fill="#64B5F6" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<circle cx="12" cy="89.0" r="5" fill="#81C784" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="24" y="93.0" font-size="12" fill="#D7DADF">dest_mask</text>
|
||||
<circle cx="12" cy="111.0" r="5" fill="#64B5F6" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="24" y="115.0" font-size="12" fill="#D7DADF">source_image</text>
|
||||
<circle cx="12" cy="133.0" r="5" fill="#81C784" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="24" y="137.0" font-size="12" fill="#D7DADF">source_mask</text>
|
||||
<rect x="12" y="152" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="168" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="168" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="168" font-size="11.5" fill="#9AA0A8">feathering</text>
|
||||
<text x="326" y="168" font-size="11.5" fill="#E6E8EB" text-anchor="end">20</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.8 KiB |
@@ -0,0 +1,48 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="360" height="397" viewBox="0 0 360 397" font-family="Inter, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif" role="img" aria-label="NH Smart Ratio Image Resize node">
|
||||
<rect x="0.5" y="0.5" width="359" height="396" rx="10" fill="#26282c" stroke="#45484f"/>
|
||||
<path d="M0.5 10.5 a10 10 0 0 1 10 -10 h339 a10 10 0 0 1 10 10 v36 h-359 z" fill="#34373d"/>
|
||||
<circle cx="17" cy="17" r="4.5" fill="#8FD18F"/>
|
||||
<text x="28" y="22" font-size="14" font-weight="600" fill="#EEF0F3">NH Smart Ratio Image Resize</text>
|
||||
<text x="28" y="38" font-size="10.5" fill="#9AA0A8" font-family="'JetBrains Mono', Consolas, 'Courier New', monospace">NH_SmartRatioImageResize · Resolution</text>
|
||||
<circle cx="12" cy="67.0" r="5" fill="#64B5F6" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="24" y="71.0" font-size="12" fill="#D7DADF">image</text>
|
||||
<text x="336" y="71.0" font-size="12" fill="#D7DADF" text-anchor="end">image</text>
|
||||
<circle cx="348" cy="67.0" r="5" fill="#64B5F6" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="336" y="93.0" font-size="12" fill="#D7DADF" text-anchor="end">width</text>
|
||||
<circle cx="348" cy="89.0" r="5" fill="#9CC2E5" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="336" y="115.0" font-size="12" fill="#D7DADF" text-anchor="end">height</text>
|
||||
<circle cx="348" cy="111.0" r="5" fill="#9CC2E5" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="336" y="137.0" font-size="12" fill="#D7DADF" text-anchor="end">info</text>
|
||||
<circle cx="348" cy="133.0" r="5" fill="#C5E1A5" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="336" y="159.0" font-size="12" fill="#D7DADF" text-anchor="end">source_aspect_ratio</text>
|
||||
<circle cx="348" cy="155.0" r="5" fill="#9CC2E5" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="336" y="181.0" font-size="12" fill="#D7DADF" text-anchor="end">target_aspect_ratio</text>
|
||||
<circle cx="348" cy="177.0" r="5" fill="#9CC2E5" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="336" y="203.0" font-size="12" fill="#D7DADF" text-anchor="end">selected_preset</text>
|
||||
<circle cx="348" cy="199.0" r="5" fill="#C5E1A5" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<text x="336" y="225.0" font-size="12" fill="#D7DADF" text-anchor="end">resize_data</text>
|
||||
<circle cx="348" cy="221.0" r="5" fill="#E0B272" stroke="#1b1c1f" stroke-width="1.5"/>
|
||||
<rect x="12" y="240" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="256" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="256" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="256" font-size="11.5" fill="#9AA0A8">model_family</text>
|
||||
<text x="326" y="256" font-size="11.5" fill="#E6E8EB" text-anchor="end">FLUX.2 Klein 9B</text>
|
||||
<rect x="12" y="269" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="285" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="285" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="285" font-size="11.5" fill="#9AA0A8">resolution_level</text>
|
||||
<text x="326" y="285" font-size="11.5" fill="#E6E8EB" text-anchor="end">1 MP</text>
|
||||
<rect x="12" y="298" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="314" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="314" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="314" font-size="11.5" fill="#9AA0A8">resize_mode</text>
|
||||
<text x="326" y="314" font-size="11.5" fill="#E6E8EB" text-anchor="end">cover_crop</text>
|
||||
<rect x="12" y="327" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="20" y="343" font-size="10" fill="#6f747c">◀</text>
|
||||
<text x="332" y="343" font-size="10" fill="#6f747c">▶</text>
|
||||
<text x="34" y="343" font-size="11.5" fill="#9AA0A8">resize_method</text>
|
||||
<text x="326" y="343" font-size="11.5" fill="#E6E8EB" text-anchor="end">lanczos</text>
|
||||
<rect x="12" y="356" width="336" height="24" rx="7" fill="#1d1f22" stroke="#3a3d43"/>
|
||||
<text x="22" y="372" font-size="11.5" fill="#9AA0A8">pad_color_hex</text>
|
||||
<text x="338" y="372" font-size="11.5" fill="#E6E8EB" text-anchor="end">#FFFFFF</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.9 KiB |