Spaces:
Running
Running
File size: 6,113 Bytes
2e175db | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 | """
Shared helpers for Stage 3A dataset generation scripts.
Keep this module dependency-light: it is used on GPU dataset boxes, not in the
inference container. Generator-specific scripts should own their pipeline setup;
this module only centralizes the boring parts that must stay consistent across
sources.
"""
from __future__ import annotations
import hashlib
import json
import random
from dataclasses import dataclass
from pathlib import Path
from typing import Any, Sequence
@dataclass(frozen=True)
class GeneratorSpec:
"""License and metadata constants for one approved generator source."""
source: str
generator: str
model_family: str
model_id: str
license: str
license_url: str
pipeline: str
APPROVED_GENERATORS: dict[str, GeneratorSpec] = {
"flux.1-schnell": GeneratorSpec(
source="flux.1-schnell",
generator="flux.1-schnell",
model_family="rectified_flow",
model_id="black-forest-labs/FLUX.1-schnell",
license="Apache-2.0",
license_url="https://www.apache.org/licenses/LICENSE-2.0",
pipeline="FluxPipeline",
),
"sdxl": GeneratorSpec(
source="sdxl",
generator="sdxl",
model_family="diffusion_unet",
model_id="stabilityai/stable-diffusion-xl-base-1.0",
license="CreativeML Open RAIL++-M",
license_url=(
"https://huggingface.co/stabilityai/"
"stable-diffusion-xl-base-1.0/blob/main/LICENSE.md"
),
pipeline="StableDiffusionXLPipeline",
),
"sd3.5-medium": GeneratorSpec(
source="sd3.5-medium",
generator="sd3.5-medium",
model_family="diffusion_transformer",
model_id="stabilityai/stable-diffusion-3.5-medium",
license="Stability AI Community License",
license_url="https://stability.ai/license",
pipeline="StableDiffusion3Pipeline",
),
"auraflow-v0.3": GeneratorSpec(
source="auraflow-v0.3",
generator="auraflow-v0.3",
model_family="rectified_flow",
model_id="fal/AuraFlow-v0.3",
license="Apache-2.0",
license_url="https://www.apache.org/licenses/LICENSE-2.0",
pipeline="AuraFlowPipeline",
),
}
STAGE3A_MANIFEST_FIELDS = [
"path",
"class",
"source",
"license",
"license_url",
"sha256",
"generator",
"model_family",
"model_id",
"prompt",
"seed",
"width",
"height",
"generation_params_json",
]
def load_prompts(
path: Path | None,
*,
fallback_prompts: Sequence[str] | None = None,
) -> list[str]:
"""Read non-blank, non-comment prompts or return a fallback prompt list."""
if path is None:
if fallback_prompts is None:
raise ValueError("No prompts path provided and no fallback prompts given")
prompts = [p.strip() for p in fallback_prompts if p.strip()]
if not prompts:
raise ValueError("Fallback prompts list is empty")
return prompts
prompts: list[str] = []
with path.open(encoding="utf-8") as fh:
for line in fh:
stripped = line.strip()
if not stripped or stripped.startswith("#"):
continue
prompts.append(stripped)
if not prompts:
raise ValueError(f"No prompts found in {path}")
return prompts
def choose_prompt(rng: random.Random, prompts: Sequence[str]) -> str:
"""Pick one prompt with the caller's deterministic RNG."""
if not prompts:
raise ValueError("prompts must not be empty")
return rng.choice(list(prompts))
def next_seed(rng: random.Random) -> int:
"""Return a deterministic positive seed compatible with torch generators."""
return rng.randint(0, 2**31 - 1)
def stable_image_key(prompt: str, seed: int) -> str:
"""Return the legacy content-addressed key for generated image files."""
return hashlib.sha256(f"{prompt}|{seed}".encode()).hexdigest()[:24]
def infer_data_root(out_dir: Path) -> Path:
"""Infer the dataset root from an output path containing a `raw` segment."""
resolved = out_dir.resolve()
parts = resolved.parts
if "raw" not in parts:
raise ValueError(
f"Cannot infer data root from {out_dir}; pass --data-root explicitly"
)
raw_index = parts.index("raw")
if raw_index == 0:
raise ValueError(
f"Cannot infer data root from {out_dir}; pass --data-root explicitly"
)
return Path(*parts[:raw_index])
def sha256_file(path: Path) -> str:
"""Return the SHA-256 digest for a file."""
h = hashlib.sha256()
with path.open("rb") as fh:
for chunk in iter(lambda: fh.read(1024 * 1024), b""):
h.update(chunk)
return h.hexdigest()
def image_dimensions(path: Path) -> tuple[int, int]:
"""Return image width and height without keeping the image open."""
from PIL import Image
with Image.open(path) as image:
return image.size
def params_json(params: dict[str, Any]) -> str:
"""Serialize generation parameters consistently for manifest rows."""
return json.dumps(params, sort_keys=True, separators=(",", ":"))
def manifest_row(
*,
path: str,
cls: str,
spec: GeneratorSpec,
sha256: str,
prompt: str = "",
seed: int | str = "",
width: int | str = "",
height: int | str = "",
generation_params: dict[str, Any] | None = None,
extra: dict[str, Any] | None = None,
) -> dict[str, Any]:
"""Build a Stage 3A-compatible manifest row for one generated image."""
row: dict[str, Any] = {
"path": path,
"class": cls,
"source": spec.source,
"license": spec.license,
"license_url": spec.license_url,
"sha256": sha256,
"generator": spec.generator,
"model_family": spec.model_family,
"model_id": spec.model_id,
"prompt": prompt,
"seed": seed,
"width": width,
"height": height,
"generation_params_json": params_json(generation_params or {}),
}
if extra:
row.update(extra)
return row
|