InvokeAI 6.9.0rc3__py3-none-any.whl → 6.10.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- invokeai/app/api/dependencies.py +2 -0
- invokeai/app/api/routers/model_manager.py +91 -2
- invokeai/app/api/routers/workflows.py +9 -0
- invokeai/app/invocations/fields.py +19 -0
- invokeai/app/invocations/flux_denoise.py +15 -1
- invokeai/app/invocations/image_to_latents.py +23 -5
- invokeai/app/invocations/latents_to_image.py +2 -25
- invokeai/app/invocations/metadata.py +9 -1
- invokeai/app/invocations/metadata_linked.py +47 -0
- invokeai/app/invocations/model.py +8 -0
- invokeai/app/invocations/pbr_maps.py +59 -0
- invokeai/app/invocations/primitives.py +12 -0
- invokeai/app/invocations/prompt_template.py +57 -0
- invokeai/app/invocations/z_image_control.py +112 -0
- invokeai/app/invocations/z_image_denoise.py +770 -0
- invokeai/app/invocations/z_image_image_to_latents.py +102 -0
- invokeai/app/invocations/z_image_latents_to_image.py +103 -0
- invokeai/app/invocations/z_image_lora_loader.py +153 -0
- invokeai/app/invocations/z_image_model_loader.py +135 -0
- invokeai/app/invocations/z_image_text_encoder.py +197 -0
- invokeai/app/services/config/config_default.py +3 -1
- invokeai/app/services/model_install/model_install_common.py +14 -1
- invokeai/app/services/model_install/model_install_default.py +119 -19
- invokeai/app/services/model_manager/model_manager_default.py +7 -0
- invokeai/app/services/model_records/model_records_base.py +12 -0
- invokeai/app/services/model_records/model_records_sql.py +17 -0
- invokeai/app/services/shared/graph.py +132 -77
- invokeai/app/services/workflow_records/workflow_records_base.py +8 -0
- invokeai/app/services/workflow_records/workflow_records_sqlite.py +42 -0
- invokeai/app/util/step_callback.py +3 -0
- invokeai/backend/flux/denoise.py +196 -11
- invokeai/backend/flux/schedulers.py +62 -0
- invokeai/backend/image_util/pbr_maps/architecture/block.py +367 -0
- invokeai/backend/image_util/pbr_maps/architecture/pbr_rrdb_net.py +70 -0
- invokeai/backend/image_util/pbr_maps/pbr_maps.py +141 -0
- invokeai/backend/image_util/pbr_maps/utils/image_ops.py +93 -0
- invokeai/backend/model_manager/configs/controlnet.py +47 -1
- invokeai/backend/model_manager/configs/factory.py +26 -1
- invokeai/backend/model_manager/configs/lora.py +79 -1
- invokeai/backend/model_manager/configs/main.py +113 -0
- invokeai/backend/model_manager/configs/qwen3_encoder.py +156 -0
- invokeai/backend/model_manager/load/model_cache/model_cache.py +104 -2
- invokeai/backend/model_manager/load/model_cache/torch_module_autocast/custom_modules/custom_diffusers_rms_norm.py +40 -0
- invokeai/backend/model_manager/load/model_cache/torch_module_autocast/custom_modules/custom_layer_norm.py +25 -0
- invokeai/backend/model_manager/load/model_cache/torch_module_autocast/torch_module_autocast.py +11 -2
- invokeai/backend/model_manager/load/model_loaders/cogview4.py +2 -1
- invokeai/backend/model_manager/load/model_loaders/flux.py +13 -6
- invokeai/backend/model_manager/load/model_loaders/generic_diffusers.py +4 -2
- invokeai/backend/model_manager/load/model_loaders/lora.py +11 -0
- invokeai/backend/model_manager/load/model_loaders/onnx.py +1 -0
- invokeai/backend/model_manager/load/model_loaders/stable_diffusion.py +2 -1
- invokeai/backend/model_manager/load/model_loaders/z_image.py +969 -0
- invokeai/backend/model_manager/load/model_util.py +6 -1
- invokeai/backend/model_manager/metadata/metadata_base.py +12 -5
- invokeai/backend/model_manager/model_on_disk.py +3 -0
- invokeai/backend/model_manager/starter_models.py +79 -0
- invokeai/backend/model_manager/taxonomy.py +5 -0
- invokeai/backend/model_manager/util/select_hf_files.py +23 -8
- invokeai/backend/patches/layer_patcher.py +34 -16
- invokeai/backend/patches/layers/lora_layer_base.py +2 -1
- invokeai/backend/patches/lora_conversions/flux_aitoolkit_lora_conversion_utils.py +17 -2
- invokeai/backend/patches/lora_conversions/flux_xlabs_lora_conversion_utils.py +92 -0
- invokeai/backend/patches/lora_conversions/formats.py +5 -0
- invokeai/backend/patches/lora_conversions/z_image_lora_constants.py +8 -0
- invokeai/backend/patches/lora_conversions/z_image_lora_conversion_utils.py +189 -0
- invokeai/backend/quantization/gguf/ggml_tensor.py +38 -4
- invokeai/backend/quantization/gguf/loaders.py +47 -12
- invokeai/backend/stable_diffusion/diffusion/conditioning_data.py +13 -0
- invokeai/backend/util/devices.py +25 -0
- invokeai/backend/util/hotfixes.py +2 -2
- invokeai/backend/z_image/__init__.py +16 -0
- invokeai/backend/z_image/extensions/__init__.py +1 -0
- invokeai/backend/z_image/extensions/regional_prompting_extension.py +205 -0
- invokeai/backend/z_image/text_conditioning.py +74 -0
- invokeai/backend/z_image/z_image_control_adapter.py +238 -0
- invokeai/backend/z_image/z_image_control_transformer.py +643 -0
- invokeai/backend/z_image/z_image_controlnet_extension.py +531 -0
- invokeai/backend/z_image/z_image_patchify_utils.py +135 -0
- invokeai/backend/z_image/z_image_transformer_patch.py +234 -0
- invokeai/frontend/web/dist/assets/App-BBELGD-n.js +161 -0
- invokeai/frontend/web/dist/assets/{browser-ponyfill-CN1j0ARZ.js → browser-ponyfill-4xPFTMT3.js} +1 -1
- invokeai/frontend/web/dist/assets/index-vCDSQboA.js +530 -0
- invokeai/frontend/web/dist/index.html +1 -1
- invokeai/frontend/web/dist/locales/de.json +24 -6
- invokeai/frontend/web/dist/locales/en-GB.json +1 -0
- invokeai/frontend/web/dist/locales/en.json +78 -3
- invokeai/frontend/web/dist/locales/es.json +0 -5
- invokeai/frontend/web/dist/locales/fr.json +0 -6
- invokeai/frontend/web/dist/locales/it.json +17 -64
- invokeai/frontend/web/dist/locales/ja.json +379 -44
- invokeai/frontend/web/dist/locales/ru.json +0 -6
- invokeai/frontend/web/dist/locales/vi.json +7 -54
- invokeai/frontend/web/dist/locales/zh-CN.json +0 -6
- invokeai/version/invokeai_version.py +1 -1
- {invokeai-6.9.0rc3.dist-info → invokeai-6.10.0.dist-info}/METADATA +4 -4
- {invokeai-6.9.0rc3.dist-info → invokeai-6.10.0.dist-info}/RECORD +102 -71
- invokeai/frontend/web/dist/assets/App-Cn9UyjoV.js +0 -161
- invokeai/frontend/web/dist/assets/index-BDrf9CL-.js +0 -530
- {invokeai-6.9.0rc3.dist-info → invokeai-6.10.0.dist-info}/WHEEL +0 -0
- {invokeai-6.9.0rc3.dist-info → invokeai-6.10.0.dist-info}/entry_points.txt +0 -0
- {invokeai-6.9.0rc3.dist-info → invokeai-6.10.0.dist-info}/licenses/LICENSE +0 -0
- {invokeai-6.9.0rc3.dist-info → invokeai-6.10.0.dist-info}/licenses/LICENSE-SD1+SD2.txt +0 -0
- {invokeai-6.9.0rc3.dist-info → invokeai-6.10.0.dist-info}/licenses/LICENSE-SDXL.txt +0 -0
- {invokeai-6.9.0rc3.dist-info → invokeai-6.10.0.dist-info}/top_level.txt +0 -0
invokeai/app/api/dependencies.py
CHANGED
|
@@ -49,6 +49,7 @@ from invokeai.backend.stable_diffusion.diffusion.conditioning_data import (
|
|
|
49
49
|
FLUXConditioningInfo,
|
|
50
50
|
SD3ConditioningInfo,
|
|
51
51
|
SDXLConditioningInfo,
|
|
52
|
+
ZImageConditioningInfo,
|
|
52
53
|
)
|
|
53
54
|
from invokeai.backend.util.logging import InvokeAILogger
|
|
54
55
|
from invokeai.version.invokeai_version import __version__
|
|
@@ -129,6 +130,7 @@ class ApiDependencies:
|
|
|
129
130
|
FLUXConditioningInfo,
|
|
130
131
|
SD3ConditioningInfo,
|
|
131
132
|
CogView4ConditioningInfo,
|
|
133
|
+
ZImageConditioningInfo,
|
|
132
134
|
],
|
|
133
135
|
ephemeral=True,
|
|
134
136
|
),
|
|
@@ -28,7 +28,7 @@ from invokeai.app.services.model_records import (
|
|
|
28
28
|
UnknownModelException,
|
|
29
29
|
)
|
|
30
30
|
from invokeai.app.util.suppress_output import SuppressOutput
|
|
31
|
-
from invokeai.backend.model_manager.configs.factory import AnyModelConfig
|
|
31
|
+
from invokeai.backend.model_manager.configs.factory import AnyModelConfig, ModelConfigFactory
|
|
32
32
|
from invokeai.backend.model_manager.configs.main import (
|
|
33
33
|
Main_Checkpoint_SD1_Config,
|
|
34
34
|
Main_Checkpoint_SD2_Config,
|
|
@@ -38,6 +38,7 @@ from invokeai.backend.model_manager.configs.main import (
|
|
|
38
38
|
from invokeai.backend.model_manager.load.model_cache.cache_stats import CacheStats
|
|
39
39
|
from invokeai.backend.model_manager.metadata.fetch.huggingface import HuggingFaceMetadataFetch
|
|
40
40
|
from invokeai.backend.model_manager.metadata.metadata_base import ModelMetadataWithFiles, UnknownMetadataException
|
|
41
|
+
from invokeai.backend.model_manager.model_on_disk import ModelOnDisk
|
|
41
42
|
from invokeai.backend.model_manager.search import ModelSearch
|
|
42
43
|
from invokeai.backend.model_manager.starter_models import (
|
|
43
44
|
STARTER_BUNDLES,
|
|
@@ -191,6 +192,40 @@ async def get_model_record(
|
|
|
191
192
|
raise HTTPException(status_code=404, detail=str(e))
|
|
192
193
|
|
|
193
194
|
|
|
195
|
+
@model_manager_router.post(
|
|
196
|
+
"/i/{key}/reidentify",
|
|
197
|
+
operation_id="reidentify_model",
|
|
198
|
+
responses={
|
|
199
|
+
200: {
|
|
200
|
+
"description": "The model configuration was retrieved successfully",
|
|
201
|
+
"content": {"application/json": {"example": example_model_config}},
|
|
202
|
+
},
|
|
203
|
+
400: {"description": "Bad request"},
|
|
204
|
+
404: {"description": "The model could not be found"},
|
|
205
|
+
},
|
|
206
|
+
)
|
|
207
|
+
async def reidentify_model(
|
|
208
|
+
key: Annotated[str, Path(description="Key of the model to reidentify.")],
|
|
209
|
+
) -> AnyModelConfig:
|
|
210
|
+
"""Attempt to reidentify a model by re-probing its weights file."""
|
|
211
|
+
try:
|
|
212
|
+
config = ApiDependencies.invoker.services.model_manager.store.get_model(key)
|
|
213
|
+
models_path = ApiDependencies.invoker.services.configuration.models_path
|
|
214
|
+
if pathlib.Path(config.path).is_relative_to(models_path):
|
|
215
|
+
model_path = pathlib.Path(config.path)
|
|
216
|
+
else:
|
|
217
|
+
model_path = models_path / config.path
|
|
218
|
+
mod = ModelOnDisk(model_path)
|
|
219
|
+
result = ModelConfigFactory.from_model_on_disk(mod)
|
|
220
|
+
if result.config is None:
|
|
221
|
+
raise InvalidModelException("Unable to identify model format")
|
|
222
|
+
result.config.key = config.key # retain the same key
|
|
223
|
+
new_config = ApiDependencies.invoker.services.model_manager.store.replace_model(config.key, result.config)
|
|
224
|
+
return new_config
|
|
225
|
+
except UnknownModelException as e:
|
|
226
|
+
raise HTTPException(status_code=404, detail=str(e))
|
|
227
|
+
|
|
228
|
+
|
|
194
229
|
class FoundModel(BaseModel):
|
|
195
230
|
path: str = Field(description="Path to the model")
|
|
196
231
|
is_installed: bool = Field(description="Whether or not the model is already installed")
|
|
@@ -238,9 +273,10 @@ async def scan_for_models(
|
|
|
238
273
|
found_model = FoundModel(path=path, is_installed=is_installed)
|
|
239
274
|
scan_results.append(found_model)
|
|
240
275
|
except Exception as e:
|
|
276
|
+
error_type = type(e).__name__
|
|
241
277
|
raise HTTPException(
|
|
242
278
|
status_code=500,
|
|
243
|
-
detail=f"An error occurred while searching the directory: {
|
|
279
|
+
detail=f"An error occurred while searching the directory: {error_type}",
|
|
244
280
|
)
|
|
245
281
|
return scan_results
|
|
246
282
|
|
|
@@ -411,6 +447,59 @@ async def delete_model(
|
|
|
411
447
|
raise HTTPException(status_code=404, detail=str(e))
|
|
412
448
|
|
|
413
449
|
|
|
450
|
+
class BulkDeleteModelsRequest(BaseModel):
|
|
451
|
+
"""Request body for bulk model deletion."""
|
|
452
|
+
|
|
453
|
+
keys: List[str] = Field(description="List of model keys to delete")
|
|
454
|
+
|
|
455
|
+
|
|
456
|
+
class BulkDeleteModelsResponse(BaseModel):
|
|
457
|
+
"""Response body for bulk model deletion."""
|
|
458
|
+
|
|
459
|
+
deleted: List[str] = Field(description="List of successfully deleted model keys")
|
|
460
|
+
failed: List[dict] = Field(description="List of failed deletions with error messages")
|
|
461
|
+
|
|
462
|
+
|
|
463
|
+
@model_manager_router.post(
|
|
464
|
+
"/i/bulk_delete",
|
|
465
|
+
operation_id="bulk_delete_models",
|
|
466
|
+
responses={
|
|
467
|
+
200: {"description": "Models deleted (possibly with some failures)"},
|
|
468
|
+
},
|
|
469
|
+
status_code=200,
|
|
470
|
+
)
|
|
471
|
+
async def bulk_delete_models(
|
|
472
|
+
request: BulkDeleteModelsRequest = Body(description="List of model keys to delete"),
|
|
473
|
+
) -> BulkDeleteModelsResponse:
|
|
474
|
+
"""
|
|
475
|
+
Delete multiple model records from database.
|
|
476
|
+
|
|
477
|
+
The configuration records will be removed. The corresponding weights files will be
|
|
478
|
+
deleted as well if they reside within the InvokeAI "models" directory.
|
|
479
|
+
Returns a list of successfully deleted keys and failed deletions with error messages.
|
|
480
|
+
"""
|
|
481
|
+
logger = ApiDependencies.invoker.services.logger
|
|
482
|
+
installer = ApiDependencies.invoker.services.model_manager.install
|
|
483
|
+
|
|
484
|
+
deleted = []
|
|
485
|
+
failed = []
|
|
486
|
+
|
|
487
|
+
for key in request.keys:
|
|
488
|
+
try:
|
|
489
|
+
installer.delete(key)
|
|
490
|
+
deleted.append(key)
|
|
491
|
+
logger.info(f"Deleted model: {key}")
|
|
492
|
+
except UnknownModelException as e:
|
|
493
|
+
logger.error(f"Failed to delete model {key}: {str(e)}")
|
|
494
|
+
failed.append({"key": key, "error": str(e)})
|
|
495
|
+
except Exception as e:
|
|
496
|
+
logger.error(f"Failed to delete model {key}: {str(e)}")
|
|
497
|
+
failed.append({"key": key, "error": str(e)})
|
|
498
|
+
|
|
499
|
+
logger.info(f"Bulk delete completed: {len(deleted)} deleted, {len(failed)} failed")
|
|
500
|
+
return BulkDeleteModelsResponse(deleted=deleted, failed=failed)
|
|
501
|
+
|
|
502
|
+
|
|
414
503
|
@model_manager_router.delete(
|
|
415
504
|
"/i/{key}/image",
|
|
416
505
|
operation_id="delete_model_image",
|
|
@@ -223,6 +223,15 @@ async def get_workflow_thumbnail(
|
|
|
223
223
|
raise HTTPException(status_code=404)
|
|
224
224
|
|
|
225
225
|
|
|
226
|
+
@workflows_router.get("/tags", operation_id="get_all_tags")
|
|
227
|
+
async def get_all_tags(
|
|
228
|
+
categories: Optional[list[WorkflowCategory]] = Query(default=None, description="The categories to include"),
|
|
229
|
+
) -> list[str]:
|
|
230
|
+
"""Gets all unique tags from workflows"""
|
|
231
|
+
|
|
232
|
+
return ApiDependencies.invoker.services.workflow_records.get_all_tags(categories=categories)
|
|
233
|
+
|
|
234
|
+
|
|
226
235
|
@workflows_router.get("/counts_by_tag", operation_id="get_counts_by_tag")
|
|
227
236
|
async def get_counts_by_tag(
|
|
228
237
|
tags: list[str] = Query(description="The tags to get counts for"),
|
|
@@ -154,6 +154,7 @@ class FieldDescriptions:
|
|
|
154
154
|
clip = "CLIP (tokenizer, text encoder, LoRAs) and skipped layer count"
|
|
155
155
|
t5_encoder = "T5 tokenizer and text encoder"
|
|
156
156
|
glm_encoder = "GLM (THUDM) tokenizer and text encoder"
|
|
157
|
+
qwen3_encoder = "Qwen3 tokenizer and text encoder"
|
|
157
158
|
clip_embed_model = "CLIP Embed loader"
|
|
158
159
|
clip_g_model = "CLIP-G Embed loader"
|
|
159
160
|
unet = "UNet (scheduler, LoRAs)"
|
|
@@ -169,6 +170,7 @@ class FieldDescriptions:
|
|
|
169
170
|
flux_model = "Flux model (Transformer) to load"
|
|
170
171
|
sd3_model = "SD3 model (MMDiTX) to load"
|
|
171
172
|
cogview4_model = "CogView4 model (Transformer) to load"
|
|
173
|
+
z_image_model = "Z-Image model (Transformer) to load"
|
|
172
174
|
sdxl_main_model = "SDXL Main model (UNet, VAE, CLIP1, CLIP2) to load"
|
|
173
175
|
sdxl_refiner_model = "SDXL Refiner Main Modde (UNet, VAE, CLIP2) to load"
|
|
174
176
|
onnx_main_model = "ONNX Main model (UNet, VAE, CLIP) to load"
|
|
@@ -241,6 +243,12 @@ class BoardField(BaseModel):
|
|
|
241
243
|
board_id: str = Field(description="The id of the board")
|
|
242
244
|
|
|
243
245
|
|
|
246
|
+
class StylePresetField(BaseModel):
|
|
247
|
+
"""A style preset primitive field"""
|
|
248
|
+
|
|
249
|
+
style_preset_id: str = Field(description="The id of the style preset")
|
|
250
|
+
|
|
251
|
+
|
|
244
252
|
class DenoiseMaskField(BaseModel):
|
|
245
253
|
"""An inpaint mask field"""
|
|
246
254
|
|
|
@@ -321,6 +329,17 @@ class CogView4ConditioningField(BaseModel):
|
|
|
321
329
|
conditioning_name: str = Field(description="The name of conditioning tensor")
|
|
322
330
|
|
|
323
331
|
|
|
332
|
+
class ZImageConditioningField(BaseModel):
|
|
333
|
+
"""A Z-Image conditioning tensor primitive value"""
|
|
334
|
+
|
|
335
|
+
conditioning_name: str = Field(description="The name of conditioning tensor")
|
|
336
|
+
mask: Optional[TensorField] = Field(
|
|
337
|
+
default=None,
|
|
338
|
+
description="The mask associated with this conditioning tensor for regional prompting. "
|
|
339
|
+
"Excluded regions should be set to False, included regions should be set to True.",
|
|
340
|
+
)
|
|
341
|
+
|
|
342
|
+
|
|
324
343
|
class ConditioningField(BaseModel):
|
|
325
344
|
"""A conditioning tensor primitive value"""
|
|
326
345
|
|
|
@@ -47,6 +47,7 @@ from invokeai.backend.flux.sampling_utils import (
|
|
|
47
47
|
pack,
|
|
48
48
|
unpack,
|
|
49
49
|
)
|
|
50
|
+
from invokeai.backend.flux.schedulers import FLUX_SCHEDULER_LABELS, FLUX_SCHEDULER_MAP, FLUX_SCHEDULER_NAME_VALUES
|
|
50
51
|
from invokeai.backend.flux.text_conditioning import FluxReduxConditioning, FluxTextConditioning
|
|
51
52
|
from invokeai.backend.model_manager.taxonomy import BaseModelType, FluxVariantType, ModelFormat, ModelType
|
|
52
53
|
from invokeai.backend.patches.layer_patcher import LayerPatcher
|
|
@@ -63,7 +64,7 @@ from invokeai.backend.util.devices import TorchDevice
|
|
|
63
64
|
title="FLUX Denoise",
|
|
64
65
|
tags=["image", "flux"],
|
|
65
66
|
category="image",
|
|
66
|
-
version="4.
|
|
67
|
+
version="4.2.0",
|
|
67
68
|
)
|
|
68
69
|
class FluxDenoiseInvocation(BaseInvocation):
|
|
69
70
|
"""Run denoising process with a FLUX transformer model."""
|
|
@@ -132,6 +133,12 @@ class FluxDenoiseInvocation(BaseInvocation):
|
|
|
132
133
|
num_steps: int = InputField(
|
|
133
134
|
default=4, description="Number of diffusion steps. Recommended values are schnell: 4, dev: 50."
|
|
134
135
|
)
|
|
136
|
+
scheduler: FLUX_SCHEDULER_NAME_VALUES = InputField(
|
|
137
|
+
default="euler",
|
|
138
|
+
description="Scheduler (sampler) for the denoising process. 'euler' is fast and standard. "
|
|
139
|
+
"'heun' is 2nd-order (better quality, 2x slower). 'lcm' is optimized for few steps.",
|
|
140
|
+
ui_choice_labels=FLUX_SCHEDULER_LABELS,
|
|
141
|
+
)
|
|
135
142
|
guidance: float = InputField(
|
|
136
143
|
default=4.0,
|
|
137
144
|
description="The guidance strength. Higher values adhere more strictly to the prompt, and will produce less diverse images. FLUX dev only, ignored for schnell.",
|
|
@@ -242,6 +249,12 @@ class FluxDenoiseInvocation(BaseInvocation):
|
|
|
242
249
|
shift=not is_schnell,
|
|
243
250
|
)
|
|
244
251
|
|
|
252
|
+
# Create scheduler if not using default euler
|
|
253
|
+
scheduler = None
|
|
254
|
+
if self.scheduler in FLUX_SCHEDULER_MAP:
|
|
255
|
+
scheduler_class = FLUX_SCHEDULER_MAP[self.scheduler]
|
|
256
|
+
scheduler = scheduler_class(num_train_timesteps=1000)
|
|
257
|
+
|
|
245
258
|
# Clip the timesteps schedule based on denoising_start and denoising_end.
|
|
246
259
|
timesteps = clip_timestep_schedule_fractional(timesteps, self.denoising_start, self.denoising_end)
|
|
247
260
|
|
|
@@ -426,6 +439,7 @@ class FluxDenoiseInvocation(BaseInvocation):
|
|
|
426
439
|
img_cond=img_cond,
|
|
427
440
|
img_cond_seq=img_cond_seq,
|
|
428
441
|
img_cond_seq_ids=img_cond_seq_ids,
|
|
442
|
+
scheduler=scheduler,
|
|
429
443
|
)
|
|
430
444
|
|
|
431
445
|
x = unpack(x.float(), self.height, self.width)
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from contextlib import nullcontext
|
|
2
2
|
from functools import singledispatchmethod
|
|
3
|
+
from typing import Literal
|
|
3
4
|
|
|
4
5
|
import einops
|
|
5
6
|
import torch
|
|
@@ -20,7 +21,7 @@ from invokeai.app.invocations.fields import (
|
|
|
20
21
|
Input,
|
|
21
22
|
InputField,
|
|
22
23
|
)
|
|
23
|
-
from invokeai.app.invocations.model import VAEField
|
|
24
|
+
from invokeai.app.invocations.model import BaseModelType, VAEField
|
|
24
25
|
from invokeai.app.invocations.primitives import LatentsOutput
|
|
25
26
|
from invokeai.app.services.shared.invocation_context import InvocationContext
|
|
26
27
|
from invokeai.backend.model_manager.load.load_base import LoadedModel
|
|
@@ -29,13 +30,21 @@ from invokeai.backend.stable_diffusion.vae_tiling import patch_vae_tiling_params
|
|
|
29
30
|
from invokeai.backend.util.devices import TorchDevice
|
|
30
31
|
from invokeai.backend.util.vae_working_memory import estimate_vae_working_memory_sd15_sdxl
|
|
31
32
|
|
|
33
|
+
"""
|
|
34
|
+
SDXL VAE color compensation values determined experimentally to reduce color drift.
|
|
35
|
+
If more reliable values are found in the future (e.g. individual color channels), they can be updated.
|
|
36
|
+
SD1.5, TAESD, TAESDXL VAEs distort in less predictable ways, so no compensation is offered at this time.
|
|
37
|
+
"""
|
|
38
|
+
COMPENSATION_OPTIONS = Literal["None", "SDXL"]
|
|
39
|
+
COLOR_COMPENSATION_MAP = {"None": [1, 0], "SDXL": [1.015, -0.002]}
|
|
40
|
+
|
|
32
41
|
|
|
33
42
|
@invocation(
|
|
34
43
|
"i2l",
|
|
35
44
|
title="Image to Latents - SD1.5, SDXL",
|
|
36
45
|
tags=["latents", "image", "vae", "i2l"],
|
|
37
46
|
category="latents",
|
|
38
|
-
version="1.
|
|
47
|
+
version="1.2.0",
|
|
39
48
|
)
|
|
40
49
|
class ImageToLatentsInvocation(BaseInvocation):
|
|
41
50
|
"""Encodes an image into latents."""
|
|
@@ -52,6 +61,10 @@ class ImageToLatentsInvocation(BaseInvocation):
|
|
|
52
61
|
# offer a way to directly set None values.
|
|
53
62
|
tile_size: int = InputField(default=0, multiple_of=8, description=FieldDescriptions.vae_tile_size)
|
|
54
63
|
fp32: bool = InputField(default=False, description=FieldDescriptions.fp32)
|
|
64
|
+
color_compensation: COMPENSATION_OPTIONS = InputField(
|
|
65
|
+
default="None",
|
|
66
|
+
description="Apply VAE scaling compensation when encoding images (reduces color drift).",
|
|
67
|
+
)
|
|
55
68
|
|
|
56
69
|
@classmethod
|
|
57
70
|
def vae_encode(
|
|
@@ -62,7 +75,7 @@ class ImageToLatentsInvocation(BaseInvocation):
|
|
|
62
75
|
image_tensor: torch.Tensor,
|
|
63
76
|
tile_size: int = 0,
|
|
64
77
|
) -> torch.Tensor:
|
|
65
|
-
assert isinstance(vae_info.model, (AutoencoderKL, AutoencoderTiny))
|
|
78
|
+
assert isinstance(vae_info.model, (AutoencoderKL, AutoencoderTiny)), "VAE must be of type SD-1.5 or SDXL"
|
|
66
79
|
estimated_working_memory = estimate_vae_working_memory_sd15_sdxl(
|
|
67
80
|
operation="encode",
|
|
68
81
|
image_tensor=image_tensor,
|
|
@@ -71,7 +84,7 @@ class ImageToLatentsInvocation(BaseInvocation):
|
|
|
71
84
|
fp32=upcast,
|
|
72
85
|
)
|
|
73
86
|
with vae_info.model_on_device(working_mem_bytes=estimated_working_memory) as (_, vae):
|
|
74
|
-
assert isinstance(vae, (AutoencoderKL, AutoencoderTiny))
|
|
87
|
+
assert isinstance(vae, (AutoencoderKL, AutoencoderTiny)), "VAE must be of type SD-1.5 or SDXL"
|
|
75
88
|
orig_dtype = vae.dtype
|
|
76
89
|
if upcast:
|
|
77
90
|
vae.to(dtype=torch.float32)
|
|
@@ -127,9 +140,14 @@ class ImageToLatentsInvocation(BaseInvocation):
|
|
|
127
140
|
image = context.images.get_pil(self.image.image_name)
|
|
128
141
|
|
|
129
142
|
vae_info = context.models.load(self.vae.vae)
|
|
130
|
-
assert isinstance(vae_info.model, (AutoencoderKL, AutoencoderTiny))
|
|
143
|
+
assert isinstance(vae_info.model, (AutoencoderKL, AutoencoderTiny)), "VAE must be of type SD-1.5 or SDXL"
|
|
131
144
|
|
|
132
145
|
image_tensor = image_resized_to_grid_as_tensor(image.convert("RGB"))
|
|
146
|
+
|
|
147
|
+
if self.color_compensation != "None" and vae_info.config.base == BaseModelType.StableDiffusionXL:
|
|
148
|
+
scale, bias = COLOR_COMPENSATION_MAP[self.color_compensation]
|
|
149
|
+
image_tensor = image_tensor * scale + bias
|
|
150
|
+
|
|
133
151
|
if image_tensor.dim() == 3:
|
|
134
152
|
image_tensor = einops.rearrange(image_tensor, "c h w -> 1 c h w")
|
|
135
153
|
|
|
@@ -2,12 +2,6 @@ from contextlib import nullcontext
|
|
|
2
2
|
|
|
3
3
|
import torch
|
|
4
4
|
from diffusers.image_processor import VaeImageProcessor
|
|
5
|
-
from diffusers.models.attention_processor import (
|
|
6
|
-
AttnProcessor2_0,
|
|
7
|
-
LoRAAttnProcessor2_0,
|
|
8
|
-
LoRAXFormersAttnProcessor,
|
|
9
|
-
XFormersAttnProcessor,
|
|
10
|
-
)
|
|
11
5
|
from diffusers.models.autoencoders.autoencoder_kl import AutoencoderKL
|
|
12
6
|
from diffusers.models.autoencoders.autoencoder_tiny import AutoencoderTiny
|
|
13
7
|
|
|
@@ -77,26 +71,9 @@ class LatentsToImageInvocation(BaseInvocation, WithMetadata, WithBoard):
|
|
|
77
71
|
assert isinstance(vae, (AutoencoderKL, AutoencoderTiny))
|
|
78
72
|
latents = latents.to(TorchDevice.choose_torch_device())
|
|
79
73
|
if self.fp32:
|
|
74
|
+
# FP32 mode: convert everything to float32 for maximum precision
|
|
80
75
|
vae.to(dtype=torch.float32)
|
|
81
|
-
|
|
82
|
-
use_torch_2_0_or_xformers = hasattr(vae.decoder, "mid_block") and isinstance(
|
|
83
|
-
vae.decoder.mid_block.attentions[0].processor,
|
|
84
|
-
(
|
|
85
|
-
AttnProcessor2_0,
|
|
86
|
-
XFormersAttnProcessor,
|
|
87
|
-
LoRAXFormersAttnProcessor,
|
|
88
|
-
LoRAAttnProcessor2_0,
|
|
89
|
-
),
|
|
90
|
-
)
|
|
91
|
-
# if xformers or torch_2_0 is used attention block does not need
|
|
92
|
-
# to be in float32 which can save lots of memory
|
|
93
|
-
if use_torch_2_0_or_xformers:
|
|
94
|
-
vae.post_quant_conv.to(latents.dtype)
|
|
95
|
-
vae.decoder.conv_in.to(latents.dtype)
|
|
96
|
-
vae.decoder.mid_block.to(latents.dtype)
|
|
97
|
-
else:
|
|
98
|
-
latents = latents.float()
|
|
99
|
-
|
|
76
|
+
latents = latents.float()
|
|
100
77
|
else:
|
|
101
78
|
vae.to(dtype=torch.float16)
|
|
102
79
|
latents = latents.half()
|
|
@@ -158,6 +158,10 @@ GENERATION_MODES = Literal[
|
|
|
158
158
|
"cogview4_img2img",
|
|
159
159
|
"cogview4_inpaint",
|
|
160
160
|
"cogview4_outpaint",
|
|
161
|
+
"z_image_txt2img",
|
|
162
|
+
"z_image_img2img",
|
|
163
|
+
"z_image_inpaint",
|
|
164
|
+
"z_image_outpaint",
|
|
161
165
|
]
|
|
162
166
|
|
|
163
167
|
|
|
@@ -166,7 +170,7 @@ GENERATION_MODES = Literal[
|
|
|
166
170
|
title="Core Metadata",
|
|
167
171
|
tags=["metadata"],
|
|
168
172
|
category="metadata",
|
|
169
|
-
version="2.
|
|
173
|
+
version="2.1.0",
|
|
170
174
|
classification=Classification.Internal,
|
|
171
175
|
)
|
|
172
176
|
class CoreMetadataInvocation(BaseInvocation):
|
|
@@ -217,6 +221,10 @@ class CoreMetadataInvocation(BaseInvocation):
|
|
|
217
221
|
default=None,
|
|
218
222
|
description="The VAE used for decoding, if the main model's default was not used",
|
|
219
223
|
)
|
|
224
|
+
qwen3_encoder: Optional[ModelIdentifierField] = InputField(
|
|
225
|
+
default=None,
|
|
226
|
+
description="The Qwen3 text encoder model used for Z-Image inference",
|
|
227
|
+
)
|
|
220
228
|
|
|
221
229
|
# High resolution fix metadata.
|
|
222
230
|
hrf_enabled: Optional[bool] = InputField(
|
|
@@ -52,6 +52,7 @@ from invokeai.app.invocations.primitives import (
|
|
|
52
52
|
)
|
|
53
53
|
from invokeai.app.invocations.scheduler import SchedulerOutput
|
|
54
54
|
from invokeai.app.invocations.t2i_adapter import T2IAdapterField, T2IAdapterInvocation
|
|
55
|
+
from invokeai.app.invocations.z_image_denoise import ZImageDenoiseInvocation
|
|
55
56
|
from invokeai.app.services.shared.invocation_context import InvocationContext
|
|
56
57
|
from invokeai.backend.model_manager.taxonomy import BaseModelType, ModelType, SubModelType
|
|
57
58
|
from invokeai.backend.stable_diffusion.schedulers.schedulers import SCHEDULER_NAME_VALUES
|
|
@@ -729,6 +730,52 @@ class FluxDenoiseLatentsMetaInvocation(FluxDenoiseInvocation, WithMetadata):
|
|
|
729
730
|
return LatentsMetaOutput(**params, metadata=MetadataField.model_validate(md))
|
|
730
731
|
|
|
731
732
|
|
|
733
|
+
@invocation(
|
|
734
|
+
"z_image_denoise_meta",
|
|
735
|
+
title=f"{ZImageDenoiseInvocation.UIConfig.title} + Metadata",
|
|
736
|
+
tags=["z-image", "latents", "denoise", "txt2img", "t2i", "t2l", "img2img", "i2i", "l2l"],
|
|
737
|
+
category="latents",
|
|
738
|
+
version="1.0.0",
|
|
739
|
+
)
|
|
740
|
+
class ZImageDenoiseMetaInvocation(ZImageDenoiseInvocation, WithMetadata):
|
|
741
|
+
"""Run denoising process with a Z-Image transformer model + metadata."""
|
|
742
|
+
|
|
743
|
+
def invoke(self, context: InvocationContext) -> LatentsMetaOutput:
|
|
744
|
+
def _loras_to_json(obj: Union[Any, list[Any]]):
|
|
745
|
+
if not isinstance(obj, list):
|
|
746
|
+
obj = [obj]
|
|
747
|
+
|
|
748
|
+
output: list[dict[str, Any]] = []
|
|
749
|
+
for item in obj:
|
|
750
|
+
output.append(
|
|
751
|
+
LoRAMetadataField(
|
|
752
|
+
model=item.lora,
|
|
753
|
+
weight=item.weight,
|
|
754
|
+
).model_dump(exclude_none=True, exclude={"id", "type", "is_intermediate", "use_cache"})
|
|
755
|
+
)
|
|
756
|
+
return output
|
|
757
|
+
|
|
758
|
+
obj = super().invoke(context)
|
|
759
|
+
|
|
760
|
+
md: Dict[str, Any] = {} if self.metadata is None else self.metadata.root
|
|
761
|
+
md.update({"width": obj.width})
|
|
762
|
+
md.update({"height": obj.height})
|
|
763
|
+
md.update({"steps": self.steps})
|
|
764
|
+
md.update({"guidance": self.guidance_scale})
|
|
765
|
+
md.update({"denoising_start": self.denoising_start})
|
|
766
|
+
md.update({"denoising_end": self.denoising_end})
|
|
767
|
+
md.update({"scheduler": self.scheduler})
|
|
768
|
+
md.update({"model": self.transformer.transformer})
|
|
769
|
+
md.update({"seed": self.seed})
|
|
770
|
+
if len(self.transformer.loras) > 0:
|
|
771
|
+
md.update({"loras": _loras_to_json(self.transformer.loras)})
|
|
772
|
+
|
|
773
|
+
params = obj.__dict__.copy()
|
|
774
|
+
del params["type"]
|
|
775
|
+
|
|
776
|
+
return LatentsMetaOutput(**params, metadata=MetadataField.model_validate(md))
|
|
777
|
+
|
|
778
|
+
|
|
732
779
|
@invocation(
|
|
733
780
|
"metadata_to_vae",
|
|
734
781
|
title="Metadata To VAE",
|
|
@@ -72,6 +72,14 @@ class GlmEncoderField(BaseModel):
|
|
|
72
72
|
text_encoder: ModelIdentifierField = Field(description="Info to load text_encoder submodel")
|
|
73
73
|
|
|
74
74
|
|
|
75
|
+
class Qwen3EncoderField(BaseModel):
|
|
76
|
+
"""Field for Qwen3 text encoder used by Z-Image models."""
|
|
77
|
+
|
|
78
|
+
tokenizer: ModelIdentifierField = Field(description="Info to load tokenizer submodel")
|
|
79
|
+
text_encoder: ModelIdentifierField = Field(description="Info to load text_encoder submodel")
|
|
80
|
+
loras: List[LoRAField] = Field(default_factory=list, description="LoRAs to apply on model loading")
|
|
81
|
+
|
|
82
|
+
|
|
75
83
|
class VAEField(BaseModel):
|
|
76
84
|
vae: ModelIdentifierField = Field(description="Info to load vae submodel")
|
|
77
85
|
seamless_axes: List[str] = Field(default_factory=list, description='Axes("x" and "y") to which apply seamless')
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import pathlib
|
|
2
|
+
from typing import Literal
|
|
3
|
+
|
|
4
|
+
from invokeai.app.invocations.baseinvocation import BaseInvocation, BaseInvocationOutput, invocation, invocation_output
|
|
5
|
+
from invokeai.app.invocations.fields import ImageField, InputField, OutputField, WithBoard, WithMetadata
|
|
6
|
+
from invokeai.app.services.shared.invocation_context import InvocationContext
|
|
7
|
+
from invokeai.backend.image_util.pbr_maps.architecture.pbr_rrdb_net import PBR_RRDB_Net
|
|
8
|
+
from invokeai.backend.image_util.pbr_maps.pbr_maps import NORMAL_MAP_MODEL, OTHER_MAP_MODEL, PBRMapsGenerator
|
|
9
|
+
from invokeai.backend.util.devices import TorchDevice
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@invocation_output("pbr_maps-output")
|
|
13
|
+
class PBRMapsOutput(BaseInvocationOutput):
|
|
14
|
+
normal_map: ImageField = OutputField(default=None, description="The generated normal map")
|
|
15
|
+
roughness_map: ImageField = OutputField(default=None, description="The generated roughness map")
|
|
16
|
+
displacement_map: ImageField = OutputField(default=None, description="The generated displacement map")
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@invocation("pbr_maps", title="PBR Maps", tags=["image", "material"], category="image", version="1.0.0")
|
|
20
|
+
class PBRMapsInvocation(BaseInvocation, WithMetadata, WithBoard):
|
|
21
|
+
"""Generate Normal, Displacement and Roughness Map from a given image"""
|
|
22
|
+
|
|
23
|
+
image: ImageField = InputField(description="Input image")
|
|
24
|
+
tile_size: int = InputField(default=512, description="Tile size")
|
|
25
|
+
border_mode: Literal["none", "seamless", "mirror", "replicate"] = InputField(
|
|
26
|
+
default="none", description="Border mode to apply to eliminate any artifacts or seams"
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
def invoke(self, context: InvocationContext) -> PBRMapsOutput:
|
|
30
|
+
image_pil = context.images.get_pil(self.image.image_name, mode="RGB")
|
|
31
|
+
|
|
32
|
+
def loader(model_path: pathlib.Path):
|
|
33
|
+
return PBRMapsGenerator.load_model(model_path, TorchDevice.choose_torch_device())
|
|
34
|
+
|
|
35
|
+
torch_device = TorchDevice.choose_torch_device()
|
|
36
|
+
|
|
37
|
+
with (
|
|
38
|
+
context.models.load_remote_model(NORMAL_MAP_MODEL, loader) as normal_map_model,
|
|
39
|
+
context.models.load_remote_model(OTHER_MAP_MODEL, loader) as other_map_model,
|
|
40
|
+
):
|
|
41
|
+
assert isinstance(normal_map_model, PBR_RRDB_Net)
|
|
42
|
+
assert isinstance(other_map_model, PBR_RRDB_Net)
|
|
43
|
+
pbr_pipeline = PBRMapsGenerator(normal_map_model, other_map_model, torch_device)
|
|
44
|
+
normal_map, roughness_map, displacement_map = pbr_pipeline.generate_maps(
|
|
45
|
+
image_pil, self.tile_size, self.border_mode
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
normal_map = context.images.save(normal_map)
|
|
49
|
+
normal_map_field = ImageField(image_name=normal_map.image_name)
|
|
50
|
+
|
|
51
|
+
roughness_map = context.images.save(roughness_map)
|
|
52
|
+
roughness_map_field = ImageField(image_name=roughness_map.image_name)
|
|
53
|
+
|
|
54
|
+
displacement_map = context.images.save(displacement_map)
|
|
55
|
+
displacement_map_field = ImageField(image_name=displacement_map.image_name)
|
|
56
|
+
|
|
57
|
+
return PBRMapsOutput(
|
|
58
|
+
normal_map=normal_map_field, roughness_map=roughness_map_field, displacement_map=displacement_map_field
|
|
59
|
+
)
|
|
@@ -27,6 +27,7 @@ from invokeai.app.invocations.fields import (
|
|
|
27
27
|
SD3ConditioningField,
|
|
28
28
|
TensorField,
|
|
29
29
|
UIComponent,
|
|
30
|
+
ZImageConditioningField,
|
|
30
31
|
)
|
|
31
32
|
from invokeai.app.services.images.images_common import ImageDTO
|
|
32
33
|
from invokeai.app.services.shared.invocation_context import InvocationContext
|
|
@@ -461,6 +462,17 @@ class CogView4ConditioningOutput(BaseInvocationOutput):
|
|
|
461
462
|
return cls(conditioning=CogView4ConditioningField(conditioning_name=conditioning_name))
|
|
462
463
|
|
|
463
464
|
|
|
465
|
+
@invocation_output("z_image_conditioning_output")
|
|
466
|
+
class ZImageConditioningOutput(BaseInvocationOutput):
|
|
467
|
+
"""Base class for nodes that output a Z-Image text conditioning tensor."""
|
|
468
|
+
|
|
469
|
+
conditioning: ZImageConditioningField = OutputField(description=FieldDescriptions.cond)
|
|
470
|
+
|
|
471
|
+
@classmethod
|
|
472
|
+
def build(cls, conditioning_name: str) -> "ZImageConditioningOutput":
|
|
473
|
+
return cls(conditioning=ZImageConditioningField(conditioning_name=conditioning_name))
|
|
474
|
+
|
|
475
|
+
|
|
464
476
|
@invocation_output("conditioning_output")
|
|
465
477
|
class ConditioningOutput(BaseInvocationOutput):
|
|
466
478
|
"""Base class for nodes that output a single conditioning tensor"""
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
from invokeai.app.invocations.baseinvocation import BaseInvocation, BaseInvocationOutput, invocation, invocation_output
|
|
2
|
+
from invokeai.app.invocations.fields import InputField, OutputField, StylePresetField, UIComponent
|
|
3
|
+
from invokeai.app.services.shared.invocation_context import InvocationContext
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@invocation_output("prompt_template_output")
|
|
7
|
+
class PromptTemplateOutput(BaseInvocationOutput):
|
|
8
|
+
"""Output for the Prompt Template node"""
|
|
9
|
+
|
|
10
|
+
positive_prompt: str = OutputField(description="The positive prompt with the template applied")
|
|
11
|
+
negative_prompt: str = OutputField(description="The negative prompt with the template applied")
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@invocation(
|
|
15
|
+
"prompt_template",
|
|
16
|
+
title="Prompt Template",
|
|
17
|
+
tags=["prompt", "template", "style", "preset"],
|
|
18
|
+
category="prompt",
|
|
19
|
+
version="1.0.0",
|
|
20
|
+
)
|
|
21
|
+
class PromptTemplateInvocation(BaseInvocation):
|
|
22
|
+
"""Applies a Style Preset template to positive and negative prompts.
|
|
23
|
+
|
|
24
|
+
Select a Style Preset and provide positive/negative prompts. The node replaces
|
|
25
|
+
{prompt} placeholders in the template with your input prompts.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
style_preset: StylePresetField = InputField(
|
|
29
|
+
description="The Style Preset to use as a template",
|
|
30
|
+
)
|
|
31
|
+
positive_prompt: str = InputField(
|
|
32
|
+
default="",
|
|
33
|
+
description="The positive prompt to insert into the template's {prompt} placeholder",
|
|
34
|
+
ui_component=UIComponent.Textarea,
|
|
35
|
+
)
|
|
36
|
+
negative_prompt: str = InputField(
|
|
37
|
+
default="",
|
|
38
|
+
description="The negative prompt to insert into the template's {prompt} placeholder",
|
|
39
|
+
ui_component=UIComponent.Textarea,
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
def invoke(self, context: InvocationContext) -> PromptTemplateOutput:
|
|
43
|
+
# Fetch the style preset from the database
|
|
44
|
+
style_preset = context._services.style_preset_records.get(self.style_preset.style_preset_id)
|
|
45
|
+
|
|
46
|
+
# Get the template prompts
|
|
47
|
+
positive_template = style_preset.preset_data.positive_prompt
|
|
48
|
+
negative_template = style_preset.preset_data.negative_prompt
|
|
49
|
+
|
|
50
|
+
# Replace {prompt} placeholder with the input prompts
|
|
51
|
+
rendered_positive = positive_template.replace("{prompt}", self.positive_prompt)
|
|
52
|
+
rendered_negative = negative_template.replace("{prompt}", self.negative_prompt)
|
|
53
|
+
|
|
54
|
+
return PromptTemplateOutput(
|
|
55
|
+
positive_prompt=rendered_positive,
|
|
56
|
+
negative_prompt=rendered_negative,
|
|
57
|
+
)
|