Spaces:
Running
Running
Upload 9 files
Browse files- app.py +20 -70
- check.py +17 -0
- inference.py +91 -79
- requirements.txt +1 -0
app.py
CHANGED
|
@@ -1,105 +1,55 @@
|
|
| 1 |
-
"""
|
| 2 |
-
Gradio app for StyleGAN v1 image generation.
|
| 3 |
-
Run: gradio app.py
|
| 4 |
-
For Hugging Face Spaces, the Space runs this file.
|
| 5 |
-
"""
|
| 6 |
import os
|
| 7 |
import gradio as gr
|
| 8 |
-
|
| 9 |
-
# Backend must be set before any Keras/JAX imports
|
| 10 |
os.environ["KERAS_BACKEND"] = "jax"
|
| 11 |
|
| 12 |
-
from inference import load_weights, generate_images,
|
| 13 |
-
from configuration import RESOLUTIONS
|
| 14 |
|
| 15 |
-
# Load model once at startup
|
| 16 |
_model_loaded = False
|
| 17 |
|
| 18 |
-
|
| 19 |
-
def load_model_once(resolution=256):
|
| 20 |
global _model_loaded
|
| 21 |
if _model_loaded:
|
| 22 |
return True
|
| 23 |
-
ok = load_weights(resolution=
|
| 24 |
if ok:
|
| 25 |
_model_loaded = True
|
| 26 |
return ok
|
| 27 |
|
| 28 |
-
|
| 29 |
-
def generate(n_images, resolution, seed):
|
| 30 |
n_images = max(1, min(int(n_images), 16))
|
| 31 |
-
resolution = int(resolution)
|
| 32 |
seed = int(seed) if seed is not None and str(seed).strip() else None
|
| 33 |
|
| 34 |
-
if not load_model_once(
|
| 35 |
-
return (
|
| 36 |
-
[],
|
| 37 |
-
"No weights found. Add generator weights to the repo:\n"
|
| 38 |
-
f"β’ Keras: {WEIGHTS_PATH}\n"
|
| 39 |
-
f"β’ Or state dict: {STATE_PATH}\n"
|
| 40 |
-
"See README for Hugging Face setup.",
|
| 41 |
-
)
|
| 42 |
|
| 43 |
try:
|
| 44 |
-
images = generate_images(n_images, resolution=
|
| 45 |
-
if
|
| 46 |
return [], "No images generated."
|
| 47 |
-
return images, f"Generated {len(images)} image(s) at
|
| 48 |
except Exception as e:
|
| 49 |
import traceback
|
| 50 |
return [], f"Error: {e}\n{traceback.format_exc()}"
|
| 51 |
|
| 52 |
-
|
| 53 |
def main():
|
| 54 |
-
|
|
|
|
|
|
|
| 55 |
|
| 56 |
-
with gr.Blocks(
|
| 57 |
-
title="StyleGAN v1",
|
| 58 |
-
theme=gr.themes.Soft(),
|
| 59 |
-
) as demo:
|
| 60 |
-
gr.Markdown("# StyleGAN v1 β Image generation")
|
| 61 |
-
gr.Markdown("Choose how many images to generate and the resolution. Then click **Generate**.")
|
| 62 |
-
|
| 63 |
-
with gr.Row():
|
| 64 |
-
n_images = gr.Slider(
|
| 65 |
-
minimum=1,
|
| 66 |
-
maximum=16,
|
| 67 |
-
value=4,
|
| 68 |
-
step=1,
|
| 69 |
-
label="Number of images",
|
| 70 |
-
)
|
| 71 |
-
resolution = gr.Dropdown(
|
| 72 |
-
choices=[str(r) for r in resolutions],
|
| 73 |
-
value="256",
|
| 74 |
-
label="Resolution",
|
| 75 |
-
)
|
| 76 |
-
seed = gr.Number(
|
| 77 |
-
value=None,
|
| 78 |
-
label="Random seed (optional)",
|
| 79 |
-
precision=0,
|
| 80 |
-
)
|
| 81 |
with gr.Row():
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
gallery = gr.Gallery(
|
| 85 |
-
label="Generated images",
|
| 86 |
-
show_label=True,
|
| 87 |
-
columns=4,
|
| 88 |
-
object_fit="contain",
|
| 89 |
-
height="auto",
|
| 90 |
-
)
|
| 91 |
|
| 92 |
-
gen_btn.
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
|
| 96 |
-
)
|
| 97 |
|
| 98 |
demo.launch(
|
| 99 |
server_name="0.0.0.0",
|
| 100 |
server_port=int(os.environ.get("PORT", 7860)),
|
| 101 |
)
|
| 102 |
|
| 103 |
-
|
| 104 |
if __name__ == "__main__":
|
| 105 |
-
main()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
import os
|
| 2 |
import gradio as gr
|
|
|
|
|
|
|
| 3 |
os.environ["KERAS_BACKEND"] = "jax"
|
| 4 |
|
| 5 |
+
from inference import load_weights, generate_images, WEIGHTS_H5, CHECKPOINT_PKL
|
|
|
|
| 6 |
|
|
|
|
| 7 |
_model_loaded = False
|
| 8 |
|
| 9 |
+
def load_model_once():
|
|
|
|
| 10 |
global _model_loaded
|
| 11 |
if _model_loaded:
|
| 12 |
return True
|
| 13 |
+
ok = load_weights(resolution=256)
|
| 14 |
if ok:
|
| 15 |
_model_loaded = True
|
| 16 |
return ok
|
| 17 |
|
| 18 |
+
def generate(n_images, seed):
|
|
|
|
| 19 |
n_images = max(1, min(int(n_images), 16))
|
|
|
|
| 20 |
seed = int(seed) if seed is not None and str(seed).strip() else None
|
| 21 |
|
| 22 |
+
if not load_model_once():
|
| 23 |
+
return [], f"No weights found. Put checkpoint.pkl in:\n{os.path.dirname(WEIGHTS_H5)}"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 24 |
|
| 25 |
try:
|
| 26 |
+
images = generate_images(n_images, resolution=256, seed=seed)
|
| 27 |
+
if images is None or len(images) == 0:
|
| 28 |
return [], "No images generated."
|
| 29 |
+
return images, f"Generated {len(images)} image(s) at 256Γ256."
|
| 30 |
except Exception as e:
|
| 31 |
import traceback
|
| 32 |
return [], f"Error: {e}\n{traceback.format_exc()}"
|
| 33 |
|
|
|
|
| 34 |
def main():
|
| 35 |
+
with gr.Blocks(title="StyleGAN v1", theme=gr.themes.Soft()) as demo:
|
| 36 |
+
gr.Markdown("# StyleGAN v1 β Face Generation")
|
| 37 |
+
gr.Markdown("Generate faces at 256Γ256. Optionally set a seed for reproducibility.")
|
| 38 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 39 |
with gr.Row():
|
| 40 |
+
n_images = gr.Slider(minimum=1, maximum=16, value=4, step=1, label="Number of images")
|
| 41 |
+
seed = gr.Number(value=None, label="Random seed (optional)", precision=0)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 42 |
|
| 43 |
+
gen_btn = gr.Button("Generate", variant="primary")
|
| 44 |
+
status = gr.Textbox(label="Status", interactive=False)
|
| 45 |
+
gallery = gr.Gallery(label="Generated images", columns=4, object_fit="contain", height="auto")
|
| 46 |
+
|
| 47 |
+
gen_btn.click(fn=generate, inputs=[n_images, seed], outputs=[gallery, status])
|
| 48 |
|
| 49 |
demo.launch(
|
| 50 |
server_name="0.0.0.0",
|
| 51 |
server_port=int(os.environ.get("PORT", 7860)),
|
| 52 |
)
|
| 53 |
|
|
|
|
| 54 |
if __name__ == "__main__":
|
| 55 |
+
main()
|
check.py
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import joblib
|
| 2 |
+
import jax.numpy as jnp
|
| 3 |
+
from loading_model import generator
|
| 4 |
+
from inference import _build_model
|
| 5 |
+
|
| 6 |
+
_build_model()
|
| 7 |
+
|
| 8 |
+
# Load checkpoint
|
| 9 |
+
ck = joblib.load("weights/checkpoint.pkl")
|
| 10 |
+
|
| 11 |
+
print("=== CHECKPOINT variables ===")
|
| 12 |
+
for i, w in enumerate(ck["gen_trainable"]):
|
| 13 |
+
print(f" [{i:03d}] shape={str(w.shape):30s}")
|
| 14 |
+
|
| 15 |
+
print("\n=== MODEL variables ===")
|
| 16 |
+
for i, v in enumerate(generator.trainable_variables):
|
| 17 |
+
print(f" [{i:03d}] shape={str(v.shape):30s} name={v.name}")
|
inference.py
CHANGED
|
@@ -1,7 +1,3 @@
|
|
| 1 |
-
"""
|
| 2 |
-
StyleGAN v1 inference: load weights and generate images.
|
| 3 |
-
Supports Keras .weights.h5 and optional state checkpoint (gen_state).
|
| 4 |
-
"""
|
| 5 |
import os
|
| 6 |
os.environ["KERAS_BACKEND"] = "jax"
|
| 7 |
|
|
@@ -9,106 +5,122 @@ import numpy as np
|
|
| 9 |
import jax
|
| 10 |
import jax.numpy as jnp
|
| 11 |
|
| 12 |
-
# Import after backend set
|
| 13 |
from loading_model import generator
|
| 14 |
from configuration import LATENT_DIM, RESOLUTIONS
|
| 15 |
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
STATE_PATH = os.environ.get("STYLEGAN_STATE_PATH", os.path.join(WEIGHTS_DIR, "gen_state.pkl"))
|
| 20 |
|
| 21 |
-
_gen_state
|
| 22 |
_use_stateless = False
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 23 |
|
| 24 |
|
| 25 |
def _build_model():
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
dummy_z
|
| 29 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 30 |
|
| 31 |
|
| 32 |
def load_weights(resolution=256):
|
| 33 |
-
|
| 34 |
-
Load generator weights from disk.
|
| 35 |
-
Prefer WEIGHTS_PATH (Keras .weights.h5). If not found, try STATE_PATH (gen_state pickle).
|
| 36 |
-
"""
|
| 37 |
-
global _gen_state, _use_stateless
|
| 38 |
_build_model()
|
| 39 |
|
| 40 |
-
if os.path.isfile(
|
| 41 |
-
|
|
|
|
| 42 |
_use_stateless = False
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 66 |
if seed is None:
|
| 67 |
seed = int(np.random.randint(0, 2**31))
|
| 68 |
rng = jax.random.PRNGKey(seed)
|
| 69 |
-
|
| 70 |
if _use_stateless and _gen_state is not None:
|
| 71 |
-
return _generate_stateless(n_images,
|
| 72 |
-
return _generate_keras(n_images,
|
| 73 |
|
| 74 |
|
| 75 |
-
def _generate_keras(n_images,
|
| 76 |
-
|
| 77 |
-
res = max(r for r in RESOLUTIONS if r <= res)
|
| 78 |
-
generator.current_resolution = res
|
| 79 |
-
|
| 80 |
rng, z_key, noise_key = jax.random.split(rng, 3)
|
| 81 |
-
z
|
|
|
|
|
|
|
|
|
|
| 82 |
|
| 83 |
-
out_call = generator(z, 1.0, noise_key)
|
| 84 |
-
images = out_call[0] if isinstance(out_call, (list, tuple)) else out_call
|
| 85 |
-
images = (np.array(images) + 1.0) / 2.0
|
| 86 |
-
images = np.clip(images, 0.0, 1.0)
|
| 87 |
-
out = []
|
| 88 |
-
for i in range(n_images):
|
| 89 |
-
img = (images[i] * 255).astype(np.uint8)
|
| 90 |
-
out.append(img)
|
| 91 |
-
return out
|
| 92 |
-
|
| 93 |
-
|
| 94 |
-
def _generate_stateless(n_images, resolution, rng):
|
| 95 |
-
gen_state = _gen_state
|
| 96 |
-
res = min(resolution, max(RESOLUTIONS))
|
| 97 |
-
res = max(r for r in RESOLUTIONS if r <= res)
|
| 98 |
-
# If state was saved with a resolution, we use generator.current_resolution from state
|
| 99 |
-
# Otherwise we'd need to store it in gen_state; for now use generator attribute
|
| 100 |
-
generator.current_resolution = res
|
| 101 |
|
|
|
|
|
|
|
| 102 |
rng, z_key, noise_key = jax.random.split(rng, 3)
|
| 103 |
z = jax.random.normal(z_key, (n_images, LATENT_DIM))
|
| 104 |
-
|
| 105 |
-
|
| 106 |
-
|
| 107 |
-
gen_state["non_trainable"],
|
| 108 |
z,
|
| 109 |
-
|
| 110 |
noise_key,
|
| 111 |
)
|
| 112 |
-
|
| 113 |
-
fake_images = np.clip(fake_images, 0.0, 1.0)
|
| 114 |
-
return [(fake_images[i] * 255).astype(np.uint8) for i in range(n_images)]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
import os
|
| 2 |
os.environ["KERAS_BACKEND"] = "jax"
|
| 3 |
|
|
|
|
| 5 |
import jax
|
| 6 |
import jax.numpy as jnp
|
| 7 |
|
|
|
|
| 8 |
from loading_model import generator
|
| 9 |
from configuration import LATENT_DIM, RESOLUTIONS
|
| 10 |
|
| 11 |
+
WEIGHTS_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "weights")
|
| 12 |
+
WEIGHTS_H5 = os.path.join(WEIGHTS_DIR, "generator.weights.h5")
|
| 13 |
+
CHECKPOINT_PKL = os.path.join(WEIGHTS_DIR, "checkpoint.pkl")
|
|
|
|
| 14 |
|
| 15 |
+
_gen_state = None
|
| 16 |
_use_stateless = False
|
| 17 |
+
_saved_alpha = 1.0
|
| 18 |
+
|
| 19 |
+
TARGET_RES = 256 # hardcoded β only generate at 256
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
def _postprocess(images: np.ndarray) -> list:
|
| 23 |
+
raw_min, raw_max = images.min(), images.max()
|
| 24 |
+
print(f"raw min={raw_min:.3f} max={raw_max:.3f}")
|
| 25 |
+
if raw_min >= -1.5 and raw_max <= 1.5:
|
| 26 |
+
images = (images + 1.0) / 2.0
|
| 27 |
+
images = np.clip(images, 0.0, 1.0)
|
| 28 |
+
return [images[i] for i in range(images.shape[0])]
|
| 29 |
+
else:
|
| 30 |
+
out = []
|
| 31 |
+
for i in range(images.shape[0]):
|
| 32 |
+
img = images[i].astype(np.float32)
|
| 33 |
+
img = (img - img.min()) / (img.max() - img.min() + 1e-8)
|
| 34 |
+
out.append(img)
|
| 35 |
+
return out
|
| 36 |
|
| 37 |
|
| 38 |
def _build_model():
|
| 39 |
+
dummy_alpha = jnp.array(1.0)
|
| 40 |
+
dummy_rng = jax.random.PRNGKey(0)
|
| 41 |
+
dummy_z = jnp.zeros((1, LATENT_DIM))
|
| 42 |
+
for res in RESOLUTIONS:
|
| 43 |
+
generator.current_resolution = res
|
| 44 |
+
_ = generator(dummy_z, alpha=dummy_alpha, rng_key=dummy_rng)
|
| 45 |
+
generator.current_resolution = TARGET_RES
|
| 46 |
+
print(f"β
Model built β trainable: {len(generator.trainable_variables)} non-trainable: {len(generator.non_trainable_variables)}")
|
| 47 |
|
| 48 |
|
| 49 |
def load_weights(resolution=256):
|
| 50 |
+
global _gen_state, _use_stateless, _saved_alpha
|
|
|
|
|
|
|
|
|
|
|
|
|
| 51 |
_build_model()
|
| 52 |
|
| 53 |
+
if os.path.isfile(WEIGHTS_H5):
|
| 54 |
+
print(f"π Loading Keras weights: {WEIGHTS_H5}")
|
| 55 |
+
generator.load_weights(WEIGHTS_H5)
|
| 56 |
_use_stateless = False
|
| 57 |
+
print("β
Keras weights loaded.")
|
| 58 |
+
|
| 59 |
+
elif os.path.isfile(CHECKPOINT_PKL):
|
| 60 |
+
print(f"π Loading checkpoint: {CHECKPOINT_PKL}")
|
| 61 |
+
import joblib
|
| 62 |
+
data = joblib.load(CHECKPOINT_PKL)
|
| 63 |
+
print(f" Keys: {list(data.keys())}")
|
| 64 |
+
|
| 65 |
+
gen_trainable = data["gen_trainable"]
|
| 66 |
+
gen_non_trainable = data.get("gen_non_trainable", [])
|
| 67 |
+
_saved_alpha = float(data.get("alpha", 1.0))
|
| 68 |
+
|
| 69 |
+
print(f" trainable={len(gen_trainable)} non_trainable={len(gen_non_trainable)} alpha={_saved_alpha:.4f}")
|
| 70 |
+
|
| 71 |
+
n_model = len(generator.trainable_variables)
|
| 72 |
+
if len(gen_trainable) != n_model:
|
| 73 |
+
raise ValueError(f"β Mismatch β checkpoint:{len(gen_trainable)} model:{n_model}")
|
| 74 |
+
|
| 75 |
+
n_non = len(generator.non_trainable_variables)
|
| 76 |
+
_gen_state = {
|
| 77 |
+
"ema_trainable": [jnp.asarray(t) for t in gen_trainable],
|
| 78 |
+
"non_trainable": (
|
| 79 |
+
[jnp.asarray(t) for t in gen_non_trainable[:n_non]]
|
| 80 |
+
if gen_non_trainable
|
| 81 |
+
else [jnp.asarray(v.value) for v in generator.non_trainable_variables]
|
| 82 |
+
),
|
| 83 |
+
}
|
| 84 |
+
_use_stateless = True
|
| 85 |
+
print("β
Checkpoint loaded.")
|
| 86 |
+
|
| 87 |
+
else:
|
| 88 |
+
raise FileNotFoundError(f"β No weights found in: {WEIGHTS_DIR}")
|
| 89 |
+
|
| 90 |
+
generator.current_resolution = TARGET_RES
|
| 91 |
+
print(f"πΌοΈ Resolution locked to: {TARGET_RES}")
|
| 92 |
+
return True
|
| 93 |
+
|
| 94 |
+
|
| 95 |
+
def generate_images(n_images, resolution=None, seed=None):
|
| 96 |
+
if not _use_stateless and _gen_state is None:
|
| 97 |
+
raise RuntimeError("β Call load_weights() first.")
|
| 98 |
if seed is None:
|
| 99 |
seed = int(np.random.randint(0, 2**31))
|
| 100 |
rng = jax.random.PRNGKey(seed)
|
|
|
|
| 101 |
if _use_stateless and _gen_state is not None:
|
| 102 |
+
return _generate_stateless(n_images, rng)
|
| 103 |
+
return _generate_keras(n_images, rng)
|
| 104 |
|
| 105 |
|
| 106 |
+
def _generate_keras(n_images, rng):
|
| 107 |
+
generator.current_resolution = TARGET_RES
|
|
|
|
|
|
|
|
|
|
| 108 |
rng, z_key, noise_key = jax.random.split(rng, 3)
|
| 109 |
+
z = jax.random.normal(z_key, (n_images, LATENT_DIM))
|
| 110 |
+
out = generator(z, alpha=1.0, rng_key=noise_key)
|
| 111 |
+
images = out[0] if isinstance(out, (list, tuple)) else out
|
| 112 |
+
return _postprocess(np.array(images))
|
| 113 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 114 |
|
| 115 |
+
def _generate_stateless(n_images, rng):
|
| 116 |
+
generator.current_resolution = TARGET_RES
|
| 117 |
rng, z_key, noise_key = jax.random.split(rng, 3)
|
| 118 |
z = jax.random.normal(z_key, (n_images, LATENT_DIM))
|
| 119 |
+
images, _ = generator.stateless_call(
|
| 120 |
+
_gen_state["ema_trainable"],
|
| 121 |
+
_gen_state["non_trainable"],
|
|
|
|
| 122 |
z,
|
| 123 |
+
jnp.array(_saved_alpha),
|
| 124 |
noise_key,
|
| 125 |
)
|
| 126 |
+
return _postprocess(np.array(images))
|
|
|
|
|
|
requirements.txt
CHANGED
|
@@ -4,3 +4,4 @@ keras>=3.0.0
|
|
| 4 |
jax>=0.4.0
|
| 5 |
jaxlib>=0.4.0
|
| 6 |
numpy>=1.24.0,<2.0.0
|
|
|
|
|
|
| 4 |
jax>=0.4.0
|
| 5 |
jaxlib>=0.4.0
|
| 6 |
numpy>=1.24.0,<2.0.0
|
| 7 |
+
joblib>=1.0.0
|