InvokeAI 6.10.0rc1__py3-none-any.whl → 6.11.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/routers/model_manager.py +43 -1
- invokeai/app/invocations/fields.py +1 -1
- invokeai/app/invocations/flux2_denoise.py +499 -0
- invokeai/app/invocations/flux2_klein_model_loader.py +222 -0
- invokeai/app/invocations/flux2_klein_text_encoder.py +222 -0
- invokeai/app/invocations/flux2_vae_decode.py +106 -0
- invokeai/app/invocations/flux2_vae_encode.py +88 -0
- invokeai/app/invocations/flux_denoise.py +77 -3
- invokeai/app/invocations/flux_lora_loader.py +1 -1
- invokeai/app/invocations/flux_model_loader.py +2 -5
- invokeai/app/invocations/ideal_size.py +6 -1
- invokeai/app/invocations/metadata.py +4 -0
- invokeai/app/invocations/metadata_linked.py +47 -0
- invokeai/app/invocations/model.py +1 -0
- invokeai/app/invocations/pbr_maps.py +59 -0
- invokeai/app/invocations/z_image_denoise.py +244 -84
- invokeai/app/invocations/z_image_image_to_latents.py +9 -1
- invokeai/app/invocations/z_image_latents_to_image.py +9 -1
- invokeai/app/invocations/z_image_seed_variance_enhancer.py +110 -0
- invokeai/app/services/config/config_default.py +3 -1
- invokeai/app/services/invocation_stats/invocation_stats_common.py +6 -6
- invokeai/app/services/invocation_stats/invocation_stats_default.py +9 -4
- invokeai/app/services/model_manager/model_manager_default.py +7 -0
- invokeai/app/services/model_records/model_records_base.py +4 -2
- invokeai/app/services/shared/invocation_context.py +15 -0
- invokeai/app/services/shared/sqlite/sqlite_util.py +2 -0
- invokeai/app/services/shared/sqlite_migrator/migrations/migration_25.py +61 -0
- invokeai/app/util/step_callback.py +58 -2
- invokeai/backend/flux/denoise.py +338 -118
- invokeai/backend/flux/dype/__init__.py +31 -0
- invokeai/backend/flux/dype/base.py +260 -0
- invokeai/backend/flux/dype/embed.py +116 -0
- invokeai/backend/flux/dype/presets.py +148 -0
- invokeai/backend/flux/dype/rope.py +110 -0
- invokeai/backend/flux/extensions/dype_extension.py +91 -0
- invokeai/backend/flux/schedulers.py +62 -0
- invokeai/backend/flux/util.py +35 -1
- invokeai/backend/flux2/__init__.py +4 -0
- invokeai/backend/flux2/denoise.py +280 -0
- invokeai/backend/flux2/ref_image_extension.py +294 -0
- invokeai/backend/flux2/sampling_utils.py +209 -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/factory.py +19 -1
- invokeai/backend/model_manager/configs/lora.py +36 -0
- invokeai/backend/model_manager/configs/main.py +395 -3
- invokeai/backend/model_manager/configs/qwen3_encoder.py +116 -7
- invokeai/backend/model_manager/configs/vae.py +104 -2
- invokeai/backend/model_manager/load/model_cache/model_cache.py +107 -2
- invokeai/backend/model_manager/load/model_loaders/cogview4.py +2 -1
- invokeai/backend/model_manager/load/model_loaders/flux.py +1020 -8
- invokeai/backend/model_manager/load/model_loaders/generic_diffusers.py +4 -2
- 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 +158 -31
- invokeai/backend/model_manager/starter_models.py +141 -4
- invokeai/backend/model_manager/taxonomy.py +31 -4
- invokeai/backend/model_manager/util/select_hf_files.py +3 -2
- invokeai/backend/patches/lora_conversions/z_image_lora_conversion_utils.py +39 -5
- invokeai/backend/quantization/gguf/ggml_tensor.py +15 -4
- invokeai/backend/util/vae_working_memory.py +0 -2
- invokeai/backend/z_image/extensions/regional_prompting_extension.py +10 -12
- invokeai/frontend/web/dist/assets/App-D13dX7be.js +161 -0
- invokeai/frontend/web/dist/assets/{browser-ponyfill-DHZxq1nk.js → browser-ponyfill-u_ZjhQTI.js} +1 -1
- invokeai/frontend/web/dist/assets/index-BB0nHmDe.js +530 -0
- invokeai/frontend/web/dist/index.html +1 -1
- invokeai/frontend/web/dist/locales/en-GB.json +1 -0
- invokeai/frontend/web/dist/locales/en.json +85 -6
- invokeai/frontend/web/dist/locales/it.json +135 -15
- invokeai/frontend/web/dist/locales/ru.json +11 -11
- invokeai/version/invokeai_version.py +1 -1
- {invokeai-6.10.0rc1.dist-info → invokeai-6.11.0.dist-info}/METADATA +8 -2
- {invokeai-6.10.0rc1.dist-info → invokeai-6.11.0.dist-info}/RECORD +81 -57
- {invokeai-6.10.0rc1.dist-info → invokeai-6.11.0.dist-info}/WHEEL +1 -1
- invokeai/frontend/web/dist/assets/App-CYhlZO3Q.js +0 -161
- invokeai/frontend/web/dist/assets/index-dgSJAY--.js +0 -530
- {invokeai-6.10.0rc1.dist-info → invokeai-6.11.0.dist-info}/entry_points.txt +0 -0
- {invokeai-6.10.0rc1.dist-info → invokeai-6.11.0.dist-info}/licenses/LICENSE +0 -0
- {invokeai-6.10.0rc1.dist-info → invokeai-6.11.0.dist-info}/licenses/LICENSE-SD1+SD2.txt +0 -0
- {invokeai-6.10.0rc1.dist-info → invokeai-6.11.0.dist-info}/licenses/LICENSE-SDXL.txt +0 -0
- {invokeai-6.10.0rc1.dist-info → invokeai-6.11.0.dist-info}/top_level.txt +0 -0
|
@@ -32,6 +32,13 @@ from invokeai.app.services.shared.invocation_context import InvocationContext
|
|
|
32
32
|
from invokeai.backend.flux.controlnet.instantx_controlnet_flux import InstantXControlNetFlux
|
|
33
33
|
from invokeai.backend.flux.controlnet.xlabs_controlnet_flux import XLabsControlNetFlux
|
|
34
34
|
from invokeai.backend.flux.denoise import denoise
|
|
35
|
+
from invokeai.backend.flux.dype.presets import (
|
|
36
|
+
DYPE_PRESET_LABELS,
|
|
37
|
+
DYPE_PRESET_OFF,
|
|
38
|
+
DyPEPreset,
|
|
39
|
+
get_dype_config_from_preset,
|
|
40
|
+
)
|
|
41
|
+
from invokeai.backend.flux.extensions.dype_extension import DyPEExtension
|
|
35
42
|
from invokeai.backend.flux.extensions.instantx_controlnet_extension import InstantXControlNetExtension
|
|
36
43
|
from invokeai.backend.flux.extensions.kontext_extension import KontextExtension
|
|
37
44
|
from invokeai.backend.flux.extensions.regional_prompting_extension import RegionalPromptingExtension
|
|
@@ -47,6 +54,7 @@ from invokeai.backend.flux.sampling_utils import (
|
|
|
47
54
|
pack,
|
|
48
55
|
unpack,
|
|
49
56
|
)
|
|
57
|
+
from invokeai.backend.flux.schedulers import FLUX_SCHEDULER_LABELS, FLUX_SCHEDULER_MAP, FLUX_SCHEDULER_NAME_VALUES
|
|
50
58
|
from invokeai.backend.flux.text_conditioning import FluxReduxConditioning, FluxTextConditioning
|
|
51
59
|
from invokeai.backend.model_manager.taxonomy import BaseModelType, FluxVariantType, ModelFormat, ModelType
|
|
52
60
|
from invokeai.backend.patches.layer_patcher import LayerPatcher
|
|
@@ -63,7 +71,7 @@ from invokeai.backend.util.devices import TorchDevice
|
|
|
63
71
|
title="FLUX Denoise",
|
|
64
72
|
tags=["image", "flux"],
|
|
65
73
|
category="image",
|
|
66
|
-
version="4.
|
|
74
|
+
version="4.5.0",
|
|
67
75
|
)
|
|
68
76
|
class FluxDenoiseInvocation(BaseInvocation):
|
|
69
77
|
"""Run denoising process with a FLUX transformer model."""
|
|
@@ -132,6 +140,12 @@ class FluxDenoiseInvocation(BaseInvocation):
|
|
|
132
140
|
num_steps: int = InputField(
|
|
133
141
|
default=4, description="Number of diffusion steps. Recommended values are schnell: 4, dev: 50."
|
|
134
142
|
)
|
|
143
|
+
scheduler: FLUX_SCHEDULER_NAME_VALUES = InputField(
|
|
144
|
+
default="euler",
|
|
145
|
+
description="Scheduler (sampler) for the denoising process. 'euler' is fast and standard. "
|
|
146
|
+
"'heun' is 2nd-order (better quality, 2x slower). 'lcm' is optimized for few steps.",
|
|
147
|
+
ui_choice_labels=FLUX_SCHEDULER_LABELS,
|
|
148
|
+
)
|
|
135
149
|
guidance: float = InputField(
|
|
136
150
|
default=4.0,
|
|
137
151
|
description="The guidance strength. Higher values adhere more strictly to the prompt, and will produce less diverse images. FLUX dev only, ignored for schnell.",
|
|
@@ -159,6 +173,28 @@ class FluxDenoiseInvocation(BaseInvocation):
|
|
|
159
173
|
input=Input.Connection,
|
|
160
174
|
)
|
|
161
175
|
|
|
176
|
+
# DyPE (Dynamic Position Extrapolation) for high-resolution generation
|
|
177
|
+
dype_preset: DyPEPreset = InputField(
|
|
178
|
+
default=DYPE_PRESET_OFF,
|
|
179
|
+
description="DyPE preset for high-resolution generation. 'auto' enables automatically for resolutions > 1536px. '4k' uses optimized settings for 4K output.",
|
|
180
|
+
ui_order=100,
|
|
181
|
+
ui_choice_labels=DYPE_PRESET_LABELS,
|
|
182
|
+
)
|
|
183
|
+
dype_scale: Optional[float] = InputField(
|
|
184
|
+
default=None,
|
|
185
|
+
ge=0.0,
|
|
186
|
+
le=8.0,
|
|
187
|
+
description="DyPE magnitude (λs). Higher values = stronger extrapolation. Only used when dype_preset is not 'off'.",
|
|
188
|
+
ui_order=101,
|
|
189
|
+
)
|
|
190
|
+
dype_exponent: Optional[float] = InputField(
|
|
191
|
+
default=None,
|
|
192
|
+
ge=0.0,
|
|
193
|
+
le=1000.0,
|
|
194
|
+
description="DyPE decay speed (λt). Controls transition from low to high frequency detail. Only used when dype_preset is not 'off'.",
|
|
195
|
+
ui_order=102,
|
|
196
|
+
)
|
|
197
|
+
|
|
162
198
|
@torch.no_grad()
|
|
163
199
|
def invoke(self, context: InvocationContext) -> LatentsOutput:
|
|
164
200
|
latents = self._run_diffusion(context)
|
|
@@ -232,8 +268,14 @@ class FluxDenoiseInvocation(BaseInvocation):
|
|
|
232
268
|
)
|
|
233
269
|
|
|
234
270
|
transformer_config = context.models.get_config(self.transformer.transformer)
|
|
235
|
-
assert
|
|
236
|
-
|
|
271
|
+
assert (
|
|
272
|
+
transformer_config.base in (BaseModelType.Flux, BaseModelType.Flux2)
|
|
273
|
+
and transformer_config.type is ModelType.Main
|
|
274
|
+
)
|
|
275
|
+
# Schnell is only for FLUX.1, FLUX.2 Klein behaves like Dev (with guidance)
|
|
276
|
+
is_schnell = (
|
|
277
|
+
transformer_config.base is BaseModelType.Flux and transformer_config.variant is FluxVariantType.Schnell
|
|
278
|
+
)
|
|
237
279
|
|
|
238
280
|
# Calculate the timestep schedule.
|
|
239
281
|
timesteps = get_schedule(
|
|
@@ -242,6 +284,12 @@ class FluxDenoiseInvocation(BaseInvocation):
|
|
|
242
284
|
shift=not is_schnell,
|
|
243
285
|
)
|
|
244
286
|
|
|
287
|
+
# Create scheduler if not using default euler
|
|
288
|
+
scheduler = None
|
|
289
|
+
if self.scheduler in FLUX_SCHEDULER_MAP:
|
|
290
|
+
scheduler_class = FLUX_SCHEDULER_MAP[self.scheduler]
|
|
291
|
+
scheduler = scheduler_class(num_train_timesteps=1000)
|
|
292
|
+
|
|
245
293
|
# Clip the timesteps schedule based on denoising_start and denoising_end.
|
|
246
294
|
timesteps = clip_timestep_schedule_fractional(timesteps, self.denoising_start, self.denoising_end)
|
|
247
295
|
|
|
@@ -409,6 +457,30 @@ class FluxDenoiseInvocation(BaseInvocation):
|
|
|
409
457
|
kontext_extension.ensure_batch_size(x.shape[0])
|
|
410
458
|
img_cond_seq, img_cond_seq_ids = kontext_extension.kontext_latents, kontext_extension.kontext_ids
|
|
411
459
|
|
|
460
|
+
# Prepare DyPE extension for high-resolution generation
|
|
461
|
+
dype_extension: DyPEExtension | None = None
|
|
462
|
+
dype_config = get_dype_config_from_preset(
|
|
463
|
+
preset=self.dype_preset,
|
|
464
|
+
width=self.width,
|
|
465
|
+
height=self.height,
|
|
466
|
+
custom_scale=self.dype_scale,
|
|
467
|
+
custom_exponent=self.dype_exponent,
|
|
468
|
+
)
|
|
469
|
+
if dype_config is not None:
|
|
470
|
+
dype_extension = DyPEExtension(
|
|
471
|
+
config=dype_config,
|
|
472
|
+
target_height=self.height,
|
|
473
|
+
target_width=self.width,
|
|
474
|
+
)
|
|
475
|
+
context.logger.info(
|
|
476
|
+
f"DyPE enabled: resolution={self.width}x{self.height}, preset={self.dype_preset}, "
|
|
477
|
+
f"method={dype_config.method}, scale={dype_config.dype_scale:.2f}, "
|
|
478
|
+
f"exponent={dype_config.dype_exponent:.2f}, start_sigma={dype_config.dype_start_sigma:.2f}, "
|
|
479
|
+
f"base_resolution={dype_config.base_resolution}"
|
|
480
|
+
)
|
|
481
|
+
else:
|
|
482
|
+
context.logger.debug(f"DyPE disabled: resolution={self.width}x{self.height}, preset={self.dype_preset}")
|
|
483
|
+
|
|
412
484
|
x = denoise(
|
|
413
485
|
model=transformer,
|
|
414
486
|
img=x,
|
|
@@ -426,6 +498,8 @@ class FluxDenoiseInvocation(BaseInvocation):
|
|
|
426
498
|
img_cond=img_cond,
|
|
427
499
|
img_cond_seq=img_cond_seq,
|
|
428
500
|
img_cond_seq_ids=img_cond_seq_ids,
|
|
501
|
+
dype_extension=dype_extension,
|
|
502
|
+
scheduler=scheduler,
|
|
429
503
|
)
|
|
430
504
|
|
|
431
505
|
x = unpack(x.float(), self.height, self.width)
|
|
@@ -162,7 +162,7 @@ class FLUXLoRACollectionLoader(BaseInvocation):
|
|
|
162
162
|
if not context.models.exists(lora.lora.key):
|
|
163
163
|
raise Exception(f"Unknown lora: {lora.lora.key}!")
|
|
164
164
|
|
|
165
|
-
assert lora.lora.base
|
|
165
|
+
assert lora.lora.base in (BaseModelType.Flux, BaseModelType.Flux2)
|
|
166
166
|
|
|
167
167
|
added_loras.append(lora.lora.key)
|
|
168
168
|
|
|
@@ -6,7 +6,7 @@ from invokeai.app.invocations.baseinvocation import (
|
|
|
6
6
|
invocation,
|
|
7
7
|
invocation_output,
|
|
8
8
|
)
|
|
9
|
-
from invokeai.app.invocations.fields import FieldDescriptions,
|
|
9
|
+
from invokeai.app.invocations.fields import FieldDescriptions, InputField, OutputField
|
|
10
10
|
from invokeai.app.invocations.model import CLIPField, ModelIdentifierField, T5EncoderField, TransformerField, VAEField
|
|
11
11
|
from invokeai.app.services.shared.invocation_context import InvocationContext
|
|
12
12
|
from invokeai.app.util.t5_model_identifier import (
|
|
@@ -37,28 +37,25 @@ class FluxModelLoaderOutput(BaseInvocationOutput):
|
|
|
37
37
|
title="Main Model - FLUX",
|
|
38
38
|
tags=["model", "flux"],
|
|
39
39
|
category="model",
|
|
40
|
-
version="1.0.
|
|
40
|
+
version="1.0.7",
|
|
41
41
|
)
|
|
42
42
|
class FluxModelLoaderInvocation(BaseInvocation):
|
|
43
43
|
"""Loads a flux base model, outputting its submodels."""
|
|
44
44
|
|
|
45
45
|
model: ModelIdentifierField = InputField(
|
|
46
46
|
description=FieldDescriptions.flux_model,
|
|
47
|
-
input=Input.Direct,
|
|
48
47
|
ui_model_base=BaseModelType.Flux,
|
|
49
48
|
ui_model_type=ModelType.Main,
|
|
50
49
|
)
|
|
51
50
|
|
|
52
51
|
t5_encoder_model: ModelIdentifierField = InputField(
|
|
53
52
|
description=FieldDescriptions.t5_encoder,
|
|
54
|
-
input=Input.Direct,
|
|
55
53
|
title="T5 Encoder",
|
|
56
54
|
ui_model_type=ModelType.T5Encoder,
|
|
57
55
|
)
|
|
58
56
|
|
|
59
57
|
clip_embed_model: ModelIdentifierField = InputField(
|
|
60
58
|
description=FieldDescriptions.clip_embed_model,
|
|
61
|
-
input=Input.Direct,
|
|
62
59
|
title="CLIP Embed",
|
|
63
60
|
ui_model_type=ModelType.CLIPEmbed,
|
|
64
61
|
)
|
|
@@ -46,7 +46,12 @@ class IdealSizeInvocation(BaseInvocation):
|
|
|
46
46
|
dimension = 512
|
|
47
47
|
elif unet_config.base == BaseModelType.StableDiffusion2:
|
|
48
48
|
dimension = 768
|
|
49
|
-
elif unet_config.base in (
|
|
49
|
+
elif unet_config.base in (
|
|
50
|
+
BaseModelType.StableDiffusionXL,
|
|
51
|
+
BaseModelType.Flux,
|
|
52
|
+
BaseModelType.Flux2,
|
|
53
|
+
BaseModelType.StableDiffusion3,
|
|
54
|
+
):
|
|
50
55
|
dimension = 1024
|
|
51
56
|
else:
|
|
52
57
|
raise ValueError(f"Unsupported model type: {unet_config.base}")
|
|
@@ -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",
|
|
@@ -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
|
+
)
|