HandEdit commited on
Commit
ce47bc4
·
verified ·
1 Parent(s): c63d837

Add files using upload-large-folder tool

Browse files
README.md ADDED
@@ -0,0 +1,92 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ library_name: peft
3
+ tags:
4
+ - image-editing
5
+ - lora
6
+ - robotics
7
+ - hand-editing
8
+ base_model:
9
+ - meituan-longcat/LongCat-Image-Edit
10
+ - Shitao/OmniGen-v1
11
+ - black-forest-labs/FLUX.2-klein-base-4B
12
+ - stepfun-ai/Step1X-Edit
13
+ ---
14
+
15
+ # HandEdit LoRA
16
+
17
+ HandEdit provides parameter-efficient LoRA adapters that specialize four
18
+ open-source image-editing backbones for human-to-robot-hand replacement.
19
+
20
+ We build more than 20K aligned training pairs from the HandEdit data. Each pair
21
+ contains an input image with a real human hand interacting with an object and a
22
+ corresponding target image in which only the hand is replaced by an Inspire
23
+ robotic hand. The training instruction asks the model to preserve the original
24
+ wrist pose, finger configuration, grasp relation, object contact points,
25
+ interaction object, background, lighting, and camera viewpoint as closely as
26
+ possible.
27
+
28
+ Only LoRA parameters are released; the full base-model weights are not
29
+ redistributed.
30
+
31
+ ## Models
32
+
33
+ | Adapter | Official base model | Local checkpoint |
34
+ | --- | --- | --- |
35
+ | LongCat-Image-Edit | [`meituan-longcat/LongCat-Image-Edit`](https://huggingface.co/meituan-longcat/LongCat-Image-Edit) | `checkpoints/longcat/` |
36
+ | OmniGen-v1 | [`Shitao/OmniGen-v1`](https://huggingface.co/Shitao/OmniGen-v1) | `checkpoints/omnigen/` |
37
+ | FLUX.2 Klein Base 4B | [`black-forest-labs/FLUX.2-klein-base-4B`](https://huggingface.co/black-forest-labs/FLUX.2-klein-base-4B) | `checkpoints/flux2/handedit_flux2_klein4b_lora.safetensors` |
38
+ | Step1X-Edit | [`stepfun-ai/Step1X-Edit`](https://huggingface.co/stepfun-ai/Step1X-Edit) | `checkpoints/step1x/inspire_step1x_r32_a16_res512.safetensors` |
39
+
40
+ ## Download
41
+
42
+ **[Download all four sanitized LoRA checkpoints](https://huggingface.co/HandEdit/HandEdit-LoRA/resolve/main/checkpoints.zip?download=true)**
43
+
44
+ Or download and verify them from the command line:
45
+
46
+ ```bash
47
+ pip install huggingface_hub
48
+ python scripts/download_weights.py
49
+ python scripts/verify_weights.py
50
+ ```
51
+
52
+ The published ZIP and individual adapter files are hosted in
53
+ [`HandEdit/HandEdit-LoRA`](https://huggingface.co/HandEdit/HandEdit-LoRA).
54
+
55
+ ## Inference
56
+
57
+ Detailed environment setup and commands are in
58
+ [`docs/INFERENCE.md`](docs/INFERENCE.md). The shortest examples are:
59
+
60
+ ```bash
61
+ # LongCat-Image-Edit
62
+ python scripts/infer_longcat_lora.py \
63
+ --input_dir ./examples/input --output_dir ./outputs/longcat
64
+
65
+ # OmniGen-v1
66
+ python scripts/infer_omnigen_lora.py \
67
+ --input_dir ./examples/input --output_dir ./outputs/omnigen
68
+
69
+ # FLUX.2 Klein Base 4B
70
+ python scripts/infer_flux2_lora.py \
71
+ --input_dir ./examples/input --output_dir ./outputs/flux2
72
+
73
+ # Step1X-Edit
74
+ python scripts/infer_step1x_lora.py \
75
+ --repo_dir ./third_party/Step1X-Edit \
76
+ --model_dir ./weights/Step1X-Edit \
77
+ --input_dir ./examples/input --output_dir ./outputs/step1x \
78
+ --quantized --offload
79
+ ```
80
+
81
+ ## Release hygiene
82
+
83
+ The public weights are sanitized copies. Training-data identifiers, dataset
84
+ sizes, local paths, author fields, timestamps, session information, and other
85
+ training-process metadata were removed without changing tensor bytes. See
86
+ [`SANITIZATION.md`](SANITIZATION.md) and `weights_manifest.json`.
87
+
88
+ ## Base-model licenses
89
+
90
+ Users must follow the license and access terms of each official base model.
91
+ This repository does not redistribute the four base models.
92
+
SANITIZATION.md ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Release sanitization
2
+
3
+ The public checkpoint files are sanitized copies, not the original training
4
+ artifacts.
5
+
6
+ - Optional SafeTensors metadata was removed from all four adapters.
7
+ - Published SafeTensors headers retain only `{"format": "pt"}`.
8
+ - PEFT JSON files contain only adapter architecture and loading parameters.
9
+ - Dataset identifiers, dataset sizes, local paths, timestamps, author fields,
10
+ host/session information, and training-process metadata are not published.
11
+ - Tensor names, shapes, data types, offsets, and tensor data bytes were preserved.
12
+
13
+ `weights_manifest.json` records checksums for the sanitized release files.
14
+
checkpoints.zip ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:9e89032b37d439205e940821f82735c790a35e5c32b4813ab985fb5ee0fdcad2
3
+ size 621076920
checkpoints/flux2/handedit_flux2_klein4b_lora.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:11a9d65f8b53a1f1de08d5bcf02c181ebc122ae79661367bdad3b5fddeac591a
3
+ size 369643968
checkpoints/longcat/adapter_config.json ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "bias": "none",
3
+ "fan_in_fan_out": false,
4
+ "inference_mode": true,
5
+ "init_lora_weights": "gaussian",
6
+ "lora_alpha": 8,
7
+ "lora_dropout": 0.0,
8
+ "peft_type": "LORA",
9
+ "r": 32,
10
+ "target_modules": [
11
+ "attn.to_add_out",
12
+ "attn.to_q",
13
+ "attn.add_v_proj",
14
+ "attn.to_v",
15
+ "attn.add_k_proj",
16
+ "attn.add_q_proj",
17
+ "ff_context.net.2",
18
+ "ff_context.net.0.proj",
19
+ "attn.to_k",
20
+ "ff.net.0.proj",
21
+ "attn.to_out.0",
22
+ "ff.net.2"
23
+ ],
24
+ "task_type": null,
25
+ "use_dora": false,
26
+ "use_rslora": false
27
+ }
checkpoints/longcat/adapter_model.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:2391face660c6e5dd5439553d96f89059c8bfa5160af89778455cf9bf8568a5e
3
+ size 94421944
checkpoints/omnigen/adapter_config.json ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "bias": "none",
3
+ "fan_in_fan_out": false,
4
+ "inference_mode": true,
5
+ "init_lora_weights": "gaussian",
6
+ "lora_alpha": 16,
7
+ "lora_dropout": 0.0,
8
+ "peft_type": "LORA",
9
+ "r": 16,
10
+ "target_modules": [
11
+ "o_proj",
12
+ "qkv_proj"
13
+ ],
14
+ "task_type": null,
15
+ "use_dora": false,
16
+ "use_rslora": false
17
+ }
checkpoints/omnigen/adapter_model.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:6863295d2829c25eeed85eb175d5d21ee5867b2536023124d9f1cd2dfce9b971
3
+ size 18891496
checkpoints/step1x/inspire_step1x_r32_a16_res512.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:e32ef656e023b0cc503ab0a24f645a3e99ba32ce8cae3dbd0eba221b988c1156
3
+ size 306426744
docs/INFERENCE.md ADDED
@@ -0,0 +1,132 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Inference
2
+
3
+ Run commands from the repository root. The four upstream projects do not
4
+ necessarily share one dependency set, so separate virtual environments are
5
+ recommended.
6
+
7
+ ## 1. Download the HandEdit adapters
8
+
9
+ Download and extract all four sanitized LoRA adapters:
10
+
11
+ ```bash
12
+ python scripts/download_weights.py
13
+ ```
14
+
15
+ You can verify an existing local copy with:
16
+
17
+ ```bash
18
+ python scripts/verify_weights.py
19
+ ```
20
+
21
+ The base models are not bundled. Download them from their official projects and
22
+ follow the corresponding licenses and access requirements.
23
+
24
+ ## 2. LongCat-Image-Edit
25
+
26
+ Install the shared dependencies:
27
+
28
+ ```bash
29
+ pip install -r requirements.txt
30
+ ```
31
+
32
+ Run batch inference:
33
+
34
+ ```bash
35
+ python scripts/infer_longcat_lora.py \
36
+ --input_dir ./examples/input \
37
+ --output_dir ./outputs/longcat \
38
+ --skip_existing
39
+ ```
40
+
41
+ The default base model is `meituan-longcat/LongCat-Image-Edit`. Use `--base`
42
+ to point to a local model directory, and add `--local_files_only` for an
43
+ offline cache.
44
+
45
+ ## 3. OmniGen-v1
46
+
47
+ Install the official OmniGen repository in its own environment:
48
+
49
+ ```bash
50
+ git clone https://github.com/VectorSpaceLab/OmniGen.git third_party/OmniGen
51
+ pip install -e third_party/OmniGen
52
+ pip install peft pillow
53
+ ```
54
+
55
+ Run:
56
+
57
+ ```bash
58
+ python scripts/infer_omnigen_lora.py \
59
+ --input_dir ./examples/input \
60
+ --output_dir ./outputs/omnigen \
61
+ --offload_model \
62
+ --skip_existing
63
+ ```
64
+
65
+ The default base model is `Shitao/OmniGen-v1`.
66
+
67
+ ## 4. FLUX.2 Klein Base 4B
68
+
69
+ Install the shared dependencies:
70
+
71
+ ```bash
72
+ pip install -r requirements.txt
73
+ ```
74
+
75
+ Run:
76
+
77
+ ```bash
78
+ python scripts/infer_flux2_lora.py \
79
+ --input_dir ./examples/input \
80
+ --output_dir ./outputs/flux2 \
81
+ --skip_existing
82
+ ```
83
+
84
+ The default base model is
85
+ `black-forest-labs/FLUX.2-klein-base-4B`. The script uses model CPU offload by
86
+ default; select `--offload sequential` for lower VRAM or `--offload none` for
87
+ maximum speed on a sufficiently large GPU.
88
+
89
+ ## 5. Step1X-Edit
90
+
91
+ Clone and install the official repository in its own environment:
92
+
93
+ ```bash
94
+ git clone https://github.com/stepfun-ai/Step1X-Edit.git third_party/Step1X-Edit
95
+ pip install -r third_party/Step1X-Edit/requirements.txt
96
+ ```
97
+
98
+ Prepare the official base-model directory expected by Step1X:
99
+
100
+ ```text
101
+ weights/Step1X-Edit/
102
+ ├── Qwen2.5-VL-7B-Instruct/
103
+ ├── step1x-edit-i1258.safetensors
104
+ └── vae.safetensors
105
+ ```
106
+
107
+ Run:
108
+
109
+ ```bash
110
+ python scripts/infer_step1x_lora.py \
111
+ --repo_dir ./third_party/Step1X-Edit \
112
+ --model_dir ./weights/Step1X-Edit \
113
+ --input_dir ./examples/input \
114
+ --output_dir ./outputs/step1x \
115
+ --version v1.0 \
116
+ --quantized \
117
+ --offload \
118
+ --skip_existing
119
+ ```
120
+
121
+ ## Shared edit instruction
122
+
123
+ The scripts use the following default instruction:
124
+
125
+ > Edit only the human hand region. Replace the human hand with a realistic
126
+ > Inspire robotic hand with correct robotic finger structure and joints.
127
+ > Preserve the original wrist pose, palm orientation, finger articulation,
128
+ > grasp geometry, and contact points with the object. Keep the object,
129
+ > background, lighting, camera viewpoint, and all non-hand regions unchanged.
130
+
131
+ Override it with `--prompt` when needed.
132
+
requirements.txt ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Shared runtime for LongCat-Image-Edit and FLUX.2 Klein.
2
+ # OmniGen and Step1X additionally require their official repositories;
3
+ # see docs/INFERENCE.md.
4
+ accelerate>=1.2
5
+ huggingface_hub>=0.28
6
+ numpy>=1.24
7
+ peft>=0.18.0
8
+ pillow>=10.0
9
+ safetensors>=0.4
10
+ torch>=2.5
11
+ torchvision>=0.20
12
+ transformers>=4.57
13
+ git+https://github.com/huggingface/diffusers.git
14
+
scripts/download_weights.py ADDED
@@ -0,0 +1,91 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """Download and safely extract the published HandEdit LoRA archive."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import argparse
7
+ import hashlib
8
+ import json
9
+ import shutil
10
+ import zipfile
11
+ from pathlib import Path
12
+
13
+ from huggingface_hub import hf_hub_download
14
+
15
+ DEFAULT_REPO_ID = "HandEdit/HandEdit-LoRA"
16
+ ARCHIVE_NAME = "checkpoints.zip"
17
+
18
+
19
+ def sha256(path: Path) -> str:
20
+ digest = hashlib.sha256()
21
+ with path.open("rb") as handle:
22
+ for chunk in iter(lambda: handle.read(8 * 1024 * 1024), b""):
23
+ digest.update(chunk)
24
+ return digest.hexdigest()
25
+
26
+
27
+ def safe_extract(archive: Path, destination: Path) -> None:
28
+ root = destination.resolve()
29
+ with zipfile.ZipFile(archive) as bundle:
30
+ for member in bundle.infolist():
31
+ target = (root / member.filename).resolve()
32
+ if target != root and root not in target.parents:
33
+ raise RuntimeError(f"Unsafe archive member: {member.filename}")
34
+ bundle.extractall(root)
35
+
36
+
37
+ def main() -> None:
38
+ parser = argparse.ArgumentParser(
39
+ description="Download the sanitized HandEdit LoRA checkpoints."
40
+ )
41
+ parser.add_argument("--repo-id", default=DEFAULT_REPO_ID)
42
+ parser.add_argument(
43
+ "--output-dir",
44
+ type=Path,
45
+ default=Path("."),
46
+ help="Repository root where checkpoints/ will be extracted.",
47
+ )
48
+ parser.add_argument(
49
+ "--keep-archive",
50
+ action="store_true",
51
+ help="Keep checkpoints.zip after successful extraction.",
52
+ )
53
+ args = parser.parse_args()
54
+
55
+ output_dir = args.output_dir.expanduser().resolve()
56
+ output_dir.mkdir(parents=True, exist_ok=True)
57
+ archive = Path(
58
+ hf_hub_download(
59
+ repo_id=args.repo_id,
60
+ filename=ARCHIVE_NAME,
61
+ repo_type="model",
62
+ local_dir=output_dir,
63
+ )
64
+ )
65
+
66
+ manifest_path = Path(
67
+ hf_hub_download(
68
+ repo_id=args.repo_id,
69
+ filename="weights_manifest.json",
70
+ repo_type="model",
71
+ local_dir=output_dir,
72
+ )
73
+ )
74
+ manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
75
+ expected = manifest["archive"]["sha256"]
76
+ actual = sha256(archive)
77
+ if actual != expected:
78
+ raise RuntimeError(
79
+ f"Archive checksum mismatch: expected {expected}, received {actual}"
80
+ )
81
+
82
+ safe_extract(archive, output_dir)
83
+ print(f"[OK] Extracted sanitized weights to: {output_dir / 'checkpoints'}")
84
+ if not args.keep_archive:
85
+ archive.unlink()
86
+ print(f"[OK] Removed downloaded archive: {archive}")
87
+
88
+
89
+ if __name__ == "__main__":
90
+ main()
91
+
scripts/infer_flux2_lora.py ADDED
@@ -0,0 +1,133 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """Batch image editing with FLUX.2 Klein and a Diffusers-compatible LoRA file."""
3
+
4
+ import argparse
5
+ from pathlib import Path
6
+
7
+ import torch
8
+ from PIL import Image
9
+ from diffusers import Flux2KleinPipeline
10
+
11
+ IMAGE_EXTS = {".jpg", ".jpeg", ".png", ".webp", ".bmp"}
12
+ DEFAULT_BASE = "black-forest-labs/FLUX.2-klein-base-4B"
13
+ DEFAULT_PROMPT = (
14
+ "Edit only the human hand region. Replace the human hand with a realistic Inspire robotic "
15
+ "hand with correct robotic finger structure and joints. Preserve the original wrist pose, "
16
+ "palm orientation, finger articulation, grasp geometry, and contact points with the object. "
17
+ "The robot hand must be kinematically feasible and physically plausible, without penetrating "
18
+ "the object. Keep the object pose, shape, texture, background, lighting, camera viewpoint, "
19
+ "and all non-hand regions unchanged."
20
+ )
21
+
22
+
23
+ def list_images(input_dir: Path, recursive: bool):
24
+ iterator = input_dir.rglob("*") if recursive else input_dir.iterdir()
25
+ return sorted(
26
+ path for path in iterator
27
+ if path.is_file() and path.suffix.lower() in IMAGE_EXTS
28
+ )
29
+
30
+
31
+ def load_lora(pipe, lora_path: str, scale: float):
32
+ path = Path(lora_path).expanduser()
33
+ adapter_name = "handedit"
34
+ if path.is_file():
35
+ pipe.load_lora_weights(
36
+ str(path.parent),
37
+ weight_name=path.name,
38
+ adapter_name=adapter_name,
39
+ )
40
+ else:
41
+ pipe.load_lora_weights(str(path), adapter_name=adapter_name)
42
+ pipe.set_adapters(adapter_name, adapter_weights=scale)
43
+
44
+
45
+ def main():
46
+ parser = argparse.ArgumentParser(
47
+ description="Batch inference for FLUX.2 Klein with a LoRA checkpoint."
48
+ )
49
+ parser.add_argument(
50
+ "--base",
51
+ default=DEFAULT_BASE,
52
+ help=f"FLUX.2 Klein model directory or model ID (default: {DEFAULT_BASE}).",
53
+ )
54
+ parser.add_argument(
55
+ "--lora",
56
+ default="./checkpoints/flux2/handedit_flux2_klein4b_lora.safetensors",
57
+ help="LoRA .safetensors file or adapter directory.",
58
+ )
59
+ parser.add_argument("--input_dir", required=True)
60
+ parser.add_argument("--output_dir", required=True)
61
+ parser.add_argument("--prompt", default=DEFAULT_PROMPT)
62
+ parser.add_argument("--steps", type=int, default=50)
63
+ parser.add_argument("--guidance_scale", type=float, default=4.0)
64
+ parser.add_argument("--lora_scale", type=float, default=1.0)
65
+ parser.add_argument("--seed", type=int, default=43)
66
+ parser.add_argument("--seed_mode", choices=["fixed", "increment"], default="fixed")
67
+ parser.add_argument("--offload", choices=["model", "sequential", "none"], default="model")
68
+ parser.add_argument("--local_files_only", action="store_true")
69
+ parser.add_argument("--suffix", default="")
70
+ parser.add_argument("--recursive", action="store_true")
71
+ parser.add_argument("--skip_existing", action="store_true")
72
+ args = parser.parse_args()
73
+
74
+ if not torch.cuda.is_available():
75
+ raise RuntimeError("CUDA is required for practical FLUX.2 inference.")
76
+
77
+ input_root = Path(args.input_dir).expanduser().resolve()
78
+ output_root = Path(args.output_dir).expanduser().resolve()
79
+ if not input_root.is_dir():
80
+ raise FileNotFoundError(f"Input directory not found: {input_root}")
81
+ output_root.mkdir(parents=True, exist_ok=True)
82
+
83
+ paths = list_images(input_root, args.recursive)
84
+ if not paths:
85
+ raise RuntimeError(f"No images found under: {input_root}")
86
+
87
+ print("[1/3] Loading FLUX.2 Klein...")
88
+ pipe = Flux2KleinPipeline.from_pretrained(
89
+ args.base,
90
+ torch_dtype=torch.bfloat16,
91
+ local_files_only=args.local_files_only,
92
+ )
93
+
94
+ print("[2/3] Loading LoRA adapter...")
95
+ load_lora(pipe, args.lora, args.lora_scale)
96
+
97
+ if args.offload == "model":
98
+ pipe.enable_model_cpu_offload()
99
+ elif args.offload == "sequential":
100
+ pipe.enable_sequential_cpu_offload()
101
+ else:
102
+ pipe.to("cuda")
103
+
104
+ print(f"[3/3] Processing {len(paths)} images...")
105
+ current_seed = args.seed
106
+ for index, input_path in enumerate(paths, start=1):
107
+ relative = input_path.relative_to(input_root)
108
+ output_path = output_root / relative.with_name(relative.stem + args.suffix + ".png")
109
+ output_path.parent.mkdir(parents=True, exist_ok=True)
110
+
111
+ if args.skip_existing and output_path.exists():
112
+ print(f"[{index}/{len(paths)}] SKIP {output_path}")
113
+ else:
114
+ image = Image.open(input_path).convert("RGB")
115
+ generator = torch.Generator("cpu").manual_seed(current_seed)
116
+ result = pipe(
117
+ prompt=args.prompt,
118
+ image=image,
119
+ num_inference_steps=args.steps,
120
+ guidance_scale=args.guidance_scale,
121
+ generator=generator,
122
+ ).images[0]
123
+ result.save(output_path)
124
+ print(f"[{index}/{len(paths)}] OK {input_path.name} -> {output_path}")
125
+
126
+ if args.seed_mode == "increment":
127
+ current_seed += 1
128
+
129
+ print(f"[DONE] Results saved under: {output_root}")
130
+
131
+
132
+ if __name__ == "__main__":
133
+ main()
scripts/infer_longcat_lora.py ADDED
@@ -0,0 +1,164 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """Batch image editing with LongCat-Image-Edit and a PEFT LoRA adapter."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import argparse
7
+ from pathlib import Path
8
+
9
+ import torch
10
+ from diffusers import LongCatImageEditPipeline
11
+ from peft import PeftModel
12
+ from PIL import Image
13
+
14
+ IMAGE_EXTS = {".jpg", ".jpeg", ".png", ".webp", ".bmp"}
15
+ DEFAULT_BASE = "meituan-longcat/LongCat-Image-Edit"
16
+ DEFAULT_PROMPT = (
17
+ "Edit only the human hand region. Replace the human hand with a realistic Inspire robotic "
18
+ "hand with correct robotic finger structure and joints. Preserve the original wrist pose, "
19
+ "palm orientation, finger articulation, grasp geometry, and contact points with the object. "
20
+ "The robot hand must be kinematically feasible and physically plausible, without penetrating "
21
+ "the object. Keep the object pose, shape, texture, background, lighting, camera viewpoint, "
22
+ "and all non-hand regions unchanged."
23
+ )
24
+
25
+
26
+ def list_images(input_dir: Path, recursive: bool) -> list[Path]:
27
+ iterator = input_dir.rglob("*") if recursive else input_dir.iterdir()
28
+ return sorted(
29
+ path
30
+ for path in iterator
31
+ if path.is_file() and path.suffix.lower() in IMAGE_EXTS
32
+ )
33
+
34
+
35
+ def apply_lora_scale(model: torch.nn.Module, scale: float) -> int:
36
+ """Multiply every active PEFT LoRA layer scale by ``scale``."""
37
+ if scale == 1.0:
38
+ return 0
39
+ changed = 0
40
+ for module in model.modules():
41
+ scaling = getattr(module, "scaling", None)
42
+ if isinstance(scaling, dict):
43
+ for adapter_name in list(scaling):
44
+ scaling[adapter_name] *= scale
45
+ changed += 1
46
+ return changed
47
+
48
+
49
+ def main() -> None:
50
+ parser = argparse.ArgumentParser(
51
+ description="Batch inference for LongCat-Image-Edit with the HandEdit LoRA."
52
+ )
53
+ parser.add_argument(
54
+ "--base",
55
+ default=DEFAULT_BASE,
56
+ help=f"Base model directory or model ID (default: {DEFAULT_BASE}).",
57
+ )
58
+ parser.add_argument(
59
+ "--lora",
60
+ default="./checkpoints/longcat",
61
+ help="PEFT LoRA directory (default: ./checkpoints/longcat).",
62
+ )
63
+ parser.add_argument("--input_dir", required=True)
64
+ parser.add_argument("--output_dir", required=True)
65
+ parser.add_argument("--prompt", default=DEFAULT_PROMPT)
66
+ parser.add_argument("--negative_prompt", default="")
67
+ parser.add_argument("--guidance_scale", type=float, default=4.5)
68
+ parser.add_argument("--steps", type=int, default=50)
69
+ parser.add_argument("--seed", type=int, default=43)
70
+ parser.add_argument(
71
+ "--seed_mode", choices=["fixed", "increment"], default="fixed"
72
+ )
73
+ parser.add_argument("--lora_scale", type=float, default=1.0)
74
+ parser.add_argument(
75
+ "--offload", choices=["model", "sequential", "none"], default="model"
76
+ )
77
+ parser.add_argument("--local_files_only", action="store_true")
78
+ parser.add_argument("--suffix", default="")
79
+ parser.add_argument("--recursive", action="store_true")
80
+ parser.add_argument("--skip_existing", action="store_true")
81
+ args = parser.parse_args()
82
+
83
+ if not torch.cuda.is_available():
84
+ raise RuntimeError("CUDA is required for practical LongCat inference.")
85
+ if args.steps < 2:
86
+ raise ValueError("--steps must be at least 2")
87
+ if args.guidance_scale < 0:
88
+ raise ValueError("--guidance_scale must be non-negative")
89
+
90
+ input_root = Path(args.input_dir).expanduser().resolve()
91
+ output_root = Path(args.output_dir).expanduser().resolve()
92
+ lora_root = Path(args.lora).expanduser().resolve()
93
+ if not input_root.is_dir():
94
+ raise FileNotFoundError(f"Input directory not found: {input_root}")
95
+ if not lora_root.is_dir():
96
+ raise FileNotFoundError(f"LoRA directory not found: {lora_root}")
97
+
98
+ paths = list_images(input_root, args.recursive)
99
+ if not paths:
100
+ raise RuntimeError(f"No images found under: {input_root}")
101
+ output_root.mkdir(parents=True, exist_ok=True)
102
+
103
+ print("[1/3] Loading LongCat-Image-Edit...")
104
+ pipe = LongCatImageEditPipeline.from_pretrained(
105
+ args.base,
106
+ torch_dtype=torch.bfloat16,
107
+ local_files_only=args.local_files_only,
108
+ )
109
+
110
+ print("[2/3] Loading HandEdit LoRA...")
111
+ pipe.transformer = PeftModel.from_pretrained(
112
+ pipe.transformer,
113
+ str(lora_root),
114
+ is_trainable=False,
115
+ )
116
+ changed = apply_lora_scale(pipe.transformer, args.lora_scale)
117
+ if args.lora_scale != 1.0:
118
+ print(
119
+ f"[INFO] LoRA scale={args.lora_scale}; "
120
+ f"adjusted {changed} active LoRA layers."
121
+ )
122
+
123
+ if args.offload == "model":
124
+ pipe.enable_model_cpu_offload()
125
+ elif args.offload == "sequential":
126
+ pipe.enable_sequential_cpu_offload()
127
+ else:
128
+ pipe.to("cuda")
129
+
130
+ print(f"[3/3] Processing {len(paths)} images...")
131
+ current_seed = args.seed
132
+ for index, input_path in enumerate(paths, start=1):
133
+ relative = input_path.relative_to(input_root)
134
+ output_path = output_root / relative.with_name(
135
+ relative.stem + args.suffix + ".png"
136
+ )
137
+ output_path.parent.mkdir(parents=True, exist_ok=True)
138
+
139
+ if args.skip_existing and output_path.exists():
140
+ print(f"[{index}/{len(paths)}] SKIP {output_path}")
141
+ else:
142
+ with Image.open(input_path) as source:
143
+ image = source.convert("RGB")
144
+ generator = torch.Generator("cpu").manual_seed(current_seed)
145
+ result = pipe(
146
+ image,
147
+ args.prompt,
148
+ negative_prompt=args.negative_prompt,
149
+ guidance_scale=args.guidance_scale,
150
+ num_inference_steps=args.steps,
151
+ num_images_per_prompt=1,
152
+ generator=generator,
153
+ ).images[0]
154
+ result.save(output_path)
155
+ print(f"[{index}/{len(paths)}] OK {input_path.name} -> {output_path}")
156
+
157
+ if args.seed_mode == "increment":
158
+ current_seed += 1
159
+
160
+ print(f"[DONE] Results saved under: {output_root}")
161
+
162
+
163
+ if __name__ == "__main__":
164
+ main()
scripts/infer_omnigen_lora.py ADDED
@@ -0,0 +1,121 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """Batch image editing with OmniGen-v1 and a fine-tuned LoRA checkpoint."""
3
+
4
+ import argparse
5
+ from pathlib import Path
6
+
7
+ from PIL import Image
8
+ from OmniGen import OmniGenPipeline
9
+
10
+ IMAGE_EXTS = {".jpg", ".jpeg", ".png", ".webp", ".bmp"}
11
+ DEFAULT_BASE = "Shitao/OmniGen-v1"
12
+ DEFAULT_PROMPT = (
13
+ "<img><|image_1|></img> Edit only the human hand region. Replace the human hand "
14
+ "with a realistic Inspire robotic hand with correct robotic finger structure and joints. "
15
+ "Preserve the original wrist pose, palm orientation, finger articulation, grasp geometry, "
16
+ "and contact points with the object. The robot hand must be kinematically feasible and "
17
+ "physically plausible, without penetrating the object. Keep the object pose, shape, texture, "
18
+ "background, lighting, camera viewpoint, and all non-hand regions unchanged."
19
+ )
20
+
21
+
22
+ def list_images(input_dir: Path, recursive: bool):
23
+ iterator = input_dir.rglob("*") if recursive else input_dir.iterdir()
24
+ return sorted(
25
+ path for path in iterator
26
+ if path.is_file() and path.suffix.lower() in IMAGE_EXTS
27
+ )
28
+
29
+
30
+ def target_size(image_path: Path, max_size: int):
31
+ with Image.open(image_path) as image:
32
+ width, height = image.size
33
+ scale = min(1.0, max_size / max(width, height))
34
+ width = max(16, round(width * scale / 16) * 16)
35
+ height = max(16, round(height * scale / 16) * 16)
36
+ return width, height
37
+
38
+
39
+ def main():
40
+ parser = argparse.ArgumentParser(
41
+ description="Batch inference for OmniGen-v1 with a LoRA checkpoint."
42
+ )
43
+ parser.add_argument(
44
+ "--base",
45
+ default=DEFAULT_BASE,
46
+ help=f"OmniGen-v1 model directory or model ID (default: {DEFAULT_BASE}).",
47
+ )
48
+ parser.add_argument(
49
+ "--lora",
50
+ default="./checkpoints/omnigen",
51
+ help="LoRA checkpoint directory (default: ./checkpoints/omnigen).",
52
+ )
53
+ parser.add_argument("--input_dir", required=True)
54
+ parser.add_argument("--output_dir", required=True)
55
+ parser.add_argument("--prompt", default=DEFAULT_PROMPT)
56
+ parser.add_argument("--steps", type=int, default=50)
57
+ parser.add_argument("--guidance_scale", type=float, default=2.5)
58
+ parser.add_argument("--img_guidance_scale", type=float, default=1.6)
59
+ parser.add_argument("--max_size", type=int, default=512)
60
+ parser.add_argument("--seed", type=int, default=43)
61
+ parser.add_argument("--seed_mode", choices=["fixed", "increment"], default="fixed")
62
+ parser.add_argument("--suffix", default="")
63
+ parser.add_argument("--recursive", action="store_true")
64
+ parser.add_argument("--skip_existing", action="store_true")
65
+ parser.add_argument("--offload_model", action="store_true")
66
+ args = parser.parse_args()
67
+
68
+ input_root = Path(args.input_dir).expanduser().resolve()
69
+ output_root = Path(args.output_dir).expanduser().resolve()
70
+ if not input_root.is_dir():
71
+ raise FileNotFoundError(f"Input directory not found: {input_root}")
72
+ output_root.mkdir(parents=True, exist_ok=True)
73
+
74
+ paths = list_images(input_root, args.recursive)
75
+ if not paths:
76
+ raise RuntimeError(f"No images found under: {input_root}")
77
+
78
+ print("[1/3] Loading OmniGen-v1...")
79
+ pipe = OmniGenPipeline.from_pretrained(args.base)
80
+
81
+ print("[2/3] Merging LoRA checkpoint...")
82
+ pipe.merge_lora(args.lora)
83
+
84
+ print(f"[3/3] Processing {len(paths)} images...")
85
+ current_seed = args.seed
86
+ for index, input_path in enumerate(paths, start=1):
87
+ relative = input_path.relative_to(input_root)
88
+ output_path = output_root / relative.with_name(relative.stem + args.suffix + ".png")
89
+ output_path.parent.mkdir(parents=True, exist_ok=True)
90
+
91
+ if args.skip_existing and output_path.exists():
92
+ print(f"[{index}/{len(paths)}] SKIP {output_path}")
93
+ else:
94
+ width, height = target_size(input_path, args.max_size)
95
+ images = pipe(
96
+ prompt=args.prompt,
97
+ input_images=[str(input_path)],
98
+ height=height,
99
+ width=width,
100
+ num_inference_steps=args.steps,
101
+ guidance_scale=args.guidance_scale,
102
+ img_guidance_scale=args.img_guidance_scale,
103
+ max_input_image_size=args.max_size,
104
+ separate_cfg_infer=True,
105
+ use_kv_cache=True,
106
+ offload_kv_cache=True,
107
+ offload_model=args.offload_model,
108
+ use_input_image_size_as_output=False,
109
+ seed=current_seed,
110
+ )
111
+ images[0].save(output_path)
112
+ print(f"[{index}/{len(paths)}] OK {input_path.name} -> {output_path}")
113
+
114
+ if args.seed_mode == "increment":
115
+ current_seed += 1
116
+
117
+ print(f"[DONE] Results saved under: {output_root}")
118
+
119
+
120
+ if __name__ == "__main__":
121
+ main()
scripts/infer_step1x_lora.py ADDED
@@ -0,0 +1,156 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """Batch image editing with the official Step1X-Edit code and a LoRA file."""
3
+
4
+ import argparse
5
+ import importlib.util
6
+ import sys
7
+ from pathlib import Path
8
+
9
+ import torch
10
+ from PIL import Image
11
+
12
+ IMAGE_EXTS = {".jpg", ".jpeg", ".png", ".webp", ".bmp"}
13
+ DEFAULT_PROMPT = (
14
+ "Edit only the human hand region. Replace the human hand with a realistic Inspire robotic "
15
+ "hand with correct robotic finger structure and joints. Preserve the original wrist pose, "
16
+ "palm orientation, finger articulation, grasp geometry, and contact points with the object. "
17
+ "The robot hand must be kinematically feasible and physically plausible, without penetrating "
18
+ "the object. Keep the object pose, shape, texture, background, lighting, camera viewpoint, "
19
+ "and all non-hand regions unchanged."
20
+ )
21
+
22
+
23
+ def list_images(input_dir: Path, recursive: bool):
24
+ iterator = input_dir.rglob("*") if recursive else input_dir.iterdir()
25
+ return sorted(
26
+ path for path in iterator
27
+ if path.is_file() and path.suffix.lower() in IMAGE_EXTS
28
+ )
29
+
30
+
31
+ def import_official_inference(repo_dir: Path):
32
+ inference_file = repo_dir / "inference.py"
33
+ if not inference_file.is_file():
34
+ raise FileNotFoundError(f"Official inference.py not found: {inference_file}")
35
+
36
+ sys.path.insert(0, str(repo_dir))
37
+ spec = importlib.util.spec_from_file_location("step1x_official_inference", inference_file)
38
+ if spec is None or spec.loader is None:
39
+ raise RuntimeError(f"Unable to import: {inference_file}")
40
+ module = importlib.util.module_from_spec(spec)
41
+ spec.loader.exec_module(module)
42
+ return module
43
+
44
+
45
+ def main():
46
+ parser = argparse.ArgumentParser(
47
+ description="Batch inference for Step1X-Edit v1.0/v1.1 with a LoRA checkpoint."
48
+ )
49
+ parser.add_argument("--repo_dir", required=True, help="Official Step1X-Edit repository directory.")
50
+ parser.add_argument(
51
+ "--model_dir",
52
+ required=True,
53
+ help="Directory containing the DiT checkpoint, VAE, and Qwen2.5-VL directory.",
54
+ )
55
+ parser.add_argument(
56
+ "--lora",
57
+ default="./checkpoints/step1x/inspire_step1x_r32_a16_res512.safetensors",
58
+ help="Step1X LoRA .safetensors file.",
59
+ )
60
+ parser.add_argument("--input_dir", required=True)
61
+ parser.add_argument("--output_dir", required=True)
62
+ parser.add_argument("--prompt", default=DEFAULT_PROMPT)
63
+ parser.add_argument("--version", choices=["v1.0", "v1.1"], default="v1.0")
64
+ parser.add_argument("--steps", type=int, default=28)
65
+ parser.add_argument("--cfg_guidance", type=float, default=6.0)
66
+ parser.add_argument("--size_level", type=int, default=512)
67
+ parser.add_argument("--seed", type=int, default=42)
68
+ parser.add_argument("--seed_mode", choices=["fixed", "increment"], default="fixed")
69
+ parser.add_argument("--quantized", action="store_true")
70
+ parser.add_argument("--offload", action="store_true")
71
+ parser.add_argument("--suffix", default="")
72
+ parser.add_argument("--recursive", action="store_true")
73
+ parser.add_argument("--skip_existing", action="store_true")
74
+ args = parser.parse_args()
75
+
76
+ if not torch.cuda.is_available():
77
+ raise RuntimeError("CUDA is required for practical Step1X inference.")
78
+
79
+ repo_dir = Path(args.repo_dir).expanduser().resolve()
80
+ model_dir = Path(args.model_dir).expanduser().resolve()
81
+ input_root = Path(args.input_dir).expanduser().resolve()
82
+ output_root = Path(args.output_dir).expanduser().resolve()
83
+ lora_path = Path(args.lora).expanduser().resolve()
84
+
85
+ if not input_root.is_dir():
86
+ raise FileNotFoundError(f"Input directory not found: {input_root}")
87
+ if not lora_path.is_file():
88
+ raise FileNotFoundError(f"LoRA file not found: {lora_path}")
89
+
90
+ ckpt_name = (
91
+ "step1x-edit-i1258.safetensors"
92
+ if args.version == "v1.0"
93
+ else "step1x-edit-v1p1-official.safetensors"
94
+ )
95
+ required = [
96
+ model_dir / ckpt_name,
97
+ model_dir / "vae.safetensors",
98
+ model_dir / "Qwen2.5-VL-7B-Instruct",
99
+ ]
100
+ for path in required:
101
+ if not path.exists():
102
+ raise FileNotFoundError(f"Required Step1X component not found: {path}")
103
+
104
+ output_root.mkdir(parents=True, exist_ok=True)
105
+ paths = list_images(input_root, args.recursive)
106
+ if not paths:
107
+ raise RuntimeError(f"No images found under: {input_root}")
108
+
109
+ official = import_official_inference(repo_dir)
110
+
111
+ print("[1/2] Loading Step1X-Edit and LoRA...")
112
+ generator = official.ImageGenerator(
113
+ ae_path=str(model_dir / "vae.safetensors"),
114
+ dit_path=str(model_dir / ckpt_name),
115
+ qwen2vl_model_path=str(model_dir / "Qwen2.5-VL-7B-Instruct"),
116
+ max_length=640,
117
+ quantized=args.quantized,
118
+ offload=args.offload,
119
+ lora=str(lora_path),
120
+ mode="flash",
121
+ version=args.version,
122
+ )
123
+
124
+ print(f"[2/2] Processing {len(paths)} images...")
125
+ current_seed = args.seed
126
+ for index, input_path in enumerate(paths, start=1):
127
+ relative = input_path.relative_to(input_root)
128
+ output_path = output_root / relative.with_name(relative.stem + args.suffix + ".png")
129
+ output_path.parent.mkdir(parents=True, exist_ok=True)
130
+
131
+ if args.skip_existing and output_path.exists():
132
+ print(f"[{index}/{len(paths)}] SKIP {output_path}")
133
+ else:
134
+ ref_image = Image.open(input_path).convert("RGB")
135
+ result = generator.generate_image(
136
+ prompt=args.prompt,
137
+ negative_prompt="",
138
+ ref_images=ref_image,
139
+ num_steps=args.steps,
140
+ cfg_guidance=args.cfg_guidance,
141
+ seed=current_seed,
142
+ num_samples=1,
143
+ show_progress=True,
144
+ size_level=args.size_level,
145
+ )[0]
146
+ result.save(output_path)
147
+ print(f"[{index}/{len(paths)}] OK {input_path.name} -> {output_path}")
148
+
149
+ if args.seed_mode == "increment":
150
+ current_seed += 1
151
+
152
+ print(f"[DONE] Results saved under: {output_root}")
153
+
154
+
155
+ if __name__ == "__main__":
156
+ main()
scripts/verify_weights.py ADDED
@@ -0,0 +1,56 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """Verify local sanitized LoRA files against weights_manifest.json."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import argparse
7
+ import hashlib
8
+ import json
9
+ from pathlib import Path
10
+
11
+
12
+ def sha256(path: Path) -> str:
13
+ digest = hashlib.sha256()
14
+ with path.open("rb") as handle:
15
+ for chunk in iter(lambda: handle.read(8 * 1024 * 1024), b""):
16
+ digest.update(chunk)
17
+ return digest.hexdigest()
18
+
19
+
20
+ def main() -> None:
21
+ parser = argparse.ArgumentParser(description="Verify HandEdit LoRA checkpoints.")
22
+ parser.add_argument(
23
+ "--root",
24
+ type=Path,
25
+ default=Path("."),
26
+ help="Repository root containing weights_manifest.json and checkpoints/.",
27
+ )
28
+ args = parser.parse_args()
29
+
30
+ root = args.root.expanduser().resolve()
31
+ manifest = json.loads(
32
+ (root / "weights_manifest.json").read_text(encoding="utf-8")
33
+ )
34
+ failures = []
35
+ for adapter in manifest["adapters"].values():
36
+ for item in adapter["files"]:
37
+ path = root / item["path"]
38
+ if not path.is_file():
39
+ failures.append(f"missing: {item['path']}")
40
+ continue
41
+ if path.stat().st_size != item["size_bytes"]:
42
+ failures.append(f"size mismatch: {item['path']}")
43
+ continue
44
+ if sha256(path) != item["sha256"]:
45
+ failures.append(f"SHA-256 mismatch: {item['path']}")
46
+ continue
47
+ print(f"[OK] {item['path']}")
48
+
49
+ if failures:
50
+ raise SystemExit("\n".join(failures))
51
+ print("[DONE] All sanitized LoRA files passed verification.")
52
+
53
+
54
+ if __name__ == "__main__":
55
+ main()
56
+
weights_manifest.json ADDED
@@ -0,0 +1,61 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "repo_id": "HandEdit/HandEdit-LoRA",
3
+ "release_type": "sanitized_lora_adapters",
4
+ "archive": {
5
+ "path": "checkpoints.zip",
6
+ "size_bytes": 621076920,
7
+ "sha256": "9e89032b37d439205e940821f82735c790a35e5c32b4813ab985fb5ee0fdcad2"
8
+ },
9
+ "adapters": {
10
+ "longcat": {
11
+ "base_model": "meituan-longcat/LongCat-Image-Edit",
12
+ "files": [
13
+ {
14
+ "path": "checkpoints/longcat/adapter_config.json",
15
+ "size_bytes": 530,
16
+ "sha256": "9ec4fbd1d7c3c3d9c6eef9cbee0cadf0c1fb1ce4874fd7e0252d666bd1eb8e9a"
17
+ },
18
+ {
19
+ "path": "checkpoints/longcat/adapter_model.safetensors",
20
+ "size_bytes": 94421944,
21
+ "sha256": "2391face660c6e5dd5439553d96f89059c8bfa5160af89778455cf9bf8568a5e"
22
+ }
23
+ ]
24
+ },
25
+ "omnigen": {
26
+ "base_model": "Shitao/OmniGen-v1",
27
+ "files": [
28
+ {
29
+ "path": "checkpoints/omnigen/adapter_config.json",
30
+ "size_bytes": 307,
31
+ "sha256": "7a285bafa03c63d6414a098f76b737fa3fb143f7257a6edf0ad0442cc412cbcc"
32
+ },
33
+ {
34
+ "path": "checkpoints/omnigen/adapter_model.safetensors",
35
+ "size_bytes": 18891496,
36
+ "sha256": "6863295d2829c25eeed85eb175d5d21ee5867b2536023124d9f1cd2dfce9b971"
37
+ }
38
+ ]
39
+ },
40
+ "flux2": {
41
+ "base_model": "black-forest-labs/FLUX.2-klein-base-4B",
42
+ "files": [
43
+ {
44
+ "path": "checkpoints/flux2/handedit_flux2_klein4b_lora.safetensors",
45
+ "size_bytes": 369643968,
46
+ "sha256": "11a9d65f8b53a1f1de08d5bcf02c181ebc122ae79661367bdad3b5fddeac591a"
47
+ }
48
+ ]
49
+ },
50
+ "step1x": {
51
+ "base_model": "stepfun-ai/Step1X-Edit",
52
+ "files": [
53
+ {
54
+ "path": "checkpoints/step1x/inspire_step1x_r32_a16_res512.safetensors",
55
+ "size_bytes": 306426744,
56
+ "sha256": "e32ef656e023b0cc503ab0a24f645a3e99ba32ce8cae3dbd0eba221b988c1156"
57
+ }
58
+ ]
59
+ }
60
+ }
61
+ }