Add Folder Image Loader design + implementation plan
Dataset-oriented loader: folder path, control_after_generate index (fixed/increment/decrement), depth control, sidecar .txt text output, alpha->mask, stem filename, resolved index. TDD plan with a pure stdlib scan layer; self-contained except a merge-aware root __init__ registration (pool node is being built concurrently). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,74 @@
|
||||
# Folder Image Loader — Design
|
||||
|
||||
Date: 2026-06-21
|
||||
Status: Approved (brainstorming complete, ready for implementation plan)
|
||||
|
||||
## 1. Purpose
|
||||
|
||||
A dataset-oriented image loader node: point it at a folder, pick an index (fixed or
|
||||
auto-advancing), and it outputs the image, its sidecar caption text, an alpha mask, the
|
||||
file stem, and the resolved index. Designed for sequential one-image-per-run dataset
|
||||
processing (inpaint/sort pipelines) where you want to walk a folder and stop cleanly
|
||||
when exhausted.
|
||||
|
||||
Second node in the `ComfyUI-Datasete-Gates` package (alongside `Image Pool (Grid)`).
|
||||
|
||||
## 2. IO
|
||||
|
||||
| dir | name | type | notes |
|
||||
|---|---|---|---|
|
||||
| widget | `folder` | STRING | any absolute path |
|
||||
| widget | `index` | INT (`control_after_generate`) | fixed / increment / decrement after each run; min `0` |
|
||||
| widget | `depth` | INT, default `0` | `0` = top-level only; `N` = recurse up to N levels; `-1` = unlimited |
|
||||
| out | `image` | IMAGE | `[1,H,W,3]` float 0..1 |
|
||||
| out | `text` | STRING | sidecar `<stem>.txt` content (UTF-8, trailing newline stripped); `""` if absent |
|
||||
| out | `mask` | MASK | from alpha channel (`1 - alpha`, the LoadImage convention); zeros sized to image if no alpha |
|
||||
| out | `filename` | STRING | the file **stem** (no extension, no dir) |
|
||||
| out | `index` | INT | the resolved index actually loaded |
|
||||
|
||||
## 3. Behavior
|
||||
|
||||
- **Scan**: walk `folder` depth-limited, keep files whose suffix is in
|
||||
`{.png, .jpg, .jpeg, .webp, .bmp, .tif, .tiff}`, **natural-sort by path relative to
|
||||
the folder** (so `img2.png` < `img10.png`) → a deterministic list.
|
||||
- **Index control**: native `control_after_generate` gives fixed/increment/decrement.
|
||||
Increment past the last image walks off the end → **error** (the intended
|
||||
end-of-batch stop signal). `min=0` means decrement floors at the first image.
|
||||
- **Out of range / empty / bad path** → raise a clear error:
|
||||
- `index N out of range: M images in <folder>`
|
||||
- `No images found in <folder>` / `Not a folder: <folder>`
|
||||
- **Sidecar**: `<same-stem>.txt` next to the image; UTF-8, `rstrip("\n")`; missing → `""`.
|
||||
- **IS_CHANGED**: hash `(folder, depth, resolved index, image mtime, sidecar mtime)` so
|
||||
fixed-mode file edits re-trigger. (Increment mode re-runs anyway — the widget value
|
||||
changes each run.)
|
||||
|
||||
## 4. Code shape
|
||||
|
||||
Kept **self-contained** so it can be built independently of the in-flight pool work.
|
||||
|
||||
- `gates/scan.py` — pure, stdlib-only, unit-testable: `natural_key`, `list_images`,
|
||||
`resolve_index`, `sidecar_path`, `read_sidecar`, `stem`.
|
||||
- `gates/loader.py` — the `FolderImageLoader` node (torch/PIL); contains its own
|
||||
`load_image_and_mask(path)` (RGB + alpha→mask). ~10 lines overlap with the pool's
|
||||
`imaging.py`; deliberate, to decouple the two workstreams. Optional post-merge dedupe.
|
||||
- **Shared file**: root `__init__.py` — the only place both nodes meet. The plan
|
||||
*extends* the existing `if __package__:` block to also import + merge the loader's
|
||||
mappings (does not overwrite).
|
||||
|
||||
## 5. Edge cases
|
||||
|
||||
- Non-existent / non-dir path → `NotADirectoryError` with the path.
|
||||
- Folder with no matching images → `FileNotFoundError`.
|
||||
- Image without alpha → zero mask sized to the image (not 64×64).
|
||||
- Symlinks/hidden files: included if extension matches (no special handling v1).
|
||||
- Huge folders: `os.walk` + one sort per run is fine for thousands of files.
|
||||
|
||||
## 6. Testing
|
||||
|
||||
- pytest (`tests/test_scan.py`): natural sort, depth limiting (0 / N / -1), extension
|
||||
filter, `resolve_index` raises on OOB and empty, sidecar present/missing, stem.
|
||||
- pytest (`tests/test_loader.py`): `run()` against a tmp folder of real PNGs (with and
|
||||
without alpha + sidecars) — output tensor shapes, text, mask polarity, stem, resolved
|
||||
index; OOB raises; `IS_CHANGED` differs across index and sidecar mtime.
|
||||
- Manual: drop the node in ComfyUI, point at a real dataset folder, increment through it,
|
||||
confirm caption text + mask, and confirm it errors at the end.
|
||||
Reference in New Issue
Block a user