option to save stepwise and composite images, add interruptibility
This commit is contained in:
parent
c05329e73b
commit
fcc9b21d19
5
.gitignore
vendored
5
.gitignore
vendored
@ -14,4 +14,7 @@
|
|||||||
*.safetensors
|
*.safetensors
|
||||||
*.json
|
*.json
|
||||||
|
|
||||||
*.egg-info
|
*.egg-info
|
||||||
|
|
||||||
|
# build/ generated from 'pip install -e .' in developer mode
|
||||||
|
build/
|
||||||
|
|||||||
@ -57,7 +57,7 @@ respect-gitignore = true
|
|||||||
# Enable Pyflakes (`F`) and a subset of the pycodestyle (`E`) codes by default.
|
# Enable Pyflakes (`F`) and a subset of the pycodestyle (`E`) codes by default.
|
||||||
# Unlike Flake8, Ruff doesn't enable pycodestyle warnings (`W`) or
|
# Unlike Flake8, Ruff doesn't enable pycodestyle warnings (`W`) or
|
||||||
# McCabe complexity (`C901`) by default.
|
# McCabe complexity (`C901`) by default.
|
||||||
select = ["E4", "E7", "E9", "F"]
|
select = ["BLE", "E4", "E7", "E9", "F", "ICN", "LOG", "PERF", "W"]
|
||||||
ignore = []
|
ignore = []
|
||||||
|
|
||||||
# Allow fix for all enabled rules (when `--fix`) is provided.
|
# Allow fix for all enabled rules (when `--fix`) is provided.
|
||||||
|
|||||||
@ -3,6 +3,7 @@ from mflux.config.config import ConfigControlnet
|
|||||||
from mflux.config.model_config import ModelConfig
|
from mflux.config.model_config import ModelConfig
|
||||||
from mflux.controlnet.flux_controlnet import Flux1Controlnet
|
from mflux.controlnet.flux_controlnet import Flux1Controlnet
|
||||||
from mflux.flux.flux import Flux1
|
from mflux.flux.flux import Flux1
|
||||||
|
from mflux.exceptions import StopImageGenerationException
|
||||||
from mflux.post_processing.image_util import ImageUtil
|
from mflux.post_processing.image_util import ImageUtil
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
@ -12,4 +13,5 @@ __all__ = [
|
|||||||
"ConfigControlnet",
|
"ConfigControlnet",
|
||||||
"ModelConfig",
|
"ModelConfig",
|
||||||
"ImageUtil",
|
"ImageUtil",
|
||||||
|
"StopImageGenerationException",
|
||||||
]
|
]
|
||||||
|
|||||||
@ -1,16 +1,16 @@
|
|||||||
import logging
|
import logging
|
||||||
from typing import TYPE_CHECKING
|
|
||||||
|
|
||||||
import mlx.core as mx
|
import mlx.core as mx
|
||||||
from mlx import nn
|
from mlx import nn
|
||||||
|
from pathlib import Path
|
||||||
from tqdm import tqdm
|
from tqdm import tqdm
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
from mflux.config.config import ConfigControlnet
|
from mflux.config.config import ConfigControlnet
|
||||||
from mflux.config.model_config import ModelConfig
|
from mflux.config.model_config import ModelConfig
|
||||||
from mflux.config.runtime_config import RuntimeConfig
|
from mflux.config.runtime_config import RuntimeConfig
|
||||||
from mflux.controlnet.controlnet_util import ControlnetUtil
|
from mflux.controlnet.controlnet_util import ControlnetUtil
|
||||||
from mflux.controlnet.transformer_controlnet import TransformerControlnet
|
from mflux.controlnet.transformer_controlnet import TransformerControlnet
|
||||||
from mflux.controlnet.weight_handler_controlnet import WeightHandlerControlnet
|
from mflux.controlnet.weight_handler_controlnet import WeightHandlerControlnet
|
||||||
|
from mflux.exceptions import StopImageGenerationException
|
||||||
from mflux.models.text_encoder.clip_encoder.clip_encoder import CLIPEncoder
|
from mflux.models.text_encoder.clip_encoder.clip_encoder import CLIPEncoder
|
||||||
from mflux.models.text_encoder.t5_encoder.t5_encoder import T5Encoder
|
from mflux.models.text_encoder.t5_encoder.t5_encoder import T5Encoder
|
||||||
from mflux.models.transformer.transformer import Transformer
|
from mflux.models.transformer.transformer import Transformer
|
||||||
@ -110,7 +110,9 @@ class Flux1Controlnet:
|
|||||||
output: str,
|
output: str,
|
||||||
controlnet_image_path: str,
|
controlnet_image_path: str,
|
||||||
controlnet_save_canny: bool = False,
|
controlnet_save_canny: bool = False,
|
||||||
config: ConfigControlnet = ConfigControlnet()
|
config: ConfigControlnet = ConfigControlnet(),
|
||||||
|
stepwise_output_dir: Path = None,
|
||||||
|
stepwise_composite_only=False
|
||||||
) -> "GeneratedImage": # fmt: off
|
) -> "GeneratedImage": # fmt: off
|
||||||
# Create a new runtime config based on the model type and input parameters
|
# Create a new runtime config based on the model type and input parameters
|
||||||
config = RuntimeConfig(config, self.model_config)
|
config = RuntimeConfig(config, self.model_config)
|
||||||
@ -139,34 +141,68 @@ class Flux1Controlnet:
|
|||||||
prompt_embeds = self.t5_text_encoder.forward(t5_tokens)
|
prompt_embeds = self.t5_text_encoder.forward(t5_tokens)
|
||||||
pooled_prompt_embeds = self.clip_text_encoder.forward(clip_tokens)
|
pooled_prompt_embeds = self.clip_text_encoder.forward(clip_tokens)
|
||||||
|
|
||||||
|
step_wise_images = []
|
||||||
|
if stepwise_output_dir:
|
||||||
|
stepwise_output_dir.mkdir(parents=True, exist_ok=True)
|
||||||
for t in time_steps:
|
for t in time_steps:
|
||||||
# Compute controlnet samples
|
try:
|
||||||
controlnet_block_samples, controlnet_single_block_samples = self.transformer_controlnet.forward(
|
# Compute controlnet samples
|
||||||
t=t,
|
controlnet_block_samples, controlnet_single_block_samples = self.transformer_controlnet.forward(
|
||||||
prompt_embeds=prompt_embeds,
|
t=t,
|
||||||
pooled_prompt_embeds=pooled_prompt_embeds,
|
prompt_embeds=prompt_embeds,
|
||||||
hidden_states=latents,
|
pooled_prompt_embeds=pooled_prompt_embeds,
|
||||||
controlnet_cond=controlnet_cond,
|
hidden_states=latents,
|
||||||
config=config,
|
controlnet_cond=controlnet_cond,
|
||||||
)
|
config=config,
|
||||||
|
)
|
||||||
|
|
||||||
# 3.t Predict the noise
|
# 3.t Predict the noise
|
||||||
noise = self.transformer.predict(
|
noise = self.transformer.predict(
|
||||||
t=t,
|
t=t,
|
||||||
prompt_embeds=prompt_embeds,
|
prompt_embeds=prompt_embeds,
|
||||||
pooled_prompt_embeds=pooled_prompt_embeds,
|
pooled_prompt_embeds=pooled_prompt_embeds,
|
||||||
hidden_states=latents,
|
hidden_states=latents,
|
||||||
config=config,
|
config=config,
|
||||||
controlnet_block_samples=controlnet_block_samples,
|
controlnet_block_samples=controlnet_block_samples,
|
||||||
controlnet_single_block_samples=controlnet_single_block_samples,
|
controlnet_single_block_samples=controlnet_single_block_samples,
|
||||||
)
|
)
|
||||||
|
|
||||||
# 4.t Take one denoise step
|
# 4.t Take one denoise step
|
||||||
dt = config.sigmas[t + 1] - config.sigmas[t]
|
dt = config.sigmas[t + 1] - config.sigmas[t]
|
||||||
latents += noise * dt
|
latents += noise * dt
|
||||||
|
|
||||||
# Evaluate to enable progress tracking
|
# Evaluate to enable progress tracking
|
||||||
mx.eval(latents)
|
mx.eval(latents)
|
||||||
|
|
||||||
|
if stepwise_output_dir:
|
||||||
|
stepwise_decoded = self.vae.decode(
|
||||||
|
Flux1Controlnet._unpack_latents(latents, config.height, config.width)
|
||||||
|
)
|
||||||
|
# performance todo: Pillow mostly uses CPU,
|
||||||
|
# can try to improve image generation performance by offloading
|
||||||
|
# stepwise image processing to the CPU via threading
|
||||||
|
stepwise_img = ImageUtil.to_image(
|
||||||
|
decoded_latents=stepwise_decoded,
|
||||||
|
seed=seed,
|
||||||
|
prompt=prompt,
|
||||||
|
quantization=self.bits,
|
||||||
|
generation_time=time_steps.format_dict["elapsed"],
|
||||||
|
lora_paths=self.lora_paths,
|
||||||
|
lora_scales=self.lora_scales,
|
||||||
|
config=config,
|
||||||
|
)
|
||||||
|
step_wise_images.append(stepwise_img)
|
||||||
|
if not stepwise_composite_only:
|
||||||
|
stepwise_img.save(
|
||||||
|
path=stepwise_output_dir / f"seed_{seed}_step{t+1}of{len(time_steps)}.png",
|
||||||
|
export_json_metadata=False,
|
||||||
|
)
|
||||||
|
except KeyboardInterrupt: # noqa: PERF203
|
||||||
|
raise StopImageGenerationException(f"Stopping image generation at step {t+1}/{len(time_steps)}")
|
||||||
|
finally:
|
||||||
|
if step_wise_images and stepwise_output_dir:
|
||||||
|
composite_img = ImageUtil.to_composite_image(step_wise_images)
|
||||||
|
composite_img.save(stepwise_output_dir / f"seed_{seed}_composite.png")
|
||||||
|
|
||||||
# 5. Decode the latent array and return the image
|
# 5. Decode the latent array and return the image
|
||||||
latents = Flux1Controlnet._unpack_latents(latents, config.height, config.width)
|
latents = Flux1Controlnet._unpack_latents(latents, config.height, config.width)
|
||||||
|
|||||||
18
src/mflux/exceptions.py
Normal file
18
src/mflux/exceptions.py
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
class MFluxException(Exception):
|
||||||
|
"""base class for all custom exceptions in mflux package."""
|
||||||
|
|
||||||
|
|
||||||
|
class ImageSavingException(MFluxException):
|
||||||
|
"""error ocurred while attempting to save image to storage."""
|
||||||
|
|
||||||
|
|
||||||
|
class MetadataEmbedException(MFluxException):
|
||||||
|
"""error ocurred while attempting to embed metadata in image"""
|
||||||
|
|
||||||
|
|
||||||
|
class MFluxUserException(MFluxException):
|
||||||
|
"""an exception raised by user behavior or intention."""
|
||||||
|
|
||||||
|
|
||||||
|
class StopImageGenerationException(MFluxUserException):
|
||||||
|
"""user has requested to stop a image generation in progress."""
|
||||||
@ -1,10 +1,12 @@
|
|||||||
import mlx.core as mx
|
import mlx.core as mx
|
||||||
|
from pathlib import Path
|
||||||
from mlx import nn
|
from mlx import nn
|
||||||
from tqdm import tqdm
|
from tqdm import tqdm
|
||||||
|
|
||||||
from mflux.config.config import Config
|
from mflux.config.config import Config
|
||||||
from mflux.config.model_config import ModelConfig
|
from mflux.config.model_config import ModelConfig
|
||||||
from mflux.config.runtime_config import RuntimeConfig
|
from mflux.config.runtime_config import RuntimeConfig
|
||||||
|
from mflux.exceptions import StopImageGenerationException
|
||||||
from mflux.models.text_encoder.clip_encoder.clip_encoder import CLIPEncoder
|
from mflux.models.text_encoder.clip_encoder.clip_encoder import CLIPEncoder
|
||||||
from mflux.models.text_encoder.t5_encoder.t5_encoder import T5Encoder
|
from mflux.models.text_encoder.t5_encoder.t5_encoder import T5Encoder
|
||||||
from mflux.models.transformer.transformer import Transformer
|
from mflux.models.transformer.transformer import Transformer
|
||||||
@ -69,7 +71,7 @@ class Flux1:
|
|||||||
if weights.quantization_level is not None:
|
if weights.quantization_level is not None:
|
||||||
self._set_model_weights(weights)
|
self._set_model_weights(weights)
|
||||||
|
|
||||||
def generate_image(self, seed: int, prompt: str, config: Config = Config()) -> GeneratedImage:
|
def generate_image(self, seed: int, prompt: str, config: Config = Config(), stepwise_output_dir: Path = None, stepwise_composite_only=False) -> GeneratedImage: # fmt: off
|
||||||
# Create a new runtime config based on the model type and input parameters
|
# Create a new runtime config based on the model type and input parameters
|
||||||
config = RuntimeConfig(config, self.model_config)
|
config = RuntimeConfig(config, self.model_config)
|
||||||
time_steps = tqdm(range(config.num_inference_steps))
|
time_steps = tqdm(range(config.num_inference_steps))
|
||||||
@ -86,22 +88,54 @@ class Flux1:
|
|||||||
prompt_embeds = self.t5_text_encoder.forward(t5_tokens)
|
prompt_embeds = self.t5_text_encoder.forward(t5_tokens)
|
||||||
pooled_prompt_embeds = self.clip_text_encoder.forward(clip_tokens)
|
pooled_prompt_embeds = self.clip_text_encoder.forward(clip_tokens)
|
||||||
|
|
||||||
|
step_wise_images = []
|
||||||
|
if stepwise_output_dir:
|
||||||
|
stepwise_output_dir.mkdir(parents=True, exist_ok=True)
|
||||||
for t in time_steps:
|
for t in time_steps:
|
||||||
# 3.t Predict the noise
|
try:
|
||||||
noise = self.transformer.predict(
|
# 3.t Predict the noise
|
||||||
t=t,
|
noise = self.transformer.predict(
|
||||||
prompt_embeds=prompt_embeds,
|
t=t,
|
||||||
pooled_prompt_embeds=pooled_prompt_embeds,
|
prompt_embeds=prompt_embeds,
|
||||||
hidden_states=latents,
|
pooled_prompt_embeds=pooled_prompt_embeds,
|
||||||
config=config,
|
hidden_states=latents,
|
||||||
)
|
config=config,
|
||||||
|
)
|
||||||
|
|
||||||
# 4.t Take one denoise step
|
# 4.t Take one denoise step
|
||||||
dt = config.sigmas[t + 1] - config.sigmas[t]
|
dt = config.sigmas[t + 1] - config.sigmas[t]
|
||||||
latents += noise * dt
|
latents += noise * dt
|
||||||
|
|
||||||
# Evaluate to enable progress tracking
|
# Evaluate to enable progress tracking
|
||||||
mx.eval(latents)
|
mx.eval(latents)
|
||||||
|
|
||||||
|
if stepwise_output_dir:
|
||||||
|
stepwise_decoded = self.vae.decode(Flux1._unpack_latents(latents, config.height, config.width))
|
||||||
|
# performance todo: Pillow mostly uses CPU,
|
||||||
|
# can try to improve image generation performance by offloading
|
||||||
|
# stepwise image processing to the CPU via threading
|
||||||
|
stepwise_img = ImageUtil.to_image(
|
||||||
|
decoded_latents=stepwise_decoded,
|
||||||
|
seed=seed,
|
||||||
|
prompt=prompt,
|
||||||
|
quantization=self.bits,
|
||||||
|
generation_time=time_steps.format_dict["elapsed"],
|
||||||
|
lora_paths=self.lora_paths,
|
||||||
|
lora_scales=self.lora_scales,
|
||||||
|
config=config,
|
||||||
|
)
|
||||||
|
step_wise_images.append(stepwise_img)
|
||||||
|
if not stepwise_composite_only:
|
||||||
|
stepwise_img.save(
|
||||||
|
path=stepwise_output_dir / f"seed_{seed}_step{t+1}of{len(time_steps)}.png",
|
||||||
|
export_json_metadata=False,
|
||||||
|
)
|
||||||
|
except KeyboardInterrupt: # noqa: PERF203
|
||||||
|
raise StopImageGenerationException(f"Stopping image generation at step {t+1}/{len(time_steps)}")
|
||||||
|
finally:
|
||||||
|
if step_wise_images and stepwise_output_dir:
|
||||||
|
composite_img = ImageUtil.to_composite_image(step_wise_images)
|
||||||
|
composite_img.save(stepwise_output_dir / f"seed_{seed}_composite.png")
|
||||||
|
|
||||||
# 5. Decode the latent array and return the image
|
# 5. Decode the latent array and return the image
|
||||||
latents = Flux1._unpack_latents(latents, config.height, config.width)
|
latents = Flux1._unpack_latents(latents, config.height, config.width)
|
||||||
|
|||||||
@ -1,7 +1,8 @@
|
|||||||
import argparse
|
import argparse
|
||||||
import time
|
import time
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
from mflux import Flux1, Config, ModelConfig
|
from mflux import Flux1, Config, ModelConfig, StopImageGenerationException
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
@ -14,6 +15,7 @@ def main():
|
|||||||
parser.add_argument("--height", type=int, default=1024, help="Image height (Default is 1024)")
|
parser.add_argument("--height", type=int, default=1024, help="Image height (Default is 1024)")
|
||||||
parser.add_argument("--width", type=int, default=1024, help="Image width (Default is 1024)")
|
parser.add_argument("--width", type=int, default=1024, help="Image width (Default is 1024)")
|
||||||
parser.add_argument("--steps", type=int, default=None, help="Inference Steps")
|
parser.add_argument("--steps", type=int, default=None, help="Inference Steps")
|
||||||
|
parser.add_argument('--stepwise-image-output-dir', type=str, default=None, help='Output dir to write step-wise images and their final composite image to.')
|
||||||
parser.add_argument("--guidance", type=float, default=3.5, help="Guidance Scale (Default is 3.5)")
|
parser.add_argument("--guidance", type=float, default=3.5, help="Guidance Scale (Default is 3.5)")
|
||||||
parser.add_argument("--quantize", "-q", type=int, choices=[4, 8], default=None, help="Quantize the model (4 or 8, Default is None)")
|
parser.add_argument("--quantize", "-q", type=int, choices=[4, 8], default=None, help="Quantize the model (4 or 8, Default is None)")
|
||||||
parser.add_argument("--path", type=str, default=None, help="Local path for loading a model from disk")
|
parser.add_argument("--path", type=str, default=None, help="Local path for loading a model from disk")
|
||||||
@ -39,20 +41,24 @@ def main():
|
|||||||
lora_scales=args.lora_scales,
|
lora_scales=args.lora_scales,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Generate an image
|
try:
|
||||||
image = flux.generate_image(
|
# Generate an image
|
||||||
seed=int(time.time()) if args.seed is None else args.seed,
|
image = flux.generate_image(
|
||||||
prompt=args.prompt,
|
seed=int(time.time()) if args.seed is None else args.seed,
|
||||||
config=Config(
|
prompt=args.prompt,
|
||||||
num_inference_steps=args.steps,
|
config=Config(
|
||||||
height=args.height,
|
num_inference_steps=args.steps,
|
||||||
width=args.width,
|
height=args.height,
|
||||||
guidance=args.guidance,
|
width=args.width,
|
||||||
),
|
guidance=args.guidance,
|
||||||
)
|
),
|
||||||
|
stepwise_output_dir=Path(args.stepwise_image_output_dir) if args.stepwise_image_output_dir else None,
|
||||||
|
)
|
||||||
|
|
||||||
# Save the image
|
# Save the image
|
||||||
image.save(path=args.output, export_json_metadata=args.metadata)
|
image.save(path=args.output, export_json_metadata=args.metadata)
|
||||||
|
except StopImageGenerationException as stop_exc:
|
||||||
|
print(stop_exc)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
@ -1,7 +1,8 @@
|
|||||||
import argparse
|
import argparse
|
||||||
import time
|
import time
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
from mflux import Flux1Controlnet, ConfigControlnet, ModelConfig
|
from mflux import Flux1Controlnet, ConfigControlnet, ModelConfig, StopImageGenerationException
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
@ -17,6 +18,7 @@ def main():
|
|||||||
parser.add_argument("--height", type=int, default=1024, help="Image height (Default is 1024)")
|
parser.add_argument("--height", type=int, default=1024, help="Image height (Default is 1024)")
|
||||||
parser.add_argument("--width", type=int, default=1024, help="Image width (Default is 1024)")
|
parser.add_argument("--width", type=int, default=1024, help="Image width (Default is 1024)")
|
||||||
parser.add_argument("--steps", type=int, default=None, help="Inference Steps")
|
parser.add_argument("--steps", type=int, default=None, help="Inference Steps")
|
||||||
|
parser.add_argument('--stepwise-image-output-dir', type=str, default=None, help='Output dir to write step-wise images and their final composite image to.')
|
||||||
parser.add_argument("--guidance", type=float, default=3.5, help="Guidance Scale (Default is 3.5)")
|
parser.add_argument("--guidance", type=float, default=3.5, help="Guidance Scale (Default is 3.5)")
|
||||||
parser.add_argument("--quantize", "-q", type=int, choices=[4, 8], default=None, help="Quantize the model (4 or 8, Default is None)")
|
parser.add_argument("--quantize", "-q", type=int, choices=[4, 8], default=None, help="Quantize the model (4 or 8, Default is None)")
|
||||||
parser.add_argument("--path", type=str, default=None, help="Local path for loading a model from disk")
|
parser.add_argument("--path", type=str, default=None, help="Local path for loading a model from disk")
|
||||||
@ -42,24 +44,28 @@ def main():
|
|||||||
lora_scales=args.lora_scales,
|
lora_scales=args.lora_scales,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Generate an image
|
try:
|
||||||
image = flux.generate_image(
|
# Generate an image
|
||||||
seed=int(time.time()) if args.seed is None else args.seed,
|
image = flux.generate_image(
|
||||||
prompt=args.prompt,
|
seed=int(time.time()) if args.seed is None else args.seed,
|
||||||
output=args.output,
|
prompt=args.prompt,
|
||||||
controlnet_image_path=args.controlnet_image_path,
|
output=args.output,
|
||||||
controlnet_save_canny=args.controlnet_save_canny,
|
controlnet_image_path=args.controlnet_image_path,
|
||||||
config=ConfigControlnet(
|
controlnet_save_canny=args.controlnet_save_canny,
|
||||||
num_inference_steps=args.steps,
|
config=ConfigControlnet(
|
||||||
height=args.height,
|
num_inference_steps=args.steps,
|
||||||
width=args.width,
|
height=args.height,
|
||||||
guidance=args.guidance,
|
width=args.width,
|
||||||
controlnet_strength=args.controlnet_strength,
|
guidance=args.guidance,
|
||||||
),
|
controlnet_strength=args.controlnet_strength,
|
||||||
)
|
),
|
||||||
|
stepwise_output_dir=Path(args.stepwise_image_output_dir) if args.stepwise_image_output_dir else None,
|
||||||
|
)
|
||||||
|
|
||||||
# Save the image
|
# Save the image
|
||||||
image.save(path=args.output, export_json_metadata=args.metadata)
|
image.save(path=args.output, export_json_metadata=args.metadata)
|
||||||
|
except StopImageGenerationException as stop_exc:
|
||||||
|
print(stop_exc)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
|
import typing as t
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
import PIL
|
import PIL
|
||||||
import PIL.Image
|
import PIL.Image
|
||||||
import mlx.core as mx
|
import mlx.core as mx
|
||||||
@ -47,6 +47,17 @@ class ImageUtil:
|
|||||||
controlnet_strength=config.controlnet_strength if isinstance(config.config, ConfigControlnet) else None,
|
controlnet_strength=config.controlnet_strength if isinstance(config.config, ConfigControlnet) else None,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def to_composite_image(generated_images: t.List[GeneratedImage]) -> Image:
|
||||||
|
# stitch horizontally
|
||||||
|
total_width = sum(gen_img.image.width for gen_img in generated_images)
|
||||||
|
max_height = max(gen_img.image.height for gen_img in generated_images)
|
||||||
|
composite_img = Image.new("RGB", (total_width, max_height))
|
||||||
|
current_x = 0
|
||||||
|
for index, gen_img in enumerate(generated_images):
|
||||||
|
composite_img.paste(gen_img.image, (current_x, 0))
|
||||||
|
current_x += gen_img.image.width
|
||||||
|
return composite_img
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _denormalize(images: mx.array) -> mx.array:
|
def _denormalize(images: mx.array) -> mx.array:
|
||||||
return mx.clip((images / 2 + 0.5), 0, 1)
|
return mx.clip((images / 2 + 0.5), 0, 1)
|
||||||
@ -119,7 +130,7 @@ class ImageUtil:
|
|||||||
if metadata is not None:
|
if metadata is not None:
|
||||||
ImageUtil._embed_metadata(metadata, file_path)
|
ImageUtil._embed_metadata(metadata, file_path)
|
||||||
log.info(f"Metadata embedded successfully at: {file_path}")
|
log.info(f"Metadata embedded successfully at: {file_path}")
|
||||||
except Exception as e:
|
except Exception as e: # noqa: BLE001
|
||||||
log.error(f"Error saving image: {e}")
|
log.error(f"Error saving image: {e}")
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@ -145,5 +156,5 @@ class ImageUtil:
|
|||||||
# Save the image with metadata
|
# Save the image with metadata
|
||||||
image.save(path, exif=exif_bytes)
|
image.save(path, exif=exif_bytes)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e: # noqa: BLE001
|
||||||
log.error(f"Error embedding metadata: {e}")
|
log.error(f"Error embedding metadata: {e}")
|
||||||
|
|||||||
@ -1,7 +1,39 @@
|
|||||||
|
#!/bin/zsh -e
|
||||||
|
# ^ safe to assume Mac devs have zsh installed
|
||||||
|
# default since Catalina in 2019
|
||||||
|
|
||||||
|
mkdir -p /tmp/mflux-test
|
||||||
|
|
||||||
mflux-generate \
|
mflux-generate \
|
||||||
--prompt "Luxury food photograph" \
|
--prompt "Luxury food photograph" \
|
||||||
--model schnell \
|
--model schnell \
|
||||||
--steps 2 \
|
--steps 2 \
|
||||||
--seed 2 \
|
--seed 2 \
|
||||||
--height 1024 \
|
--height 512 \
|
||||||
--width 1024
|
--width 512 \
|
||||||
|
--output /tmp/mflux-test/luxury_food.png
|
||||||
|
|
||||||
|
# generate an image of a blue bird, then use it as input for the following test
|
||||||
|
mflux-generate \
|
||||||
|
--prompt "blue bird, morning, spring" \
|
||||||
|
--model schnell \
|
||||||
|
--steps 2 \
|
||||||
|
--seed 24 \
|
||||||
|
--height 512 \
|
||||||
|
--width 512 \
|
||||||
|
--stepwise-image-output-dir /tmp/mflux-test \
|
||||||
|
--output /tmp/mflux-test/sf_blue_bird.png
|
||||||
|
|
||||||
|
# use the image from the prior test, generate an image with similar visual structure
|
||||||
|
mflux-generate-controlnet \
|
||||||
|
--prompt "yellow bird, afternoon, snowy mountain" \
|
||||||
|
--model schnell \
|
||||||
|
--controlnet-image-path /tmp/mflux-test/sf_blue_bird.png \
|
||||||
|
--controlnet-strength 0.7 \
|
||||||
|
--controlnet-save-canny \
|
||||||
|
--steps 2 \
|
||||||
|
--seed 42 \
|
||||||
|
--height 512 \
|
||||||
|
--width 512 \
|
||||||
|
--output /tmp/mflux-test/controlnet_sf_yellow_bird.png \
|
||||||
|
--stepwise-image-output-dir /tmp/mflux-test
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user