10 Commits
Author SHA1 Message Date
InformEthics 937eb3101d Add files via upload 2025-03-09 21:14:26 +00:00
InformEthics ce9eec1735 Update README.md 2025-03-04 20:22:40 +00:00
InformEthics ac23f89b9f Delete examples directory 2025-03-04 19:58:56 +00:00
InformEthics d00e2073f6 Delete .github/workflows directory 2025-03-04 19:58:44 +00:00
InformEthics 79a23faea3 Delete workflows directory 2025-03-04 19:58:16 +00:00
InformEthics 1dfe46dad0 Add files via upload 2024-11-30 01:16:12 +00:00
InformEthics ed18d9446c Delete __init__-2.py 2024-11-30 01:15:21 +00:00
InformEthics 2573467b1a Delete __init__.py 2024-11-30 01:14:57 +00:00
InformEthics b498b11bd7 Add files via upload 2024-11-30 01:14:37 +00:00
InformEthics 11a3da87bd Delete Mflux_Comfy directory 2024-11-30 01:10:35 +00:00
29 changed files with 617 additions and 2203 deletions
-23
View File
@@ -1,23 +0,0 @@
name: Publish to Comfy registry
on:
workflow_dispatch:
push:
branches:
- main
paths:
- "pyproject.toml"
jobs:
publish-node:
name: Publish Custom Node to registry
runs-on: ubuntu-latest
# if this is a forked repository. Skipping the workflow.
if: github.event.repository.fork == false
steps:
- name: Check out code
uses: actions/checkout@v4
- name: Publish Custom Node
uses: Comfy-Org/publish-node-action@main
with:
## Add your own personal access token to your Github Repository secrets and reference it here.
personal_access_token: ${{ secrets.REGISTRY_ACCESS_TOKEN }}
+601
View File
@@ -0,0 +1,601 @@
import os
import json
import random
import folder_paths
import numpy as np
import torch
from pathlib import Path
from huggingface_hub import snapshot_download
from folder_paths import get_filename_list, get_output_directory, models_dir
from PIL import Image
from PIL.PngImagePlugin import PngInfo
from tqdm import tqdm
import comfy.utils as utils
import mlx.core as mx
from mflux.config.model_config import ModelConfig
from mflux.config.config import Config, ConfigControlnet
from mflux.flux.flux import Flux1
from mflux.controlnet.controlnet_util import ControlnetUtil
from mflux.controlnet.flux_controlnet import Flux1Controlnet
from mflux.config.runtime_config import RuntimeConfig
from mflux.post_processing.array_util import ArrayUtil
from mflux.post_processing.image_util import ImageUtil
from mflux.post_processing.generated_image import GeneratedImage
from mflux.latent_creator.latent_creator import LatentCreator
from mflux.post_processing.stepwise_handler import StepwiseHandler
from mflux.controlnet.controlnet_util import ControlnetUtil
from mflux.models.vae.vae import VAE
flux_cache = {}
def load_or_create_flux(model, quantize, path, lora_paths, lora_scales, is_controlnet=False):
key = (model, quantize, path, tuple(lora_paths), tuple(lora_scales), is_controlnet)
if key not in flux_cache:
flux_cache.clear()
FluxClass = Flux1Controlnet if is_controlnet else Flux1
flux_cache[key] = FluxClass(
model_config=ModelConfig.from_alias(model),
quantize=quantize,
local_path=path,
lora_paths=lora_paths,
lora_scales=lora_scales,
)
# This code is licensed under the Apache 2.0 License.
# Portions of this code are derived from the work of CharafChnioune at https://github.com/CharafChnioune/MFLUX-WEBUI
return flux_cache[key]
def get_lora_info(Loras):
if Loras:
return Loras.lora_paths, Loras.lora_scales
return [], []
def generate_image(prompt, model, seed, width, height, steps, guidance, quantize="None", metadata=True, Local_model="", img2img=None, Loras=None, ControlNet=None):
model = "dev" if "dev" in Local_model.lower() else "schnell" if "schnell" in Local_model.lower() else model
print(f"Using model: {model}")
img2img_image_path = img2img.image_path if img2img else None
img2img_strength = img2img.strength if img2img else None
lora_paths, lora_scales = get_lora_info(Loras)
if Loras:
print(f"LoRA paths: {lora_paths}")
print(f"LoRA scales: {lora_scales}")
ControlNet_model_selection, control_image_path, controlnet_strength, save_canny = None, None, None, None
if ControlNet and isinstance(ControlNet, MfluxControlNetPipeline):
ControlNet_model_selection = ControlNet.model_selection
save_canny = ControlNet.save_canny
control_strength = ControlNet.control_strength
control_image_path = ControlNet.control_image_path
quantize = None if quantize == "None" else int(quantize)
seed = random.randint(0, 0xffffffffffffffff) if seed == -1 else int(seed)
print(f"Using seed: {seed}")
steps = min(max(steps, 2), 4) if model == "schnell" else steps
flux = load_or_create_flux(model, quantize, Local_model if Local_model else None, lora_paths, lora_scales, ControlNet is not None)
config_class = ConfigControlnet if ControlNet else Config
config = RuntimeConfig(
config=config_class(
num_inference_steps=steps,
height=height,
width=width,
guidance=guidance,
**({"controlnet_strength": control_strength} if ControlNet else {
"init_image_path": img2img_image_path,
"init_image_strength": img2img_strength
})
),
model_config=flux.model_config
)
time_steps = tqdm(range(config.init_time_step if not ControlNet else 0, config.num_inference_steps))
stepwise_handler = StepwiseHandler(
flux=flux,
config=config,
seed=seed,
prompt=prompt,
time_steps=time_steps,
output_dir=None
)
t5_tokens = flux.t5_tokenizer.tokenize(prompt)
clip_tokens = flux.clip_tokenizer.tokenize(prompt)
prompt_embeds = flux.t5_text_encoder(t5_tokens)
pooled_prompt_embeds = flux.clip_text_encoder(clip_tokens)
if ControlNet:
control_image = ImageUtil.load_image(control_image_path)
control_image = ControlnetUtil.scale_image(config.height, config.width, control_image)
control_image = ControlnetUtil.preprocess_canny(control_image)
controlnet_cond = ImageUtil.to_array(control_image)
controlnet_cond = flux.vae.encode(controlnet_cond)
controlnet_cond = (controlnet_cond / flux.vae.scaling_factor) + flux.vae.shift_factor
controlnet_cond = ArrayUtil.pack_latents(latents=controlnet_cond, height=config.height, width=config.width)
latents = LatentCreator.create(seed=seed, height=config.height, width=config.width)
else:
latents = LatentCreator.create_for_txt2img_or_img2img(seed, config, flux.vae)
pbar = None
for gen_step, t in enumerate(time_steps, 1):
if gen_step == 2:
pbar = utils.ProgressBar(total=steps)
predict_args = {
't': t,
'prompt_embeds': prompt_embeds,
'pooled_prompt_embeds': pooled_prompt_embeds,
'hidden_states': latents,
'config': config,
}
if ControlNet:
controlnet_block_samples, controlnet_single_block_samples = flux.transformer_controlnet(
t=t,
prompt_embeds=prompt_embeds,
pooled_prompt_embeds=pooled_prompt_embeds,
hidden_states=latents,
controlnet_cond=controlnet_cond,
config=config,
)
predict_args['controlnet_block_samples'] = controlnet_block_samples
predict_args['controlnet_single_block_samples'] = controlnet_single_block_samples
noise = flux.transformer.predict(**predict_args)
dt = config.sigmas[t + 1] - config.sigmas[t]
latents += noise * dt
stepwise_handler.process_step(gen_step, latents)
if pbar:
pbar.update(1)
mx.eval(latents)
if pbar:
pbar.update(1)
latents = ArrayUtil.unpack_latents(latents=latents, height=config.height, width=config.width)
decoded = flux.vae.decode(latents)
normalized_latents = ImageUtil._denormalize(decoded)
numpy_image = ImageUtil._to_numpy(normalized_latents)
tensor_image = torch.from_numpy(numpy_image)
if tensor_image.dim() == 3:
tensor_image = tensor_image.unsqueeze(0)
return (tensor_image,)
def save_images_with_metadata(images, prompt, model, quantize, Local_model, seed, height, width, steps, guidance, lora_paths, lora_scales, img2img_image_path, img2img_strength, filename_prefix="Mflux", full_prompt=None, extra_pnginfo=None):
output_dir = folder_paths.get_output_directory()
full_output_folder, filename, counter, subfolder, filename_prefix = folder_paths.get_save_image_path(
filename_prefix, output_dir, images[0].shape[1], images[0].shape[0])
mflux_output_folder = os.path.join(full_output_folder, "MFlux")
os.makedirs(mflux_output_folder, exist_ok=True)
existing_files = os.listdir(mflux_output_folder)
existing_counters = [
int(f.split("_")[-1].split(".")[0])
for f in existing_files
if f.startswith(filename_prefix) and f.endswith(".png")
]
counter = max(existing_counters, default=0) + 1
results = list()
for image in images:
i = 255. * image.cpu().numpy().squeeze()
img = Image.fromarray(np.clip(i, 0, 255).astype(np.uint8))
metadata = None
if full_prompt is not None or extra_pnginfo is not None:
metadata = PngInfo()
if full_prompt is not None:
metadata.add_text("full_prompt", json.dumps(full_prompt))
if extra_pnginfo is not None:
for x in extra_pnginfo:
metadata.add_text(x, json.dumps(extra_pnginfo[x]))
image_file = f"{filename_prefix}_{counter:05}.png"
img.save(os.path.join(mflux_output_folder, image_file), pnginfo=metadata, compress_level=4)
results.append({
"filename": image_file,
"subfolder": subfolder,
"type": "output"
})
metadata_jsonfile = os.path.join(mflux_output_folder, f"{filename_prefix}_{counter:05}.json")
json_dict = {
"prompt": prompt,
"model": model,
"quantize": quantize,
"seed": seed,
"height": height,
"width": width,
"steps": steps,
"guidance": guidance if model == "dev" else None,
"Local_model": Local_model,
"img2img_image_path": img2img_image_path,
"img2img_strength": img2img_strength,
"lora_paths": lora_paths,
"lora_scales": lora_scales,
}
with open(metadata_jsonfile, 'w') as metadata_file:
json.dump(json_dict, metadata_file, indent=4)
counter += 1
return {"ui": {"images": results}, "counter": counter}
def create_directory(directory):
if not os.path.exists(directory):
os.makedirs(directory)
return directory
mflux_dir = os.path.join(models_dir, "Mflux")
create_directory(mflux_dir)
def get_full_model_path(model_dir, model_name):
return os.path.join(model_dir, model_name)
def download_hg_model(model_version):
repo_id = f"madroid/{model_version}" if "4bit" in model_version else f"AITRADER/{model_version}"
model_checkpoint = get_full_model_path(mflux_dir, model_version)
if not os.path.exists(model_checkpoint):
print(f"Downloading model {model_version} to {model_checkpoint}...")
try:
snapshot_download(repo_id=repo_id, local_dir=model_checkpoint)
except Exception as e:
print(f"Error downloading model {model_version}: {e}")
return None
else:
print(f"Model {model_version} already exists at {model_checkpoint}. Skipping download.")
return model_checkpoint
class MfluxModelsDownloader:
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"model_version": (["flux.1-schnell-mflux-4bit", "flux.1-dev-mflux-4bit", "MFLUX.1-schnell-8-bit", "MFLUX.1-dev-8-bit"], {"default": "flux.1-schnell-mflux-4bit"}),
}
}
RETURN_TYPES = ("PATH",)
RETURN_NAMES = ("Downloaded_model",)
CATEGORY = "MFlux"
FUNCTION = "download_model"
def download_model(self, model_version):
model_path = download_hg_model(model_version)
if model_path:
return (model_path,)
return (None,)
class MfluxCustomModels:
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"model": (["dev", "schnell"], {"default": "schnell"}),
"quantize": (["4", "8"], {"default": "4"}),
},
"optional": {
"Loras": ("MfluxLorasPipeline",),
"custom_identifier": ("STRING", {"default": "", "tooltip": "Custom identifier for the model."}),
}
}
RETURN_TYPES = ("PATH",)
RETURN_NAMES = ("Custom_model",)
CATEGORY = "MFlux"
FUNCTION = "save_model"
def save_model(self, model, quantize, Loras=None, custom_identifier=""):
identifier = custom_identifier if custom_identifier else "default"
save_dir = get_full_model_path(mflux_dir, f"Mflux-{model}-{quantize}bit-{identifier}")
create_directory(save_dir)
print(f"Saving model: {model}, quantize: {quantize}, save_dir: {save_dir}")
lora_paths, lora_scales = get_lora_info(Loras)
if lora_paths:
print(f"LoRA paths: {lora_paths}")
print(f"LoRA scales: {lora_scales}")
model_config = ModelConfig.from_alias(model)
flux = Flux1(model_config=model_config, quantize=int(quantize), lora_paths=lora_paths, lora_scales=lora_scales)
flux.save_model(save_dir)
print(f"Model saved to {save_dir}")
return (save_dir,)
class MfluxModelsLoader:
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"model_name": (cls.get_sorted_model_paths() or ["None"], {"default": cls.get_sorted_model_paths()[0] if cls.get_sorted_model_paths() else "None"}),
},
"optional": {
"free_path": ("STRING", {"default": "", "tooltip": "Free model path. If provided, this will override the model_name."}),
}
}
RETURN_TYPES = ("PATH",)
RETURN_NAMES = ("Local_model",)
CATEGORY = "MFlux"
FUNCTION = "load"
@classmethod
def get_sorted_model_paths(cls):
model_paths = [p.name for p in Path(mflux_dir).iterdir() if p.is_dir()]
return sorted(model_paths, key=lambda x: x.lower())
def load(self, model_name="", free_path=""):
if free_path:
full_model_path = free_path
if not os.path.exists(full_model_path):
raise ValueError(f"Provided custom path does not exist: {full_model_path}")
elif model_name and model_name != "None":
full_model_path = get_full_model_path(mflux_dir, model_name)
else:
raise ValueError("Either 'model_name' must be provided or 'free_path' must be used.")
return (full_model_path,)
class QuickMfluxNode:
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"prompt": ("STRING", {"multiline": True, "dynamicPrompts": True, "tooltip": "The text to be encoded.", "default": "Luxury food photograph"}),
"model": (["dev", "schnell"], {"default": "schnell"}),
"quantize": (["None", "4", "8"], {"default": "4"}),
"seed": ("INT", {"default": -1, "min": -1, "max": 0xffffffffffffffff}),
"width": ("INT", {"default": 512}),
"height": ("INT", {"default": 512}),
"steps": ("INT", {"default": 2, "min": 1}),
"guidance": ("FLOAT", {"default": 3.5, "min": 0.0}),
"metadata": ("BOOLEAN", {"default": True, "label_on": "True", "label_off": "False"}),
},
"optional": {
"Local_model": ("PATH",),
"img2img": ("MfluxImg2ImgPipeline",),
"Loras": ("MfluxLorasPipeline",),
"ControlNet": ("MfluxControlNetPipeline",),
},
"hidden": {
"full_prompt": "PROMPT",
"extra_pnginfo": "EXTRA_PNGINFO"
}
}
RETURN_TYPES = ("IMAGE",)
CATEGORY = "MFlux"
FUNCTION = "generate"
def generate(self, prompt, model, seed, width, height, steps, guidance, quantize="None", metadata=True, Local_model="", img2img=None, Loras=None, ControlNet=None, full_prompt=None, extra_pnginfo=None):
generated_images = generate_image(
prompt, model, seed, width, height, steps, guidance, quantize, metadata, Local_model, img2img, Loras, ControlNet
)
img2img_image_path = img2img.image_path if img2img else None
img2img_strength = img2img.strength if img2img else None
lora_paths, lora_scales = get_lora_info(Loras)
if metadata:
save_images_params = {
"images": generated_images,
"prompt": prompt,
"model": "dev" if "dev" in Local_model.lower() else "schnell" if "schnell" in Local_model.lower() else model,
"quantize": quantize,
"Local_model": Local_model,
"seed": seed,
"height": height,
"width": width,
"steps": steps,
"guidance": guidance,
"img2img_image_path": img2img_image_path,
"img2img_strength": img2img_strength,
"lora_paths": lora_paths,
"lora_scales": lora_scales,
"filename_prefix": "Mflux",
"full_prompt": full_prompt,
"extra_pnginfo": extra_pnginfo,
}
result = save_images_with_metadata(**save_images_params)
counter = result["counter"]
return generated_images
class MfluxImg2ImgPipeline:
def __init__(self, image_path, strength):
self.image_path = image_path
self.strength = strength
def clear_cache(self):
self.image_path = None
self.strength = None
class MfluxImg2Img:
@classmethod
def INPUT_TYPES(cls):
input_dir = folder_paths.get_input_directory()
files = [f for f in os.listdir(input_dir) if os.path.isfile(os.path.join(input_dir, f))]
return {
"required": {
"image": (sorted(files), {"image_upload": True}),
"strength": ("FLOAT", {"default": 0.4, "min": 0.0, "max": 1.0, "step": 0.01}),
}
}
CATEGORY = "MFlux"
RETURN_TYPES = ("MfluxImg2ImgPipeline", "INT", "INT")
RETURN_NAMES = ("img2img", "width", "height")
FUNCTION = "load_and_process"
def load_and_process(self, image, strength):
image_path = folder_paths.get_annotated_filepath(image)
with Image.open(image_path) as img:
width, height = img.size
return MfluxImg2ImgPipeline(image_path, strength), width, height
@classmethod
def IS_CHANGED(cls, image, strength):
image_hash = hash(image)
strength_rounded = round(float(strength), 2)
return (image_hash, strength_rounded)
@classmethod
def VALIDATE_INPUTS(cls, image, strength):
if not folder_paths.exists_annotated_filepath(image):
return "Invalid image file: {}".format(image)
if not isinstance(strength, (int, float)):
return "Strength must be a number"
return True
class MfluxLorasPipeline:
def __init__(self, lora_paths, lora_scales):
self.lora_paths = lora_paths
self.lora_scales = lora_scales
def clear_cache(self):
self.lora_paths = []
self.lora_scales = []
class MfluxLorasLoader:
@classmethod
def INPUT_TYPES(cls):
lora_base_path = folder_paths.models_dir
loras_relative = ["None"] + folder_paths.get_filename_list("loras")
inputs = {
"required": {
"Lora1": (loras_relative,),
"scale1": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}),
"Lora2": (loras_relative,),
"scale2": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}),
"Lora3": (loras_relative,),
"scale3": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}),
},
"optional": {
"Loras": ("MfluxLorasPipeline",)
}
}
return inputs
RETURN_TYPES = ("MfluxLorasPipeline",)
RETURN_NAMES = ("Loras",)
FUNCTION = "lora_stacker"
CATEGORY = "MFlux"
def lora_stacker(self, Loras=None, **kwargs):
lora_base_path = folder_paths.models_dir
lora_models = [
(os.path.join(lora_base_path, "loras", kwargs.get(f"Lora{i}")), kwargs.get(f"scale{i}"))
for i in range(1, 4) if kwargs.get(f"Lora{i}") != "None"
]
if Loras is not None and isinstance(Loras, MfluxLorasPipeline):
lora_paths = Loras.lora_paths
lora_scales = Loras.lora_scales
if lora_paths and lora_scales:
lora_models.extend(zip(lora_paths, lora_scales))
if lora_models:
lora_paths, lora_scales = zip(*lora_models)
else:
lora_paths, lora_scales = [], []
return (MfluxLorasPipeline(list(lora_paths), list(lora_scales)),)
class MfluxControlNetPipeline:
def __init__(self, model_selection, control_image_path, control_strength, save_canny=False):
self.model_selection = model_selection
self.control_image_path = control_image_path
self.control_strength = control_strength
self.save_canny = save_canny
def clear_cache(self):
self.model_selection = None
self.control_image_path = None
self.control_strength = None
self.save_canny = False
class MfluxControlNetLoader:
@classmethod
def INPUT_TYPES(cls):
controlnet_models = [
"InstantX/FLUX.1-dev-Controlnet-Canny",
]
input_dir = folder_paths.get_input_directory()
files = [f for f in os.listdir(input_dir) if os.path.isfile(os.path.join(input_dir, f))]
return {
"required": {
"model_selection": (controlnet_models, {"default": "InstantX/FLUX.1-dev-Controlnet-Canny"}),
"image": (sorted(files), {"image_upload": True}),
"control_strength": ("FLOAT", {"default": 0.5, "min": 0.0, "max": 1.0, "step": 0.01}),
"save_canny": ("BOOLEAN", {"default": False, "label_on": "True", "label_off": "False"}),
}
}
CATEGORY = "MFlux"
RETURN_TYPES = ("MfluxControlNetPipeline", "INT", "INT", "IMAGE",)
RETURN_NAMES = ("ControlNet", "width", "height", "preprocessed_image")
FUNCTION = "load_and_select"
def load_and_select(self, model_selection, image, control_strength, save_canny):
control_image_path = folder_paths.get_annotated_filepath(image)
with Image.open(control_image_path) as img:
width, height = img.size
with Image.open(control_image_path) as img:
canny_image = ControlnetUtil.preprocess_canny(img)
canny_image_np = np.array(canny_image).astype(np.float32) / 255.0
canny_tensor = torch.from_numpy(canny_image_np)
if canny_tensor.dim() == 3:
canny_tensor = canny_tensor.unsqueeze(0)
return MfluxControlNetPipeline(model_selection, control_image_path, control_strength, save_canny), width, height, canny_tensor
@classmethod
def IS_CHANGED(cls, model_selection, image, control_strength, save_canny):
control_image_path = folder_paths.get_annotated_filepath(image)
control_strength = round(float(control_strength), 2)
control_image_hash = hash(image)
return str(control_image_hash) + str(model_selection) + str(control_strength) + str(save_canny)
@classmethod
def VALIDATE_INPUTS(cls, image, model_selection, control_strength, save_canny):
available_models = [
"InstantX/FLUX.1-dev-Controlnet-Canny",
]
if model_selection not in available_models:
return f"Invalid model selection: {model_selection}"
if not folder_paths.exists_annotated_filepath(image):
return "Invalid control image file: {}".format(image)
if not isinstance(control_strength, (int, float)):
return "Strength must be a number"
if not isinstance(save_canny, bool):
return "save_canny must be a boolean value"
return True
-187
View File
@@ -1,187 +0,0 @@
import os
import json
from pathlib import Path
from huggingface_hub import snapshot_download
from folder_paths import get_filename_list, get_output_directory, models_dir
from mflux.config.model_config import ModelConfig
from mflux.flux.flux import Flux1
from .Mflux_Core import get_lora_info, generate_image, save_images_with_metadata
def create_directory(directory):
if not os.path.exists(directory):
os.makedirs(directory)
return directory
mflux_dir = os.path.join(models_dir, "Mflux")
create_directory(mflux_dir)
def get_full_model_path(model_dir, model_name):
return os.path.join(model_dir, model_name)
def download_hg_model(model_version):
repo_id = f"madroid/{model_version}" if "4bit" in model_version else f"AITRADER/{model_version}"
model_checkpoint = get_full_model_path(mflux_dir, model_version)
if not os.path.exists(model_checkpoint):
print(f"Downloading model {model_version} to {model_checkpoint}...")
try:
snapshot_download(repo_id=repo_id, local_dir=model_checkpoint)
except Exception as e:
print(f"Error downloading model {model_version}: {e}")
return None
else:
print(f"Model {model_version} already exists at {model_checkpoint}. Skipping download.")
return model_checkpoint
class MfluxModelsDownloader:
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"model_version": (["flux.1-schnell-mflux-4bit", "flux.1-dev-mflux-4bit", "MFLUX.1-schnell-8-bit", "MFLUX.1-dev-8-bit"], {"default": "flux.1-schnell-mflux-4bit"}),
}
}
RETURN_TYPES = ("PATH",)
RETURN_NAMES = ("Downloaded_model",)
CATEGORY = "MFlux/Air"
FUNCTION = "download_model"
def download_model(self, model_version):
model_path = download_hg_model(model_version)
if model_path:
return (model_path,)
return (None,)
class MfluxCustomModels:
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"model": (["dev", "schnell"], {"default": "schnell"}),
"quantize": (["4", "8"], {"default": "4"}),
},
"optional": {
"Loras": ("MfluxLorasPipeline",),
"custom_identifier": ("STRING", {"default": "", "tooltip": "Custom identifier for the model."}),
}
}
RETURN_TYPES = ("PATH",)
RETURN_NAMES = ("Custom_model",)
CATEGORY = "MFlux/Air"
FUNCTION = "save_model"
def save_model(self, model, quantize, Loras=None, custom_identifier=""):
identifier = custom_identifier if custom_identifier else "default"
save_dir = get_full_model_path(mflux_dir, f"Mflux-{model}-{quantize}bit-{identifier}")
create_directory(save_dir)
print(f"Saving model: {model}, quantize: {quantize}, save_dir: {save_dir}")
lora_paths, lora_scales = get_lora_info(Loras)
if lora_paths:
print(f"LoRA paths: {lora_paths}")
print(f"LoRA scales: {lora_scales}")
model_config = ModelConfig.from_alias(model)
flux = Flux1(model_config=model_config, quantize=int(quantize), lora_paths=lora_paths, lora_scales=lora_scales)
flux.save_model(save_dir)
print(f"Model saved to {save_dir}")
return (save_dir,)
class MfluxModelsLoader:
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"model_name": (cls.get_sorted_model_paths() or ["None"], {"default": cls.get_sorted_model_paths()[0] if cls.get_sorted_model_paths() else "None"}),
},
"optional": {
"free_path": ("STRING", {"default": "", "tooltip": "Free model path. If provided, this will override the model_name."}),
}
}
RETURN_TYPES = ("PATH",)
RETURN_NAMES = ("Local_model",)
CATEGORY = "MFlux/Air"
FUNCTION = "load"
@classmethod
def get_sorted_model_paths(cls):
model_paths = [p.name for p in Path(mflux_dir).iterdir() if p.is_dir()]
return sorted(model_paths, key=lambda x: x.lower())
def load(self, model_name="", free_path=""):
if free_path:
full_model_path = free_path
if not os.path.exists(full_model_path):
raise ValueError(f"Provided custom path does not exist: {full_model_path}")
elif model_name and model_name != "None":
full_model_path = get_full_model_path(mflux_dir, model_name)
else:
raise ValueError("Either 'model_name' must be provided or 'free_path' must be used.")
return (full_model_path,)
class QuickMfluxNode:
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"prompt": ("STRING", {"multiline": True, "dynamicPrompts": True, "tooltip": "The text to be encoded.", "default": "Luxury food photograph"}),
"model": (["dev", "schnell"], {"default": "schnell"}),
"quantize": (["None", "4", "8"], {"default": "4"}),
"seed": ("INT", {"default": -1, "min": -1, "max": 0xffffffffffffffff}),
"width": ("INT", {"default": 512}),
"height": ("INT", {"default": 512}),
"steps": ("INT", {"default": 2, "min": 1}),
"guidance": ("FLOAT", {"default": 3.5, "min": 0.0}),
"metadata": ("BOOLEAN", {"default": True, "label_on": "True", "label_off": "False"}),
},
"optional": {
"Local_model": ("PATH",),
"Loras": ("MfluxLorasPipeline",),
"img2img": ("MfluxImg2ImgPipeline",),
"ControlNet": ("MfluxControlNetPipeline",),
},
"hidden": {
"full_prompt": "PROMPT",
"extra_pnginfo": "EXTRA_PNGINFO"
}
}
RETURN_TYPES = ("IMAGE",)
CATEGORY = "MFlux/Air"
FUNCTION = "generate"
def generate(self, prompt, model, seed, width, height, steps, guidance, quantize="None", metadata=True, Local_model="", img2img=None, Loras=None, ControlNet=None, full_prompt=None, extra_pnginfo=None):
generated_images = generate_image(
prompt, model, seed, width, height, steps, guidance, quantize, metadata, Local_model, img2img, Loras, ControlNet
)
image_path = img2img.image_path if img2img else None
image_strength = img2img.image_strength if img2img else None
lora_paths, lora_scales = get_lora_info(Loras)
if metadata:
save_images_params = {
"images": generated_images,
"prompt": prompt,
"model": "dev" if "dev" in Local_model.lower() else "schnell" if "schnell" in Local_model.lower() else model,
"quantize": quantize,
"Local_model": Local_model,
"seed": seed,
"height": height,
"width": width,
"steps": steps,
"guidance": guidance,
"image_path": image_path,
"image_strength": image_strength,
"lora_paths": lora_paths,
"lora_scales": lora_scales,
"filename_prefix": "Mflux",
"full_prompt": full_prompt,
"extra_pnginfo": extra_pnginfo,
}
result = save_images_with_metadata(**save_images_params)
counter = result["counter"]
return generated_images
-225
View File
@@ -1,225 +0,0 @@
import random
import json
import os
import numpy as np
import torch
from tqdm import tqdm
from PIL import Image
from PIL.PngImagePlugin import PngInfo
import comfy.utils as utils
import folder_paths
import mlx.core as mx
from mflux.config.model_config import ModelConfig
from mflux.config.config import Config, ConfigControlnet
from mflux.flux.flux import Flux1
from mflux.controlnet.flux_controlnet import Flux1Controlnet
from mflux.config.runtime_config import RuntimeConfig
from mflux.post_processing.array_util import ArrayUtil
from mflux.post_processing.image_util import ImageUtil
from mflux.post_processing.generated_image import GeneratedImage
from mflux.latent_creator.latent_creator import LatentCreator
from mflux.post_processing.stepwise_handler import StepwiseHandler
from mflux.controlnet.controlnet_util import ControlnetUtil
from mflux.models.vae.vae import VAE
from .Mflux_Pro import MfluxControlNetPipeline
flux_cache = {}
def load_or_create_flux(model, quantize, path, lora_paths, lora_scales, is_controlnet=False):
key = (model, quantize, path, tuple(lora_paths), tuple(lora_scales), is_controlnet)
if key not in flux_cache:
flux_cache.clear()
FluxClass = Flux1Controlnet if is_controlnet else Flux1
flux_cache[key] = FluxClass(
model_config=ModelConfig.from_alias(model),
quantize=quantize,
local_path=path,
lora_paths=lora_paths,
lora_scales=lora_scales,
)
# This code is licensed under the Apache 2.0 License.
# Portions of this code are derived from the work of CharafChnioune at https://github.com/CharafChnioune/MFLUX-WEBUI
return flux_cache[key]
def get_lora_info(Loras):
if Loras:
return Loras.lora_paths, Loras.lora_scales
return [], []
def generate_image(prompt, model, seed, width, height, steps, guidance, quantize="None", metadata=True, Local_model="", image=None, Loras=None, ControlNet=None):
model = "dev" if "dev" in Local_model.lower() else "schnell" if "schnell" in Local_model.lower() else model
print(f"Using model: {model}")
image_path = image.image_path if image else None
image_strength = image.image_strength if image else None
lora_paths, lora_scales = get_lora_info(Loras)
if Loras:
print(f"LoRA paths: {lora_paths}")
print(f"LoRA scales: {lora_scales}")
ControlNet_model_selection, control_image_path, controlnet_strength, save_canny = None, None, None, None
if ControlNet and isinstance(ControlNet, MfluxControlNetPipeline):
ControlNet_model_selection = ControlNet.model_selection
save_canny = ControlNet.save_canny
control_strength = ControlNet.control_strength
control_image_path = ControlNet.control_image_path
quantize = None if quantize == "None" else int(quantize)
seed = random.randint(0, 0xffffffffffffffff) if seed == -1 else int(seed)
print(f"Using seed: {seed}")
# steps = min(max(steps, 2), 4) if model == "schnell" else steps
flux = load_or_create_flux(model, quantize, Local_model if Local_model else None, lora_paths, lora_scales, ControlNet is not None)
config_class = ConfigControlnet if ControlNet else Config
config = RuntimeConfig(
config=config_class(
num_inference_steps=steps,
height=height,
width=width,
guidance=guidance,
**({"controlnet_strength": control_strength} if ControlNet else {
"init_image_path": image_path,
"init_image_strength": image_strength
})
),
model_config=flux.model_config
)
time_steps = tqdm(range(config.init_time_step if not ControlNet else 0, config.num_inference_steps))
stepwise_handler = StepwiseHandler(
flux=flux,
config=config,
seed=seed,
prompt=prompt,
time_steps=time_steps,
output_dir=None
)
t5_tokens = flux.t5_tokenizer.tokenize(prompt)
clip_tokens = flux.clip_tokenizer.tokenize(prompt)
prompt_embeds = flux.t5_text_encoder.forward(t5_tokens)
pooled_prompt_embeds = flux.clip_text_encoder.forward(clip_tokens)
if ControlNet:
control_image = ImageUtil.load_image(control_image_path)
control_image = ControlnetUtil.scale_image(config.height, config.width, control_image)
control_image = ControlnetUtil.preprocess_canny(control_image)
controlnet_cond = ImageUtil.to_array(control_image)
controlnet_cond = flux.vae.encode(controlnet_cond)
controlnet_cond = (controlnet_cond / flux.vae.scaling_factor) + flux.vae.shift_factor
controlnet_cond = ArrayUtil.pack_latents(latents=controlnet_cond, height=config.height, width=config.width)
latents = LatentCreator.create(seed=seed, height=config.height, width=config.width)
else:
latents = LatentCreator.create_for_txt2img_or_img2img(seed, config, flux.vae)
pbar = None
for gen_step, t in enumerate(time_steps, 1):
if gen_step == 2:
pbar = utils.ProgressBar(total=steps)
predict_args = {
't': t,
'prompt_embeds': prompt_embeds,
'pooled_prompt_embeds': pooled_prompt_embeds,
'hidden_states': latents,
'config': config,
}
if ControlNet:
controlnet_block_samples, controlnet_single_block_samples = flux.transformer_controlnet.forward(
t=t,
prompt_embeds=prompt_embeds,
pooled_prompt_embeds=pooled_prompt_embeds,
hidden_states=latents,
controlnet_cond=controlnet_cond,
config=config,
)
predict_args['controlnet_block_samples'] = controlnet_block_samples
predict_args['controlnet_single_block_samples'] = controlnet_single_block_samples
noise = flux.transformer.predict(**predict_args)
dt = config.sigmas[t + 1] - config.sigmas[t]
latents += noise * dt
stepwise_handler.process_step(gen_step, latents)
if pbar:
pbar.update(1)
mx.eval(latents)
if pbar:
pbar.update(1)
latents = ArrayUtil.unpack_latents(latents=latents, height=config.height, width=config.width)
decoded = flux.vae.decode(latents)
normalized_latents = ImageUtil._denormalize(decoded)
numpy_image = ImageUtil._to_numpy(normalized_latents)
tensor_image = torch.from_numpy(numpy_image)
if tensor_image.dim() == 3:
tensor_image = tensor_image.unsqueeze(0)
return (tensor_image,)
def save_images_with_metadata(images, prompt, model, quantize, Local_model, seed, height, width, steps, guidance, lora_paths, lora_scales, image_path, image_strength, filename_prefix="Mflux", full_prompt=None, extra_pnginfo=None):
output_dir = folder_paths.get_output_directory()
full_output_folder, filename, counter, subfolder, filename_prefix = folder_paths.get_save_image_path(
filename_prefix, output_dir, images[0].shape[1], images[0].shape[0])
mflux_output_folder = os.path.join(full_output_folder, "MFlux")
os.makedirs(mflux_output_folder, exist_ok=True)
existing_files = os.listdir(mflux_output_folder)
existing_counters = [
int(f.split("_")[-1].split(".")[0])
for f in existing_files
if f.startswith(filename_prefix) and f.endswith(".png")
]
counter = max(existing_counters, default=0) + 1
results = list()
for image in images:
i = 255. * image.cpu().numpy().squeeze()
img = Image.fromarray(np.clip(i, 0, 255).astype(np.uint8))
metadata = None
if full_prompt is not None or extra_pnginfo is not None:
metadata = PngInfo()
if full_prompt is not None:
metadata.add_text("full_prompt", json.dumps(full_prompt))
if extra_pnginfo is not None:
for x in extra_pnginfo:
metadata.add_text(x, json.dumps(extra_pnginfo[x]))
image_file = f"{filename_prefix}_{counter:05}.png"
img.save(os.path.join(mflux_output_folder, image_file), pnginfo=metadata, compress_level=4)
results.append({
"filename": image_file,
"subfolder": subfolder,
"type": "output"
})
metadata_jsonfile = os.path.join(mflux_output_folder, f"{filename_prefix}_{counter:05}.json")
json_dict = {
"prompt": prompt,
"model": model,
"quantize": quantize,
"seed": seed,
"height": height,
"width": width,
"steps": steps,
"guidance": guidance if model == "dev" else None,
"Local_model": Local_model,
"init_image_path": image_path,
"init_image_strength": image_strength,
"lora_paths": lora_paths,
"lora_scales": lora_scales,
}
with open(metadata_jsonfile, 'w') as metadata_file:
json.dump(json_dict, metadata_file, indent=4)
counter += 1
return {"ui": {"images": results}, "counter": counter}
-199
View File
@@ -1,199 +0,0 @@
import os
from PIL import Image
import folder_paths
import numpy as np
import torch
from mflux.controlnet.controlnet_util import ControlnetUtil
class MfluxImg2ImgPipeline:
def __init__(self, image_path, image_strength):
self.image_path = image_path
self.image_strength = image_strength
def clear_cache(self):
self.image_path = None
self.image_strength = None
class MfluxImg2Img:
@classmethod
def INPUT_TYPES(cls):
input_dir = folder_paths.get_input_directory()
files = [f for f in os.listdir(input_dir) if os.path.isfile(os.path.join(input_dir, f))]
return {
"required": {
"image": (sorted(files), {"image_upload": True}),
"image_strength": ("FLOAT", {"default": 0.4, "min": 0.0, "max": 1.0, "step": 0.01}),
}
}
CATEGORY = "MFlux/Pro"
RETURN_TYPES = ("MfluxImg2ImgPipeline", "INT", "INT")
RETURN_NAMES = ("img2img", "width", "height")
FUNCTION = "load_and_process"
def load_and_process(self, image, image_strength):
image_path = folder_paths.get_annotated_filepath(image)
with Image.open(image_path) as img:
width, height = img.size
return MfluxImg2ImgPipeline(image_path, image_strength), width, height
@classmethod
def IS_CHANGED(cls, image, image_strength):
image_hash = hash(image)
strength_rounded = round(float(image_strength), 2)
return (image_hash, strength_rounded)
@classmethod
def VALIDATE_INPUTS(cls, image, image_strength):
if not folder_paths.exists_annotated_filepath(image):
return "Invalid image file: {}".format(image)
if not isinstance(image_strength, (int, float)):
return "Strength must be a number"
return True
class MfluxLorasPipeline:
def __init__(self, lora_paths, lora_scales):
self.lora_paths = lora_paths
self.lora_scales = lora_scales
def clear_cache(self):
self.lora_paths = []
self.lora_scales = []
class MfluxLorasLoader:
@classmethod
def INPUT_TYPES(cls):
lora_base_path = folder_paths.models_dir
loras_relative = ["None"] + folder_paths.get_filename_list("loras")
inputs = {
"required": {
"Lora1": (loras_relative,),
"scale1": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}),
"Lora2": (loras_relative,),
"scale2": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}),
"Lora3": (loras_relative,),
"scale3": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}),
},
"optional": {
"Loras": ("MfluxLorasPipeline",)
}
}
return inputs
RETURN_TYPES = ("MfluxLorasPipeline",)
RETURN_NAMES = ("Loras",)
FUNCTION = "lora_stacker"
CATEGORY = "MFlux/Pro"
def lora_stacker(self, Loras=None, **kwargs):
lora_base_path = folder_paths.models_dir
lora_models = [
(os.path.join(lora_base_path, "loras", kwargs.get(f"Lora{i}")), kwargs.get(f"scale{i}"))
for i in range(1, 4) if kwargs.get(f"Lora{i}") != "None"
]
if Loras is not None and isinstance(Loras, MfluxLorasPipeline):
lora_paths = Loras.lora_paths
lora_scales = Loras.lora_scales
if lora_paths and lora_scales:
lora_models.extend(zip(lora_paths, lora_scales))
if lora_models:
lora_paths, lora_scales = zip(*lora_models)
else:
lora_paths, lora_scales = [], []
return (MfluxLorasPipeline(list(lora_paths), list(lora_scales)),)
class MfluxControlNetPipeline:
def __init__(self, model_selection, control_image_path, control_strength, save_canny=False):
self.model_selection = model_selection
self.control_image_path = control_image_path
self.control_strength = control_strength
self.save_canny = save_canny
def clear_cache(self):
self.model_selection = None
self.control_image_path = None
self.control_strength = None
self.save_canny = False
class MfluxControlNetLoader:
@classmethod
def INPUT_TYPES(cls):
controlnet_models = [
"InstantX/FLUX.1-dev-Controlnet-Canny",
]
input_dir = folder_paths.get_input_directory()
files = [f for f in os.listdir(input_dir) if os.path.isfile(os.path.join(input_dir, f))]
return {
"required": {
"model_selection": (controlnet_models, {"default": "InstantX/FLUX.1-dev-Controlnet-Canny"}),
"image": (sorted(files), {"image_upload": True}),
"control_strength": ("FLOAT", {"default": 0.5, "min": 0.0, "max": 1.0, "step": 0.01}),
"save_canny": ("BOOLEAN", {"default": False, "label_on": "True", "label_off": "False"}),
}
}
CATEGORY = "MFlux/Pro"
RETURN_TYPES = ("MfluxControlNetPipeline", "INT", "INT", "IMAGE",)
RETURN_NAMES = ("ControlNet", "width", "height", "preprocessed_image")
FUNCTION = "load_and_select"
def load_and_select(self, model_selection, image, control_strength, save_canny):
control_image_path = folder_paths.get_annotated_filepath(image)
with Image.open(control_image_path) as img:
width, height = img.size
with Image.open(control_image_path) as img:
canny_image = ControlnetUtil.preprocess_canny(img)
canny_image_np = np.array(canny_image).astype(np.float32) / 255.0
canny_tensor = torch.from_numpy(canny_image_np)
if canny_tensor.dim() == 3:
canny_tensor = canny_tensor.unsqueeze(0)
return MfluxControlNetPipeline(model_selection, control_image_path, control_strength, save_canny), width, height, canny_tensor
@classmethod
def IS_CHANGED(cls, model_selection, image, control_strength, save_canny):
control_image_path = folder_paths.get_annotated_filepath(image)
control_strength = round(float(control_strength), 2)
control_image_hash = hash(image)
return str(control_image_hash) + str(model_selection) + str(control_strength) + str(save_canny)
@classmethod
def VALIDATE_INPUTS(cls, image, model_selection, control_strength, save_canny):
available_models = [
"InstantX/FLUX.1-dev-Controlnet-Canny",
]
if model_selection not in available_models:
return f"Invalid model selection: {model_selection}"
if not folder_paths.exists_annotated_filepath(image):
return "Invalid control image file: {}".format(image)
if not isinstance(control_strength, (int, float)):
return "Strength must be a number"
if not isinstance(save_canny, bool):
return "save_canny must be a boolean value"
return True
View File
+10 -41
View File
@@ -7,7 +7,7 @@
# Quick Mflux on Comfyui
## Introduction
Simple use of Mflux in ComfyUI, suitable for users who are not familiar with terminal usage. Only for MacOS.
Simple use of Mflux in ComfyUI, suitable for users who are not familiar with terminal usage. Only for MacOS. This is a simplified branch of the original MFlux ComfyUI plugin which is maintained by InformEthics.
## Acknowledgements
@@ -22,17 +22,17 @@ Thanks also to **@CharafChnioune**, the author of [**MFLUX-WEBUI**](https://gith
1. `cd /path/to/your_ComfyUI`
2. Activate the virtual environment
3. `cd custom_nodes`
4. `git clone https://github.com/raysers/Mflux-ComfyUI.git`
5. `pip install mflux==0.4.1`
4. `git clone https://github.com/raysers/Mflux-ComfyUI/ComfyMFlux.git`
5. `pip install mflux==0.5.1`
6. Restart ComfyUI
Alternatively, you can search for "Mflux-ComfyUI" in ComfyUI-Manager for a quick installation.
## Update Announcement
### **About This Update:**
- The ControlNet node integrates images and intensity to distinguish it from img2img — contribution from @InformEthics.
(contributions from @InformEthics)
- The Nodes are all in one directory instead of many (Air / Pro etc..)
- The whole node is unified in one python file ComfyMFlux.py for easier overview and programming
- The ControlNet node integrates images and intensity to distinguish it from img2img
This is the first time other contributors have participated in updating this plugin. Thanks @InformEthics.
@@ -56,7 +56,7 @@ If you'd like to experience the original mflux, this provides an additional opti
- Customizable file paths.
- Image-to-Image generation.
mflux has been updated to version 0.4.1. To experience image-to-image generation, please upgrade in ComfyUI with:
mflux has been updated to version 0.5.1. Please upgrade in ComfyUI with:
`pip install --upgrade mflux`
@@ -64,15 +64,12 @@ mflux has been updated to version 0.4.1. To experience image-to-image generation
Right-click to create nodes:
Under **MFlux/Air**:
Under **MFlux/**:
- **Quick MFlux Generation**
- **MFlux Models Loader**
- **MFlux Models Downloader**
- **MFlux Custom Models**
Under **MFlux/Pro**:
- **Mflux Img2Img**
- **MFlux Loras Loader**
- **MFlux ControlNet Loader**
@@ -116,7 +113,7 @@ Additionally, here is the FLUX.1-dev-Controlnet-Canny model from the InstantX te
## Workflow
### **Mflux Air:**
### **Mflux:**
#### text2img:
@@ -163,9 +160,6 @@ This update also adds the option to manually input paths. Alternatively, you can
I am still exploring the specific usage methods. If you have experiences worth sharing, please feel free to start a discussion in the issues.
### **Mflux Pro:**
#### Loras:
![Loras](examples/Pro_Loras.png)
@@ -197,31 +191,6 @@ The advantage of this 4-step LoRA model is that it still belongs to the DEV mode
### **Mflux Plus:**
![Translate + Mflux](examples/Plus1.png)
A must-have for English beginners.
![Florence2 + Mflux](examples/Plus2.png)
Image reverse generation, using the MiaoshouAI/Florence-2-large-PromptGen-v1.5 visual model here.
All these processes can be dragged directly into ComfyUI from the workflows folder.
!!! If nodes are highlighted in red, use ComfyUI-Manager's "One-click Install Missing Nodes."
!!! Please note that all processes at the end use preview nodes, which do not automatically save. You need to manually save the generated images you are satisfied with or simply replace the preview nodes with save nodes.
### **Possible Explorations**
#### **Mflux MAX:**
......
#### **Mflux Ultra:**
......
Here, I hope everyone shares their workflows more, fully promoting the spirit of sharing on the internet. Knowledge for payment? No, I advocate cooperation and sharing for mutual benefit.
+4 -5
View File
@@ -1,13 +1,12 @@
from .Mflux_Comfy.Mflux_Air import QuickMfluxNode, MfluxModelsLoader, MfluxModelsDownloader, MfluxCustomModels
from .Mflux_Comfy.Mflux_Pro import MfluxImg2Img, MfluxLorasLoader, MfluxControlNetLoader # 仅导入所需的非实验节点
from .ComfyMflux import QuickMfluxNode, MfluxModelsLoader, MfluxModelsDownloader, MfluxCustomModels, MfluxImg2Img, MfluxLorasLoader, MfluxControlNetLoader
NODE_CLASS_MAPPINGS = {
"QuickMfluxNode": QuickMfluxNode,
"MfluxModelsLoader": MfluxModelsLoader,
"MfluxModelsDownloader": MfluxModelsDownloader,
"MfluxCustomModels": MfluxCustomModels,
"MfluxImg2Img": MfluxImg2Img,
"MfluxLorasLoader": MfluxLorasLoader,
"MfluxImg2Img": MfluxImg2Img,
"MfluxControlNetLoader": MfluxControlNetLoader,
}
@@ -16,7 +15,7 @@ NODE_DISPLAY_NAME_MAPPINGS = {
"MfluxModelsLoader": "MFlux Models Loader",
"MfluxModelsDownloader": "MFlux Models Downloader",
"MfluxCustomModels": "MFlux Custom Models",
"MfluxImg2Img": "Mflux Img2Img",
"MfluxLorasLoader": "MFlux Loras Loader",
"MfluxImg2Img": "MFlux Image2Image",
"MfluxControlNetLoader": "MFlux ControlNet Loader",
}
}
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 234 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 MiB

+1 -1
View File
@@ -1,7 +1,7 @@
[project]
name = "mflux-comfyui"
description = "Simple use of [a/Mflux](https://github.com/filipstrand/mflux) in ComfyUI, suitable for users who are not familiar with terminal usage.\nNOTE: A MLX port of FLUX based on the Huggingface Diffusers implementation."
version = "1.2.5"
version = "1.2.4"
license = {file = "LICENSE"}
dependencies = ["mflux==0.4.1"]
+1 -1
View File
@@ -1 +1 @@
mflux==0.4.1
mflux==0.5.1
-309
View File
@@ -1,309 +0,0 @@
{
"last_node_id": 5,
"last_link_id": 4,
"nodes": [
{
"id": 3,
"type": "LoadImage",
"pos": {
"0": 22,
"1": 53
},
"size": {
"0": 315,
"1": 314
},
"flags": {},
"order": 0,
"mode": 0,
"inputs": [],
"outputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"links": [
2
],
"shape": 3,
"label": "图像"
},
{
"name": "MASK",
"type": "MASK",
"links": null,
"shape": 3,
"label": "遮罩"
}
],
"properties": {
"Node name for S&R": "LoadImage"
},
"widgets_values": [
"IMG_1281.jpeg",
"image"
]
},
{
"id": 1,
"type": "DownloadAndLoadFlorence2Model",
"pos": {
"0": 25,
"1": 422
},
"size": {
"0": 315,
"1": 106
},
"flags": {},
"order": 1,
"mode": 0,
"inputs": [
{
"name": "lora",
"type": "PEFTLORA",
"link": null,
"label": "LoRA"
}
],
"outputs": [
{
"name": "florence2_model",
"type": "FL2MODEL",
"links": [
1
],
"slot_index": 0,
"shape": 3,
"label": "Florence2"
}
],
"properties": {
"Node name for S&R": "DownloadAndLoadFlorence2Model"
},
"widgets_values": [
"MiaoshouAI/Florence-2-large-PromptGen-v1.5",
"fp16",
"sdpa"
]
},
{
"id": 2,
"type": "Florence2Run",
"pos": {
"0": 365,
"1": 58
},
"size": {
"0": 400,
"1": 352
},
"flags": {},
"order": 2,
"mode": 0,
"inputs": [
{
"name": "image",
"type": "IMAGE",
"link": 2,
"label": "图像"
},
{
"name": "florence2_model",
"type": "FL2MODEL",
"link": 1,
"label": "Florence2"
}
],
"outputs": [
{
"name": "image",
"type": "IMAGE",
"links": null,
"shape": 3,
"label": "图像"
},
{
"name": "mask",
"type": "MASK",
"links": null,
"shape": 3,
"label": "遮罩"
},
{
"name": "caption",
"type": "STRING",
"links": [
3
],
"slot_index": 2,
"shape": 3,
"label": "caption"
},
{
"name": "data",
"type": "JSON",
"links": null,
"shape": 3,
"label": "json数据"
}
],
"properties": {
"Node name for S&R": "Florence2Run"
},
"widgets_values": [
"",
"more_detailed_caption",
true,
false,
1024,
3,
true,
"",
945771744623101,
"randomize",
true
]
},
{
"id": 4,
"type": "QuickMfluxNode",
"pos": {
"0": 791,
"1": 60
},
"size": {
"0": 400,
"1": 336
},
"flags": {},
"order": 3,
"mode": 0,
"inputs": [
{
"name": "lora_files",
"type": "LIST",
"link": null,
"label": "lora_files"
},
{
"name": "ControlNet",
"type": "MfluxControlNetPipeline",
"link": null,
"label": "ControlNet"
},
{
"name": "prompt",
"type": "STRING",
"link": 3,
"widget": {
"name": "prompt"
},
"label": "prompt"
}
],
"outputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"links": [
4
],
"slot_index": 0,
"shape": 3,
"label": "IMAGE"
}
],
"properties": {
"Node name for S&R": "QuickMfluxNode"
},
"widgets_values": [
"Luxury food photograph",
"schnell",
"flux.1-schnell-mflux-4bit",
682107544996648,
"randomize",
512,
512,
2,
3.5,
"false",
"",
true
]
},
{
"id": 5,
"type": "PreviewImage",
"pos": {
"0": 1220,
"1": 61
},
"size": [
210,
246
],
"flags": {},
"order": 4,
"mode": 0,
"inputs": [
{
"name": "images",
"type": "IMAGE",
"link": 4,
"label": "图像"
}
],
"outputs": [],
"properties": {
"Node name for S&R": "PreviewImage"
},
"widgets_values": []
}
],
"links": [
[
1,
1,
0,
2,
1,
"FL2MODEL"
],
[
2,
3,
0,
2,
0,
"IMAGE"
],
[
3,
2,
2,
4,
2,
"STRING"
],
[
4,
4,
0,
5,
0,
"IMAGE"
]
],
"groups": [],
"config": {},
"extra": {
"ds": {
"scale": 1.1167815779424797,
"offset": [
14.59059871397305,
25.485045382690416
]
}
},
"version": 0.4
}
-367
View File
@@ -1,367 +0,0 @@
{
"last_node_id": 20,
"last_link_id": 46,
"nodes": [
{
"id": 10,
"type": "MfluxModelsLoader",
"pos": {
"0": 444,
"1": -8
},
"size": {
"0": 315,
"1": 58
},
"flags": {},
"order": 0,
"mode": 0,
"inputs": [],
"outputs": [
{
"name": "Local_model",
"type": "PATH",
"links": [
17
],
"label": "Local_model"
}
],
"properties": {
"Node name for S&R": "MfluxModelsLoader"
},
"widgets_values": [
"Mflux-dev-4bit-LoRA4step"
]
},
{
"id": 17,
"type": "MfluxControlNetLoader",
"pos": {
"0": 459,
"1": 239
},
"size": {
"0": 315,
"1": 102
},
"flags": {},
"order": 2,
"mode": 0,
"inputs": [
{
"name": "image",
"type": "MfluxImagePipeline",
"link": 44,
"shape": 7,
"label": "image"
}
],
"outputs": [
{
"name": "ControlNet",
"type": "MfluxControlNetPipeline",
"links": [
36
],
"slot_index": 0,
"label": "ControlNet"
},
{
"name": "preprocessed_image",
"type": "IMAGE",
"links": [
38
],
"slot_index": 1,
"label": "preprocessed_image"
}
],
"properties": {
"Node name for S&R": "MfluxControlNetLoader"
},
"widgets_values": [
"InstantX/FLUX.1-dev-Controlnet-Canny",
false
]
},
{
"id": 18,
"type": "PreviewImage",
"pos": {
"0": 465,
"1": 413
},
"size": {
"0": 210,
"1": 246
},
"flags": {},
"order": 4,
"mode": 0,
"inputs": [
{
"name": "images",
"type": "IMAGE",
"link": 38,
"label": "图像"
}
],
"outputs": [],
"properties": {
"Node name for S&R": "PreviewImage"
},
"widgets_values": []
},
{
"id": 11,
"type": "PreviewImage",
"pos": {
"0": 1298,
"1": 47
},
"size": {
"0": 210,
"1": 246
},
"flags": {},
"order": 5,
"mode": 0,
"inputs": [
{
"name": "images",
"type": "IMAGE",
"link": 19,
"label": "图像"
}
],
"outputs": [],
"properties": {
"Node name for S&R": "PreviewImage"
},
"widgets_values": []
},
{
"id": 9,
"type": "QuickMfluxNode",
"pos": {
"0": 844,
"1": 58
},
"size": {
"0": 400,
"1": 377
},
"flags": {},
"order": 3,
"mode": 0,
"inputs": [
{
"name": "Local_model",
"type": "PATH",
"link": 17,
"shape": 7,
"label": "Local_model"
},
{
"name": "image",
"type": "MfluxImagePipeline",
"link": 43,
"shape": 7,
"label": "image"
},
{
"name": "Loras",
"type": "MfluxLorasPipeline",
"link": null,
"shape": 7,
"label": "Loras"
},
{
"name": "ControlNet",
"type": "MfluxControlNetPipeline",
"link": 36,
"shape": 7,
"label": "ControlNet"
},
{
"name": "width",
"type": "INT",
"link": 45,
"widget": {
"name": "width"
},
"label": "width"
},
{
"name": "height",
"type": "INT",
"link": 46,
"widget": {
"name": "height"
},
"label": "height"
}
],
"outputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"links": [
19
],
"slot_index": 0,
"label": "IMAGE"
}
],
"properties": {
"Node name for S&R": "QuickMfluxNode"
},
"widgets_values": [
"1girl, ",
"schnell",
"4",
801448522607649,
"randomize",
512,
512,
4,
3.5,
true,
true
]
},
{
"id": 20,
"type": "MfluxLodeImage",
"pos": {
"0": 44,
"1": 67
},
"size": [
315,
358
],
"flags": {},
"order": 1,
"mode": 0,
"inputs": [],
"outputs": [
{
"name": "image",
"type": "MfluxImagePipeline",
"links": [
43,
44
],
"label": "image",
"slot_index": 0
},
{
"name": "width",
"type": "INT",
"links": [
45
],
"label": "width",
"slot_index": 1
},
{
"name": "height",
"type": "INT",
"links": [
46
],
"label": "height",
"slot_index": 2
}
],
"properties": {
"Node name for S&R": "MfluxLodeImage"
},
"widgets_values": [
"图像.jpeg",
0.4,
"image"
]
}
],
"links": [
[
17,
10,
0,
9,
0,
"PATH"
],
[
19,
9,
0,
11,
0,
"IMAGE"
],
[
36,
17,
0,
9,
3,
"MfluxControlNetPipeline"
],
[
38,
17,
1,
18,
0,
"IMAGE"
],
[
43,
20,
0,
9,
1,
"MfluxImagePipeline"
],
[
44,
20,
0,
17,
0,
"MfluxImagePipeline"
],
[
45,
20,
1,
9,
4,
"INT"
],
[
46,
20,
2,
9,
5,
"INT"
]
],
"groups": [],
"config": {},
"extra": {
"ds": {
"scale": 1.0152559799477054,
"offset": [
65.60323847105522,
65.08020389873201
]
}
},
"version": 0.4
}
-174
View File
@@ -1,174 +0,0 @@
{
"last_node_id": 33,
"last_link_id": 13,
"nodes": [
{
"id": 32,
"type": "MfluxLorasLoader",
"pos": {
"0": 33,
"1": 59
},
"size": {
"0": 315,
"1": 178
},
"flags": {},
"order": 0,
"mode": 0,
"inputs": [
{
"name": "Loras",
"type": "MfluxLorasPipeline",
"link": null,
"shape": 7,
"label": "Loras"
}
],
"outputs": [
{
"name": "Loras",
"type": "MfluxLorasPipeline",
"links": [
12
],
"label": "Loras"
}
],
"properties": {
"Node name for S&R": "MfluxLorasLoader"
},
"widgets_values": [
"Mflux/aidmaImageUpgrader-FLUX-V0.2.safetensors",
0.8,
"Mflux/Flux_1_Dev_LoRA_Paper-Cutout-Style.safetensors",
1,
"None",
1
]
},
{
"id": 25,
"type": "QuickMfluxNode",
"pos": {
"0": 378,
"1": 60
},
"size": {
"0": 400,
"1": 356
},
"flags": {},
"order": 1,
"mode": 0,
"inputs": [
{
"name": "Local_models",
"type": "PATH",
"link": null,
"shape": 7,
"label": "Local_models"
},
{
"name": "Loras",
"type": "MfluxLorasPipeline",
"link": 12,
"shape": 7,
"label": "Loras"
},
{
"name": "ControlNet",
"type": "MfluxControlNetPipeline",
"link": null,
"shape": 7,
"label": "ControlNet"
}
],
"outputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"links": [
6
],
"slot_index": 0,
"label": "IMAGE"
}
],
"properties": {
"Node name for S&R": "QuickMfluxNode"
},
"widgets_values": [
"There, at the very end of the earth In an unprecedented blue distance Hearing the sounds of unprecedented words Sweetly and sweetly the blood stops There the winds fly touching the stars Where trees are not afraid of thunderstorms There ships are roaming the ocean There, at the very end of the earth, glitter, hyper detailed, ornate, professional photo, photorealism, vivid color, chiaroscuro",
"schnell",
"4",
1115004813920489,
"randomize",
1216,
832,
4,
3.5,
"true",
""
]
},
{
"id": 23,
"type": "PreviewImage",
"pos": {
"0": 819,
"1": 63
},
"size": {
"0": 210,
"1": 246
},
"flags": {},
"order": 2,
"mode": 0,
"inputs": [
{
"name": "images",
"type": "IMAGE",
"link": 6,
"label": "图像"
}
],
"outputs": [],
"properties": {
"Node name for S&R": "PreviewImage"
},
"widgets_values": []
}
],
"links": [
[
6,
25,
0,
23,
0,
"IMAGE"
],
[
12,
32,
0,
25,
1,
"MfluxLorasPipeline"
]
],
"groups": [],
"config": {},
"extra": {
"ds": {
"scale": 1.116781577942483,
"offset": [
48.91843793267095,
51.73609488467785
]
}
},
"version": 0.4
}
-223
View File
@@ -1,223 +0,0 @@
{
"last_node_id": 7,
"last_link_id": 6,
"nodes": [
{
"id": 3,
"type": "MfluxModelsLoader",
"pos": {
"0": 29,
"1": 57
},
"size": {
"0": 315,
"1": 58
},
"flags": {},
"order": 0,
"mode": 0,
"inputs": [],
"outputs": [
{
"name": "Local_model",
"type": "PATH",
"links": [
2
],
"label": "Local_model"
}
],
"properties": {
"Node name for S&R": "MfluxModelsLoader"
},
"widgets_values": [
"flux.1-dev-mflux-4bit"
]
},
{
"id": 1,
"type": "QuickMfluxNode",
"pos": {
"0": 376,
"1": 58
},
"size": {
"0": 400,
"1": 357
},
"flags": {},
"order": 2,
"mode": 0,
"inputs": [
{
"name": "Local_model",
"type": "PATH",
"link": 2,
"shape": 7,
"label": "Local_model"
},
{
"name": "image",
"type": "MfluxImagePipeline",
"link": 6,
"shape": 7,
"label": "image"
},
{
"name": "Loras",
"type": "MfluxLorasPipeline",
"link": null,
"shape": 7,
"label": "Loras"
},
{
"name": "ControlNet",
"type": "MfluxControlNetPipeline",
"link": null,
"shape": 7,
"label": "ControlNet"
}
],
"outputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"links": [
3
],
"slot_index": 0,
"label": "IMAGE"
}
],
"properties": {
"Node name for S&R": "QuickMfluxNode"
},
"widgets_values": [
"Luxury food photograph",
"schnell",
"4",
1125899906842624,
"fixed",
512,
512,
30,
3.5,
true,
true
]
},
{
"id": 4,
"type": "PreviewImage",
"pos": {
"0": 810,
"1": 60
},
"size": {
"0": 210,
"1": 246
},
"flags": {},
"order": 3,
"mode": 0,
"inputs": [
{
"name": "images",
"type": "IMAGE",
"link": 3,
"label": "图像"
}
],
"outputs": [],
"properties": {
"Node name for S&R": "PreviewImage"
},
"widgets_values": []
},
{
"id": 7,
"type": "MfluxLodeImage",
"pos": {
"0": 34,
"1": 187
},
"size": [
315,
358
],
"flags": {},
"order": 1,
"mode": 0,
"inputs": [],
"outputs": [
{
"name": "image",
"type": "MfluxImagePipeline",
"links": [
6
],
"label": "image",
"slot_index": 0
},
{
"name": "width",
"type": "INT",
"links": null,
"label": "width"
},
{
"name": "height",
"type": "INT",
"links": null,
"label": "height"
}
],
"properties": {
"Node name for S&R": "MfluxLodeImage"
},
"widgets_values": [
"IMG_0198.jpeg",
0.24,
"image"
]
}
],
"links": [
[
2,
3,
0,
1,
0,
"PATH"
],
[
3,
1,
0,
4,
0,
"IMAGE"
],
[
6,
7,
0,
1,
1,
"MfluxImagePipeline"
]
],
"groups": [],
"config": {},
"extra": {
"ds": {
"scale": 1.0152559799477054,
"offset": [
65.60323847105522,
65.08020389873201
]
}
},
"version": 0.4
}
-160
View File
@@ -1,160 +0,0 @@
{
"last_node_id": 4,
"last_link_id": 2,
"nodes": [
{
"id": 2,
"type": "QuickMfluxNode",
"pos": {
"0": 372,
"1": 60
},
"size": {
"0": 400,
"1": 332
},
"flags": {},
"order": 1,
"mode": 0,
"inputs": [
{
"name": "Local_models",
"type": "PATH",
"link": 1,
"shape": 7,
"label": "Local_models"
},
{
"name": "Loras",
"type": "MfluxLorasPipeline",
"link": null,
"shape": 7,
"label": "Loras"
},
{
"name": "ControlNet",
"type": "MfluxControlNetPipeline",
"link": null,
"shape": 7,
"label": "ControlNet"
}
],
"outputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"links": [
2
],
"label": "IMAGE",
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "QuickMfluxNode"
},
"widgets_values": [
"Luxury food photograph",
"schnell",
"None",
166089506520211,
"randomize",
512,
512,
2,
3.5,
"false",
true
]
},
{
"id": 4,
"type": "PreviewImage",
"pos": {
"0": 800,
"1": 62
},
"size": [
210,
246
],
"flags": {},
"order": 2,
"mode": 0,
"inputs": [
{
"name": "images",
"type": "IMAGE",
"link": 2,
"label": "图像"
}
],
"outputs": [],
"properties": {
"Node name for S&R": "PreviewImage"
}
},
{
"id": 3,
"type": "MfluxModelsLoader",
"pos": {
"0": 28,
"1": 57
},
"size": {
"0": 315,
"1": 58
},
"flags": {},
"order": 0,
"mode": 0,
"inputs": [],
"outputs": [
{
"name": "Local_models",
"type": "PATH",
"links": [
1
],
"label": "Local_models"
}
],
"properties": {
"Node name for S&R": "MfluxModelsLoader"
},
"widgets_values": [
"flux.1-schnell-mflux-4bit"
]
}
],
"links": [
[
1,
3,
0,
2,
0,
"PATH"
],
[
2,
2,
0,
4,
0,
"IMAGE"
]
],
"groups": [],
"config": {},
"extra": {
"ds": {
"scale": 1.3513057093104006,
"offset": [
34.263450117431965,
33.06811734068839
]
}
},
"version": 0.4
}
-120
View File
@@ -1,120 +0,0 @@
{
"last_node_id": 9,
"last_link_id": 6,
"nodes": [
{
"id": 7,
"type": "QuickMfluxNode",
"pos": {
"0": 32,
"1": 59
},
"size": {
"0": 400,
"1": 332
},
"flags": {},
"order": 0,
"mode": 0,
"inputs": [
{
"name": "Local_models",
"type": "PATH",
"link": null,
"shape": 7,
"label": "Local_models"
},
{
"name": "Loras",
"type": "MfluxLorasPipeline",
"link": null,
"shape": 7,
"label": "Loras"
},
{
"name": "ControlNet",
"type": "MfluxControlNetPipeline",
"link": null,
"shape": 7,
"label": "ControlNet"
}
],
"outputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"links": [
5
],
"label": "IMAGE",
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "QuickMfluxNode"
},
"widgets_values": [
"Luxury food photograph",
"schnell",
"4",
92876610272781,
"randomize",
512,
512,
2,
3.5,
"false",
true
]
},
{
"id": 8,
"type": "PreviewImage",
"pos": {
"0": 465,
"1": 60
},
"size": [
210,
246
],
"flags": {},
"order": 1,
"mode": 0,
"inputs": [
{
"name": "images",
"type": "IMAGE",
"link": 5,
"label": "图像"
}
],
"outputs": [],
"properties": {
"Node name for S&R": "PreviewImage"
}
}
],
"links": [
[
5,
7,
0,
8,
0,
"IMAGE"
]
],
"groups": [],
"config": {},
"extra": {
"ds": {
"scale": 1.2284597357367277,
"offset": [
24.56912269045404,
25.075847400889575
]
}
},
"version": 0.4
}
-168
View File
@@ -1,168 +0,0 @@
{
"last_node_id": 3,
"last_link_id": 2,
"nodes": [
{
"id": 2,
"type": "QuickMfluxNode",
"pos": {
"0": 463,
"1": 61
},
"size": {
"0": 400,
"1": 336
},
"flags": {},
"order": 1,
"mode": 0,
"inputs": [
{
"name": "lora_files",
"type": "LIST",
"link": null,
"label": "lora_files"
},
{
"name": "ControlNet",
"type": "MfluxControlNetPipeline",
"link": null,
"label": "ControlNet"
},
{
"name": "prompt",
"type": "STRING",
"link": 1,
"widget": {
"name": "prompt"
},
"label": "prompt"
}
],
"outputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"links": [
2
],
"slot_index": 0,
"shape": 3,
"label": "IMAGE"
}
],
"properties": {
"Node name for S&R": "QuickMfluxNode"
},
"widgets_values": [
"Luxury food photograph",
"schnell",
"flux.1-schnell-mflux-4bit",
656585466532514,
"randomize",
512,
512,
2,
3.5,
"false",
"",
true
]
},
{
"id": 3,
"type": "PreviewImage",
"pos": {
"0": 901,
"1": 60
},
"size": {
"0": 210,
"1": 246
},
"flags": {},
"order": 2,
"mode": 0,
"inputs": [
{
"name": "images",
"type": "IMAGE",
"link": 2,
"label": "图像"
}
],
"outputs": [],
"properties": {
"Node name for S&R": "PreviewImage"
},
"widgets_values": []
},
{
"id": 1,
"type": "ArgosTranslateTextNode",
"pos": {
"0": 27,
"1": 57
},
"size": {
"0": 400,
"1": 200
},
"flags": {},
"order": 0,
"mode": 0,
"inputs": [],
"outputs": [
{
"name": "text",
"type": "STRING",
"links": [
1
],
"slot_index": 0,
"shape": 3,
"label": "文本"
}
],
"properties": {
"Node name for S&R": "ArgosTranslateTextNode"
},
"widgets_values": [
"chinese",
"english",
"明月松间照,清泉石上流。",
true
]
}
],
"links": [
[
1,
1,
0,
2,
2,
"STRING"
],
[
2,
2,
0,
3,
0,
"IMAGE"
]
],
"groups": [],
"config": {},
"extra": {
"ds": {
"scale": 1.2284597357367277,
"offset": [
25.90558087874367,
28.985363606324412
]
}
},
"version": 0.4
}