Publish Canter 0.5.0 contrastive PDG support
Browse files- API.md +15 -8
- README.md +5 -2
- RELEASES.md +5 -0
- TECHNICAL_REPORT.md +5 -3
- canter/__init__.py +4 -0
- canter/inference.py +39 -12
- canter/version.py +1 -1
- canter/webui_app.py +36 -2
- canter/webui_runtime.py +65 -1
- dist/canter-0.5.0-py3-none-any.whl +0 -0
API.md
CHANGED
|
@@ -26,7 +26,7 @@ image = output.image
|
|
| 26 |
| Parameter | Default | Description |
|
| 27 |
| --- | --- | --- |
|
| 28 |
| `prompts` | required | One prompt string or a sequence containing one prompt per image. |
|
| 29 |
-
| `negative_prompts` | `None` | Optional
|
| 30 |
| `config` | `CanterPipelineConfig()` | Inference and output settings. |
|
| 31 |
| `initial_noise` | `None` | Optional float32 latent noise tensor with the configured batch and spatial shape. |
|
| 32 |
| `progress` | `None` | Optional callback receiving completed and total solver updates. |
|
|
@@ -212,8 +212,9 @@ operations. The VAE decoder retains its published seed behavior.
|
|
| 212 |
### PNG metadata
|
| 213 |
|
| 214 |
Images downloaded from the Gradio interface contain a `canter` PNG text field
|
| 215 |
-
with compact JSON. The object begins with the prompt,
|
| 216 |
-
width, height, steps, solver, and schedule. It then
|
|
|
|
| 217 |
self-attention gain, logSNR shift, the Euler-Maruyama and ER-SDE noise
|
| 218 |
multipliers, installed code version, numbered checkpoint release, and weight
|
| 219 |
dtype.
|
|
@@ -287,8 +288,9 @@ output = pipe(
|
|
| 287 |
Step indices run from `0` through `steps - 1`.
|
| 288 |
|
| 289 |
An explicit negative prompt replaces the learned unconditional token on CFG
|
| 290 |
-
branches. Negative prompts require enabled CFG or a
|
| 291 |
-
For prompt batches, supply one negative
|
|
|
|
| 292 |
|
| 293 |
## PDG
|
| 294 |
|
|
@@ -342,6 +344,7 @@ contrast, and oversaturation.
|
|
| 342 |
| --- | --- | --- |
|
| 343 |
| `PdgMode.NONE` | `"none"` | No PDG branch. Required when PDG is disabled. |
|
| 344 |
| `PdgMode.FULL` | `"full"` | Guides from the middle-skipped path toward the full main path. |
|
|
|
|
| 345 |
| `PdgMode.THREE_QUARTER` | `"three_quarter"` | Guides from the 75 percent SPRINT path toward the full main path. |
|
| 346 |
| `PdgMode.ALTERNATE_PDG_FIRST` | `"alternate_pdg_first"` | Alternates PDG on even updates and CFG on odd updates within the PDG window. |
|
| 347 |
| `PdgMode.ALTERNATE_CFG_FIRST` | `"alternate_cfg_first"` | Alternates CFG on even updates and PDG on odd updates within the PDG window. |
|
|
@@ -349,8 +352,12 @@ contrast, and oversaturation.
|
|
| 349 |
| `PdgMode.PDG_WITH_ALTERNATING_CFG` | `"pdg_with_alternating_cfg"` | Applies PDG on every active update and adds CFG on odd updates. |
|
| 350 |
| `PdgMode.CFG_TO_PDG` | `"cfg_to_pdg"` | Uses CFG before the PDG window, then uses full-path PDG inside the window. |
|
| 351 |
|
| 352 |
-
The five compound modes require `cfg.scale`. `
|
| 353 |
-
`PdgMode.CFG_TO_PDG` begins directly
|
|
|
|
|
|
|
|
|
|
|
|
|
| 354 |
|
| 355 |
## Self-attention gain
|
| 356 |
|
|
@@ -457,7 +464,7 @@ schedule = output.schedule
|
|
| 457 |
| Parameter | Default | Description |
|
| 458 |
| --- | --- | --- |
|
| 459 |
| `prompts` | required | One string or a sequence of strings. |
|
| 460 |
-
| `negative_prompts` | `None` | Optional negative prompt string or sequence for CFG branches. The count must match `prompts`. |
|
| 461 |
| `config` | `CanterInferenceConfig()` | Latent inference settings. |
|
| 462 |
| `initial_noise` | `None` | Optional float32 noise with shape `[batch, 128, height / 16, width / 16]`. |
|
| 463 |
| `progress` | `None` | Optional callback receiving `(completed_updates, total_updates)`. |
|
|
|
|
| 26 |
| Parameter | Default | Description |
|
| 27 |
| --- | --- | --- |
|
| 28 |
| `prompts` | required | One prompt string or a sequence containing one prompt per image. |
|
| 29 |
+
| `negative_prompts` | `None` | Optional negative prompt string or sequence for CFG and contrastive PDG. The count must match `prompts`. `None` uses the learned unconditional token. |
|
| 30 |
| `config` | `CanterPipelineConfig()` | Inference and output settings. |
|
| 31 |
| `initial_noise` | `None` | Optional float32 latent noise tensor with the configured batch and spatial shape. |
|
| 32 |
| `progress` | `None` | Optional callback receiving completed and total solver updates. |
|
|
|
|
| 212 |
### PNG metadata
|
| 213 |
|
| 214 |
Images downloaded from the Gradio interface contain a `canter` PNG text field
|
| 215 |
+
with compact JSON. The object begins with the prompt, optional negative prompt,
|
| 216 |
+
effective per-image seed, width, height, steps, solver, and schedule. It then
|
| 217 |
+
records PDG, CFG,
|
| 218 |
self-attention gain, logSNR shift, the Euler-Maruyama and ER-SDE noise
|
| 219 |
multipliers, installed code version, numbered checkpoint release, and weight
|
| 220 |
dtype.
|
|
|
|
| 288 |
Step indices run from `0` through `steps - 1`.
|
| 289 |
|
| 290 |
An explicit negative prompt replaces the learned unconditional token on CFG
|
| 291 |
+
and contrastive PDG branches. Negative prompts require enabled CFG or a PDG
|
| 292 |
+
mode that consumes contrastive text. For prompt batches, supply one negative
|
| 293 |
+
prompt per positive prompt.
|
| 294 |
|
| 295 |
## PDG
|
| 296 |
|
|
|
|
| 344 |
| --- | --- | --- |
|
| 345 |
| `PdgMode.NONE` | `"none"` | No PDG branch. Required when PDG is disabled. |
|
| 346 |
| `PdgMode.FULL` | `"full"` | Guides from the middle-skipped path toward the full main path. |
|
| 347 |
+
| `PdgMode.FULL_CONTRASTIVE` | `"full_contrastive"` | Uses negative text on the middle-skipped path, or learned unconditional text when no negative prompt is supplied. |
|
| 348 |
| `PdgMode.THREE_QUARTER` | `"three_quarter"` | Guides from the 75 percent SPRINT path toward the full main path. |
|
| 349 |
| `PdgMode.ALTERNATE_PDG_FIRST` | `"alternate_pdg_first"` | Alternates PDG on even updates and CFG on odd updates within the PDG window. |
|
| 350 |
| `PdgMode.ALTERNATE_CFG_FIRST` | `"alternate_cfg_first"` | Alternates CFG on even updates and PDG on odd updates within the PDG window. |
|
|
|
|
| 352 |
| `PdgMode.PDG_WITH_ALTERNATING_CFG` | `"pdg_with_alternating_cfg"` | Applies PDG on every active update and adds CFG on odd updates. |
|
| 353 |
| `PdgMode.CFG_TO_PDG` | `"cfg_to_pdg"` | Uses CFG before the PDG window, then uses full-path PDG inside the window. |
|
| 354 |
|
| 355 |
+
The five compound modes require `cfg.scale`. `PdgMode.FULL_CONTRASTIVE` uses
|
| 356 |
+
the PDG scale only. `start_step=0` with `PdgMode.CFG_TO_PDG` begins directly
|
| 357 |
+
with PDG.
|
| 358 |
+
|
| 359 |
+
`pdg_branch_conditioning(mode)` returns `PdgBranchConditioning.POSITIVE` or
|
| 360 |
+
`PdgBranchConditioning.CONTRASTIVE` for the selected PDG alternative path.
|
| 361 |
|
| 362 |
## Self-attention gain
|
| 363 |
|
|
|
|
| 464 |
| Parameter | Default | Description |
|
| 465 |
| --- | --- | --- |
|
| 466 |
| `prompts` | required | One string or a sequence of strings. |
|
| 467 |
+
| `negative_prompts` | `None` | Optional negative prompt string or sequence for CFG and contrastive PDG branches. The count must match `prompts`; blank text uses learned unconditional conditioning. |
|
| 468 |
| `config` | `CanterInferenceConfig()` | Latent inference settings. |
|
| 469 |
| `initial_noise` | `None` | Optional float32 noise with shape `[batch, 128, height / 16, width / 16]`. |
|
| 470 |
| `progress` | `None` | Optional callback receiving `(completed_updates, total_updates)`. |
|
README.md
CHANGED
|
@@ -111,8 +111,11 @@ bundled one-eighth-scale latent-RGB projection. Busy preview work is skipped,
|
|
| 111 |
so the sampler never waits for browser publication. The native one-eighth-size
|
| 112 |
preview is sent directly and scaled for display by the browser. The UI checkbox
|
| 113 |
below the size preset disables previews.
|
| 114 |
-
|
| 115 |
-
|
|
|
|
|
|
|
|
|
|
| 116 |
|
| 117 |
The server listens on port 7860. To select the bind address explicitly:
|
| 118 |
|
|
|
|
| 111 |
so the sampler never waits for browser publication. The native one-eighth-size
|
| 112 |
preview is sent directly and scaled for display by the browser. The UI checkbox
|
| 113 |
below the size preset disables previews.
|
| 114 |
+
The negative prompt field appears when CFG or a contrastive-text PDG mode is
|
| 115 |
+
active. Blank negative text uses Canter's learned unconditional conditioning.
|
| 116 |
+
Downloaded PNG files contain the prompt, optional negative prompt, effective
|
| 117 |
+
per-image settings, Canter code version, and numbered checkpoint release as
|
| 118 |
+
JSON metadata.
|
| 119 |
|
| 120 |
The server listens on port 7860. To select the bind address explicitly:
|
| 121 |
|
RELEASES.md
CHANGED
|
@@ -10,6 +10,11 @@ The current package also supplies the small latent-RGB preview projection;
|
|
| 10 |
preview behavior therefore follows the installed code rather than changing an
|
| 11 |
older denoiser checkpoint tag.
|
| 12 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 13 |
Canter `0.4.1` makes the dense backend apply to the bundled text encoder as
|
| 14 |
well as denoiser text attention. Dense text encoding now uses padded PyTorch
|
| 15 |
scaled dot-product attention and does not require the explicit CUDA
|
|
|
|
| 10 |
preview behavior therefore follows the installed code rather than changing an
|
| 11 |
older denoiser checkpoint tag.
|
| 12 |
|
| 13 |
+
Canter `0.5.0` adds contrastive PDG. Its middle-skipped guidance path uses an
|
| 14 |
+
explicit negative prompt when supplied and learned unconditional text when the
|
| 15 |
+
negative prompt is blank or absent. The Python API, Gradio interface, and
|
| 16 |
+
ComfyUI nodes use the same branch-selection rule.
|
| 17 |
+
|
| 18 |
Canter `0.4.1` makes the dense backend apply to the bundled text encoder as
|
| 19 |
well as denoiser text attention. Dense text encoding now uses padded PyTorch
|
| 20 |
scaled dot-product attention and does not require the explicit CUDA
|
TECHNICAL_REPORT.md
CHANGED
|
@@ -424,9 +424,11 @@ At resolutions around the \\(1024^2\\) aspect-ratio buckets, a PDG scale near 2.
|
|
| 424 |
and a self-attention gain near -0.03 gives substantially better results than
|
| 425 |
reducing PDG while leaving the gain at zero.
|
| 426 |
|
| 427 |
-
PDG
|
| 428 |
-
|
| 429 |
-
|
|
|
|
|
|
|
| 430 |
See [API and inference parameters](API.md) for the complete interface.
|
| 431 |
|
| 432 |
## Dataset
|
|
|
|
| 424 |
and a self-attention gain near -0.03 gives substantially better results than
|
| 425 |
reducing PDG while leaving the gain at zero.
|
| 426 |
|
| 427 |
+
Contrastive PDG conditions the middle-skipped path with either a negative
|
| 428 |
+
prompt or Canter's learned unconditional text. PDG can also be combined with
|
| 429 |
+
CFG. The release provides several CFG/PDG interaction modes, constant, linear,
|
| 430 |
+
and power PDG curves, and independent start and stop steps for both guidance
|
| 431 |
+
methods.
|
| 432 |
See [API and inference parameters](API.md) for the complete interface.
|
| 433 |
|
| 434 |
## Dataset
|
canter/__init__.py
CHANGED
|
@@ -16,9 +16,11 @@ from .inference import (
|
|
| 16 |
CanterLatentOutput,
|
| 17 |
CanterVelocityModel,
|
| 18 |
CfgGuidance,
|
|
|
|
| 19 |
PdgCurve,
|
| 20 |
PdgGuidance,
|
| 21 |
PdgMode,
|
|
|
|
| 22 |
)
|
| 23 |
from .loading import (
|
| 24 |
CanterComponents,
|
|
@@ -79,6 +81,7 @@ __all__ = [
|
|
| 79 |
"CfgGuidance",
|
| 80 |
"LatentRgbPreviewer",
|
| 81 |
"LatentRgbProjection",
|
|
|
|
| 82 |
"PdgCurve",
|
| 83 |
"PdgGuidance",
|
| 84 |
"PdgMode",
|
|
@@ -92,5 +95,6 @@ __all__ = [
|
|
| 92 |
"WeightDType",
|
| 93 |
"__version__",
|
| 94 |
"build_schedule",
|
|
|
|
| 95 |
"solve",
|
| 96 |
]
|
|
|
|
| 16 |
CanterLatentOutput,
|
| 17 |
CanterVelocityModel,
|
| 18 |
CfgGuidance,
|
| 19 |
+
PdgBranchConditioning,
|
| 20 |
PdgCurve,
|
| 21 |
PdgGuidance,
|
| 22 |
PdgMode,
|
| 23 |
+
pdg_branch_conditioning,
|
| 24 |
)
|
| 25 |
from .loading import (
|
| 26 |
CanterComponents,
|
|
|
|
| 81 |
"CfgGuidance",
|
| 82 |
"LatentRgbPreviewer",
|
| 83 |
"LatentRgbProjection",
|
| 84 |
+
"PdgBranchConditioning",
|
| 85 |
"PdgCurve",
|
| 86 |
"PdgGuidance",
|
| 87 |
"PdgMode",
|
|
|
|
| 95 |
"WeightDType",
|
| 96 |
"__version__",
|
| 97 |
"build_schedule",
|
| 98 |
+
"pdg_branch_conditioning",
|
| 99 |
"solve",
|
| 100 |
]
|
canter/inference.py
CHANGED
|
@@ -42,6 +42,7 @@ class PdgMode(Enum):
|
|
| 42 |
|
| 43 |
NONE = "none"
|
| 44 |
FULL = "full"
|
|
|
|
| 45 |
THREE_QUARTER = "three_quarter"
|
| 46 |
ALTERNATE_PDG_FIRST = "alternate_pdg_first"
|
| 47 |
ALTERNATE_CFG_FIRST = "alternate_cfg_first"
|
|
@@ -308,25 +309,38 @@ def _pdg_uses_cfg(mode: PdgMode) -> bool:
|
|
| 308 |
| PdgMode.CFG_TO_PDG
|
| 309 |
):
|
| 310 |
return True
|
| 311 |
-
case
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 312 |
return False
|
| 313 |
case _ as unreachable:
|
| 314 |
raise RuntimeError(f"Unsupported PDG mode: {unreachable}")
|
| 315 |
|
| 316 |
|
| 317 |
-
|
| 318 |
-
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 319 |
|
| 320 |
match mode:
|
| 321 |
case (
|
| 322 |
-
PdgMode.
|
|
|
|
| 323 |
| PdgMode.ALTERNATE_CFG_FIRST
|
| 324 |
| PdgMode.COMBINED_CFG_PDG
|
| 325 |
| PdgMode.PDG_WITH_ALTERNATING_CFG
|
| 326 |
):
|
| 327 |
-
return
|
| 328 |
case PdgMode.NONE | PdgMode.FULL | PdgMode.THREE_QUARTER | PdgMode.CFG_TO_PDG:
|
| 329 |
-
return
|
| 330 |
case _ as unreachable:
|
| 331 |
raise RuntimeError(f"Unsupported PDG mode: {unreachable}")
|
| 332 |
|
|
@@ -406,7 +420,8 @@ class CanterGuidedVelocity:
|
|
| 406 |
if active.pdg:
|
| 407 |
tweak_text = (
|
| 408 |
self._unconditional()
|
| 409 |
-
if
|
|
|
|
| 410 |
else self.conditional
|
| 411 |
)
|
| 412 |
tweak = self._predict(
|
|
@@ -433,7 +448,12 @@ class CanterGuidedVelocity:
|
|
| 433 |
pdg_active = self.config.pdg.enabled and _inside(step, self.pdg_window)
|
| 434 |
mode = self.config.pdg.mode
|
| 435 |
match mode:
|
| 436 |
-
case
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 437 |
return _GuidanceStep(cfg_active, pdg_active, mode)
|
| 438 |
case PdgMode.ALTERNATE_PDG_FIRST:
|
| 439 |
if pdg_active:
|
|
@@ -520,7 +540,7 @@ class CanterGuidedVelocity:
|
|
| 520 |
|
| 521 |
if self.unconditional is None:
|
| 522 |
raise RuntimeError(
|
| 523 |
-
"An active
|
| 524 |
)
|
| 525 |
return self.unconditional
|
| 526 |
|
|
@@ -529,7 +549,7 @@ class CanterGuidedVelocity:
|
|
| 529 |
"""Map a concrete public PDG mode to one frozen model path."""
|
| 530 |
|
| 531 |
match mode:
|
| 532 |
-
case PdgMode.FULL:
|
| 533 |
return CanterPath.SKIP_MIDDLE
|
| 534 |
case PdgMode.THREE_QUARTER:
|
| 535 |
return CanterPath.THREE_QUARTER
|
|
@@ -549,7 +569,12 @@ def _needs_unconditional_text(config: CanterGuidanceConfig) -> bool:
|
|
| 549 |
"""Return whether any configured inference step can use unconditional text."""
|
| 550 |
|
| 551 |
return bool(config.cfg.enabled) or (
|
| 552 |
-
bool(config.pdg.enabled)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 553 |
)
|
| 554 |
|
| 555 |
|
|
@@ -793,5 +818,7 @@ def _negative_prompt_batch(
|
|
| 793 |
if _prompt_batch_is_blank(values):
|
| 794 |
return None
|
| 795 |
if not required:
|
| 796 |
-
raise ValueError(
|
|
|
|
|
|
|
| 797 |
return values
|
|
|
|
| 42 |
|
| 43 |
NONE = "none"
|
| 44 |
FULL = "full"
|
| 45 |
+
FULL_CONTRASTIVE = "full_contrastive"
|
| 46 |
THREE_QUARTER = "three_quarter"
|
| 47 |
ALTERNATE_PDG_FIRST = "alternate_pdg_first"
|
| 48 |
ALTERNATE_CFG_FIRST = "alternate_cfg_first"
|
|
|
|
| 309 |
| PdgMode.CFG_TO_PDG
|
| 310 |
):
|
| 311 |
return True
|
| 312 |
+
case (
|
| 313 |
+
PdgMode.NONE
|
| 314 |
+
| PdgMode.FULL
|
| 315 |
+
| PdgMode.FULL_CONTRASTIVE
|
| 316 |
+
| PdgMode.THREE_QUARTER
|
| 317 |
+
):
|
| 318 |
return False
|
| 319 |
case _ as unreachable:
|
| 320 |
raise RuntimeError(f"Unsupported PDG mode: {unreachable}")
|
| 321 |
|
| 322 |
|
| 323 |
+
class PdgBranchConditioning(Enum):
|
| 324 |
+
"""Text conditioning supplied to an active PDG alternative path."""
|
| 325 |
+
|
| 326 |
+
POSITIVE = "positive"
|
| 327 |
+
CONTRASTIVE = "contrastive"
|
| 328 |
+
|
| 329 |
+
|
| 330 |
+
def pdg_branch_conditioning(mode: PdgMode) -> PdgBranchConditioning:
|
| 331 |
+
"""Return the text branch used by an active PDG alternative path."""
|
| 332 |
|
| 333 |
match mode:
|
| 334 |
case (
|
| 335 |
+
PdgMode.FULL_CONTRASTIVE
|
| 336 |
+
| PdgMode.ALTERNATE_PDG_FIRST
|
| 337 |
| PdgMode.ALTERNATE_CFG_FIRST
|
| 338 |
| PdgMode.COMBINED_CFG_PDG
|
| 339 |
| PdgMode.PDG_WITH_ALTERNATING_CFG
|
| 340 |
):
|
| 341 |
+
return PdgBranchConditioning.CONTRASTIVE
|
| 342 |
case PdgMode.NONE | PdgMode.FULL | PdgMode.THREE_QUARTER | PdgMode.CFG_TO_PDG:
|
| 343 |
+
return PdgBranchConditioning.POSITIVE
|
| 344 |
case _ as unreachable:
|
| 345 |
raise RuntimeError(f"Unsupported PDG mode: {unreachable}")
|
| 346 |
|
|
|
|
| 420 |
if active.pdg:
|
| 421 |
tweak_text = (
|
| 422 |
self._unconditional()
|
| 423 |
+
if pdg_branch_conditioning(self.config.pdg.mode)
|
| 424 |
+
is PdgBranchConditioning.CONTRASTIVE
|
| 425 |
else self.conditional
|
| 426 |
)
|
| 427 |
tweak = self._predict(
|
|
|
|
| 448 |
pdg_active = self.config.pdg.enabled and _inside(step, self.pdg_window)
|
| 449 |
mode = self.config.pdg.mode
|
| 450 |
match mode:
|
| 451 |
+
case (
|
| 452 |
+
PdgMode.NONE
|
| 453 |
+
| PdgMode.FULL
|
| 454 |
+
| PdgMode.FULL_CONTRASTIVE
|
| 455 |
+
| PdgMode.THREE_QUARTER
|
| 456 |
+
):
|
| 457 |
return _GuidanceStep(cfg_active, pdg_active, mode)
|
| 458 |
case PdgMode.ALTERNATE_PDG_FIRST:
|
| 459 |
if pdg_active:
|
|
|
|
| 540 |
|
| 541 |
if self.unconditional is None:
|
| 542 |
raise RuntimeError(
|
| 543 |
+
"An active contrastive branch is missing negative or unconditional text."
|
| 544 |
)
|
| 545 |
return self.unconditional
|
| 546 |
|
|
|
|
| 549 |
"""Map a concrete public PDG mode to one frozen model path."""
|
| 550 |
|
| 551 |
match mode:
|
| 552 |
+
case PdgMode.FULL | PdgMode.FULL_CONTRASTIVE:
|
| 553 |
return CanterPath.SKIP_MIDDLE
|
| 554 |
case PdgMode.THREE_QUARTER:
|
| 555 |
return CanterPath.THREE_QUARTER
|
|
|
|
| 569 |
"""Return whether any configured inference step can use unconditional text."""
|
| 570 |
|
| 571 |
return bool(config.cfg.enabled) or (
|
| 572 |
+
bool(config.pdg.enabled)
|
| 573 |
+
and (
|
| 574 |
+
_pdg_uses_cfg(config.pdg.mode)
|
| 575 |
+
or pdg_branch_conditioning(config.pdg.mode)
|
| 576 |
+
is PdgBranchConditioning.CONTRASTIVE
|
| 577 |
+
)
|
| 578 |
)
|
| 579 |
|
| 580 |
|
|
|
|
| 818 |
if _prompt_batch_is_blank(values):
|
| 819 |
return None
|
| 820 |
if not required:
|
| 821 |
+
raise ValueError(
|
| 822 |
+
"Negative prompts require active CFG or contrastive PDG guidance."
|
| 823 |
+
)
|
| 824 |
return values
|
canter/version.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
| 1 |
"""Single source of truth for the installable Canter code version."""
|
| 2 |
|
| 3 |
-
__version__ = "0.
|
| 4 |
CANTER_VERSION = __version__
|
|
|
|
| 1 |
"""Single source of truth for the installable Canter code version."""
|
| 2 |
|
| 3 |
+
__version__ = "0.5.0"
|
| 4 |
CANTER_VERSION = __version__
|
canter/webui_app.py
CHANGED
|
@@ -28,6 +28,7 @@ from .webui_runtime import (
|
|
| 28 |
_unloaded_model_status,
|
| 29 |
random_seed,
|
| 30 |
snap_image_dimension,
|
|
|
|
| 31 |
update_pdg_clean_scale,
|
| 32 |
)
|
| 33 |
|
|
@@ -49,6 +50,7 @@ _SCHEDULE_CHOICES = (
|
|
| 49 |
)
|
| 50 |
_PDG_MODE_CHOICES = (
|
| 51 |
("Full path drop", PdgMode.FULL.value),
|
|
|
|
| 52 |
("Three-quarter path", PdgMode.THREE_QUARTER.value),
|
| 53 |
("Alternate: PDG first", PdgMode.ALTERNATE_PDG_FIRST.value),
|
| 54 |
("Alternate: CFG first", PdgMode.ALTERNATE_CFG_FIRST.value),
|
|
@@ -333,6 +335,7 @@ class _BasicInputs:
|
|
| 333 |
"""Prompt and basic generation controls rendered above advanced settings."""
|
| 334 |
|
| 335 |
prompt: gr.Textbox
|
|
|
|
| 336 |
image_count: gr.Number
|
| 337 |
size_preset: gr.Dropdown
|
| 338 |
preview_enabled: gr.Checkbox
|
|
@@ -370,6 +373,7 @@ class _WebInputs:
|
|
| 370 |
pdg_power: gr.Slider
|
| 371 |
pdg_start_step: gr.Number
|
| 372 |
pdg_stop_step: gr.Textbox
|
|
|
|
| 373 |
|
| 374 |
def ordered(self) -> tuple[gr.Component, ...]:
|
| 375 |
"""Return components in the order accepted by ``generate``."""
|
|
@@ -399,6 +403,7 @@ class _WebInputs:
|
|
| 399 |
self.pdg_start_step,
|
| 400 |
self.pdg_stop_step,
|
| 401 |
self.preview_enabled,
|
|
|
|
| 402 |
)
|
| 403 |
|
| 404 |
|
|
@@ -441,10 +446,14 @@ def _prompt_input() -> gr.Textbox:
|
|
| 441 |
)
|
| 442 |
|
| 443 |
|
| 444 |
-
def _basic_inputs(
|
|
|
|
|
|
|
|
|
|
| 445 |
"""Render prompt, image-count, preset, shape, and seed controls."""
|
| 446 |
|
| 447 |
prompt.render()
|
|
|
|
| 448 |
image_count = gr.Number(
|
| 449 |
minimum=1,
|
| 450 |
maximum=_MAX_IMAGE_COUNT,
|
|
@@ -488,6 +497,7 @@ def _basic_inputs(prompt: gr.Textbox) -> _BasicInputs:
|
|
| 488 |
)
|
| 489 |
return _BasicInputs(
|
| 490 |
prompt=prompt,
|
|
|
|
| 491 |
image_count=image_count,
|
| 492 |
size_preset=size_preset,
|
| 493 |
preview_enabled=preview_enabled,
|
|
@@ -729,6 +739,19 @@ def _pdg_inputs() -> tuple[
|
|
| 729 |
return enabled, mode, curve, noisy_scale, clean_scale, power, start, stop
|
| 730 |
|
| 731 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 732 |
def _create_inputs(
|
| 733 |
basic: _BasicInputs,
|
| 734 |
) -> _WebInputs:
|
|
@@ -782,6 +805,7 @@ def _create_inputs(
|
|
| 782 |
pdg_power=pdg_power,
|
| 783 |
pdg_start_step=pdg_start,
|
| 784 |
pdg_stop_step=pdg_stop,
|
|
|
|
| 785 |
)
|
| 786 |
|
| 787 |
|
|
@@ -1053,7 +1077,8 @@ def _create_workspace(
|
|
| 1053 |
with gr.Row(elem_id="canter-workspace"):
|
| 1054 |
with gr.Column(scale=3, min_width=360, elem_id="canter-controls"):
|
| 1055 |
prompt = _prompt_input()
|
| 1056 |
-
|
|
|
|
| 1057 |
with gr.Row():
|
| 1058 |
generate = gr.Button(
|
| 1059 |
"Generate",
|
|
@@ -1157,6 +1182,15 @@ def _connect_control_callbacks(
|
|
| 1157 |
api_name=None,
|
| 1158 |
api_visibility="private",
|
| 1159 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1160 |
controls.api.click(
|
| 1161 |
fn=None,
|
| 1162 |
inputs=None,
|
|
|
|
| 28 |
_unloaded_model_status,
|
| 29 |
random_seed,
|
| 30 |
snap_image_dimension,
|
| 31 |
+
update_negative_prompt_visibility,
|
| 32 |
update_pdg_clean_scale,
|
| 33 |
)
|
| 34 |
|
|
|
|
| 50 |
)
|
| 51 |
_PDG_MODE_CHOICES = (
|
| 52 |
("Full path drop", PdgMode.FULL.value),
|
| 53 |
+
("Contrastive PDG", PdgMode.FULL_CONTRASTIVE.value),
|
| 54 |
("Three-quarter path", PdgMode.THREE_QUARTER.value),
|
| 55 |
("Alternate: PDG first", PdgMode.ALTERNATE_PDG_FIRST.value),
|
| 56 |
("Alternate: CFG first", PdgMode.ALTERNATE_CFG_FIRST.value),
|
|
|
|
| 335 |
"""Prompt and basic generation controls rendered above advanced settings."""
|
| 336 |
|
| 337 |
prompt: gr.Textbox
|
| 338 |
+
negative_prompt: gr.Textbox
|
| 339 |
image_count: gr.Number
|
| 340 |
size_preset: gr.Dropdown
|
| 341 |
preview_enabled: gr.Checkbox
|
|
|
|
| 373 |
pdg_power: gr.Slider
|
| 374 |
pdg_start_step: gr.Number
|
| 375 |
pdg_stop_step: gr.Textbox
|
| 376 |
+
negative_prompt: gr.Textbox
|
| 377 |
|
| 378 |
def ordered(self) -> tuple[gr.Component, ...]:
|
| 379 |
"""Return components in the order accepted by ``generate``."""
|
|
|
|
| 403 |
self.pdg_start_step,
|
| 404 |
self.pdg_stop_step,
|
| 405 |
self.preview_enabled,
|
| 406 |
+
self.negative_prompt,
|
| 407 |
)
|
| 408 |
|
| 409 |
|
|
|
|
| 446 |
)
|
| 447 |
|
| 448 |
|
| 449 |
+
def _basic_inputs(
|
| 450 |
+
prompt: gr.Textbox,
|
| 451 |
+
negative_prompt: gr.Textbox,
|
| 452 |
+
) -> _BasicInputs:
|
| 453 |
"""Render prompt, image-count, preset, shape, and seed controls."""
|
| 454 |
|
| 455 |
prompt.render()
|
| 456 |
+
negative_prompt.render()
|
| 457 |
image_count = gr.Number(
|
| 458 |
minimum=1,
|
| 459 |
maximum=_MAX_IMAGE_COUNT,
|
|
|
|
| 497 |
)
|
| 498 |
return _BasicInputs(
|
| 499 |
prompt=prompt,
|
| 500 |
+
negative_prompt=negative_prompt,
|
| 501 |
image_count=image_count,
|
| 502 |
size_preset=size_preset,
|
| 503 |
preview_enabled=preview_enabled,
|
|
|
|
| 739 |
return enabled, mode, curve, noisy_scale, clean_scale, power, start, stop
|
| 740 |
|
| 741 |
|
| 742 |
+
def _negative_prompt_input() -> gr.Textbox:
|
| 743 |
+
"""Create an initially hidden negative prompt below the positive prompt."""
|
| 744 |
+
|
| 745 |
+
return gr.Textbox(
|
| 746 |
+
value="",
|
| 747 |
+
label="Negative prompt",
|
| 748 |
+
lines=3,
|
| 749 |
+
visible=False,
|
| 750 |
+
interactive=False,
|
| 751 |
+
render=False,
|
| 752 |
+
)
|
| 753 |
+
|
| 754 |
+
|
| 755 |
def _create_inputs(
|
| 756 |
basic: _BasicInputs,
|
| 757 |
) -> _WebInputs:
|
|
|
|
| 805 |
pdg_power=pdg_power,
|
| 806 |
pdg_start_step=pdg_start,
|
| 807 |
pdg_stop_step=pdg_stop,
|
| 808 |
+
negative_prompt=basic.negative_prompt,
|
| 809 |
)
|
| 810 |
|
| 811 |
|
|
|
|
| 1077 |
with gr.Row(elem_id="canter-workspace"):
|
| 1078 |
with gr.Column(scale=3, min_width=360, elem_id="canter-controls"):
|
| 1079 |
prompt = _prompt_input()
|
| 1080 |
+
negative_prompt = _negative_prompt_input()
|
| 1081 |
+
basic = _basic_inputs(prompt, negative_prompt)
|
| 1082 |
with gr.Row():
|
| 1083 |
generate = gr.Button(
|
| 1084 |
"Generate",
|
|
|
|
| 1182 |
api_name=None,
|
| 1183 |
api_visibility="private",
|
| 1184 |
)
|
| 1185 |
+
for component in (inputs.cfg_enabled, inputs.pdg_enabled, inputs.pdg_mode):
|
| 1186 |
+
component.change(
|
| 1187 |
+
fn=update_negative_prompt_visibility,
|
| 1188 |
+
inputs=(inputs.cfg_enabled, inputs.pdg_enabled, inputs.pdg_mode),
|
| 1189 |
+
outputs=inputs.negative_prompt,
|
| 1190 |
+
queue=False,
|
| 1191 |
+
api_name=None,
|
| 1192 |
+
api_visibility="private",
|
| 1193 |
+
)
|
| 1194 |
controls.api.click(
|
| 1195 |
fn=None,
|
| 1196 |
inputs=None,
|
canter/webui_runtime.py
CHANGED
|
@@ -22,9 +22,11 @@ from PIL import Image
|
|
| 22 |
from .inference import (
|
| 23 |
CanterInferenceConfig,
|
| 24 |
CfgGuidance,
|
|
|
|
| 25 |
PdgCurve,
|
| 26 |
PdgGuidance,
|
| 27 |
PdgMode,
|
|
|
|
| 28 |
)
|
| 29 |
from .pipeline import (
|
| 30 |
CanterOutputType,
|
|
@@ -64,6 +66,7 @@ class _ImagePipeline(Protocol):
|
|
| 64 |
self,
|
| 65 |
prompts: str | Sequence[str],
|
| 66 |
*,
|
|
|
|
| 67 |
config: CanterPipelineConfig,
|
| 68 |
initial_noise: None,
|
| 69 |
progress: SolverProgress | None,
|
|
@@ -84,6 +87,7 @@ class CanterWebRequest:
|
|
| 84 |
"""Validated sequential image request and frozen base configuration."""
|
| 85 |
|
| 86 |
prompt: str
|
|
|
|
| 87 |
image_count: int
|
| 88 |
config: CanterPipelineConfig
|
| 89 |
|
|
@@ -287,6 +291,7 @@ class CanterWebController:
|
|
| 287 |
pdg_start_step: int | float,
|
| 288 |
pdg_stop_step: int | float | str | None,
|
| 289 |
preview_enabled: bool,
|
|
|
|
| 290 |
browser_request: gr.Request | None = None,
|
| 291 |
) -> Generator[_GenerationUpdate, None, None]:
|
| 292 |
"""Yield each batch-one image as soon as its decoding completes."""
|
|
@@ -316,6 +321,7 @@ class CanterWebController:
|
|
| 316 |
pdg_power=pdg_power,
|
| 317 |
pdg_start_step=pdg_start_step,
|
| 318 |
pdg_stop_step=pdg_stop_step,
|
|
|
|
| 319 |
)
|
| 320 |
_log_generation_request(request)
|
| 321 |
pipeline = self._ensure_loaded()
|
|
@@ -353,6 +359,7 @@ class CanterWebController:
|
|
| 353 |
image_config = _image_config(request, image_index)
|
| 354 |
output = pipeline(
|
| 355 |
request.prompt,
|
|
|
|
| 356 |
config=image_config,
|
| 357 |
initial_noise=None,
|
| 358 |
progress=_step_progress(
|
|
@@ -395,6 +402,7 @@ class CanterWebController:
|
|
| 395 |
image = _attach_png_metadata(
|
| 396 |
output.images[0],
|
| 397 |
prompt=request.prompt,
|
|
|
|
| 398 |
config=image_config,
|
| 399 |
metadata=pipeline.metadata,
|
| 400 |
)
|
|
@@ -812,6 +820,21 @@ def _validated_prompt(prompt: str) -> str:
|
|
| 812 |
return prompt
|
| 813 |
|
| 814 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 815 |
def _pdg_mode_uses_cfg(mode: PdgMode) -> bool:
|
| 816 |
"""Return whether one public PDG mode consumes the CFG scale."""
|
| 817 |
|
|
@@ -824,7 +847,7 @@ def _pdg_mode_uses_cfg(mode: PdgMode) -> bool:
|
|
| 824 |
| PdgMode.CFG_TO_PDG
|
| 825 |
):
|
| 826 |
return True
|
| 827 |
-
case PdgMode.FULL | PdgMode.THREE_QUARTER:
|
| 828 |
return False
|
| 829 |
case PdgMode.NONE:
|
| 830 |
raise ValueError("The enabled web PDG selection cannot use mode none.")
|
|
@@ -832,6 +855,29 @@ def _pdg_mode_uses_cfg(mode: PdgMode) -> bool:
|
|
| 832 |
raise RuntimeError(f"Unsupported PDG mode: {unreachable}")
|
| 833 |
|
| 834 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 835 |
def update_pdg_clean_scale(
|
| 836 |
curve: str,
|
| 837 |
noisy_scale: int | float,
|
|
@@ -876,12 +922,25 @@ def build_web_request(
|
|
| 876 |
pdg_power: int | float,
|
| 877 |
pdg_start_step: int | float,
|
| 878 |
pdg_stop_step: int | float | str | None,
|
|
|
|
| 879 |
) -> CanterWebRequest:
|
| 880 |
"""Validate raw Gradio values and construct the frozen Canter dataclasses."""
|
| 881 |
|
| 882 |
resolved_pdg_mode = _choice(pdg_mode, _PDG_MODES, "PDG mode")
|
| 883 |
active_pdg_mode = resolved_pdg_mode if pdg_enabled else PdgMode.NONE
|
| 884 |
uses_cfg_scale = pdg_enabled and _pdg_mode_uses_cfg(resolved_pdg_mode)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 885 |
resolved_cfg_scale = (
|
| 886 |
_number(cfg_scale, "CFG scale") if cfg_enabled or uses_cfg_scale else None
|
| 887 |
)
|
|
@@ -952,6 +1011,7 @@ def build_web_request(
|
|
| 952 |
)
|
| 953 |
return CanterWebRequest(
|
| 954 |
prompt=_validated_prompt(prompt),
|
|
|
|
| 955 |
image_count=resolved_image_count,
|
| 956 |
config=CanterPipelineConfig(
|
| 957 |
inference=inference,
|
|
@@ -1006,6 +1066,7 @@ def _cfg_metadata(cfg: CfgGuidance) -> dict[str, object]:
|
|
| 1006 |
def _png_metadata_json(
|
| 1007 |
*,
|
| 1008 |
prompt: str,
|
|
|
|
| 1009 |
config: CanterPipelineConfig,
|
| 1010 |
metadata: CanterPipelineMetadata,
|
| 1011 |
) -> str:
|
|
@@ -1016,6 +1077,7 @@ def _png_metadata_json(
|
|
| 1016 |
raise RuntimeError("PNG metadata requires the resolved per-image seed.")
|
| 1017 |
payload: dict[str, object] = {
|
| 1018 |
"prompt": prompt,
|
|
|
|
| 1019 |
"seed": inference.seed,
|
| 1020 |
"width": inference.width,
|
| 1021 |
"height": inference.height,
|
|
@@ -1038,6 +1100,7 @@ def _attach_png_metadata(
|
|
| 1038 |
image: Image.Image,
|
| 1039 |
*,
|
| 1040 |
prompt: str,
|
|
|
|
| 1041 |
config: CanterPipelineConfig,
|
| 1042 |
metadata: CanterPipelineMetadata,
|
| 1043 |
) -> Image.Image:
|
|
@@ -1045,6 +1108,7 @@ def _attach_png_metadata(
|
|
| 1045 |
|
| 1046 |
image.info[_PNG_METADATA_KEY] = _png_metadata_json(
|
| 1047 |
prompt=prompt,
|
|
|
|
| 1048 |
config=config,
|
| 1049 |
metadata=metadata,
|
| 1050 |
)
|
|
|
|
| 22 |
from .inference import (
|
| 23 |
CanterInferenceConfig,
|
| 24 |
CfgGuidance,
|
| 25 |
+
PdgBranchConditioning,
|
| 26 |
PdgCurve,
|
| 27 |
PdgGuidance,
|
| 28 |
PdgMode,
|
| 29 |
+
pdg_branch_conditioning,
|
| 30 |
)
|
| 31 |
from .pipeline import (
|
| 32 |
CanterOutputType,
|
|
|
|
| 66 |
self,
|
| 67 |
prompts: str | Sequence[str],
|
| 68 |
*,
|
| 69 |
+
negative_prompts: str | Sequence[str] | None,
|
| 70 |
config: CanterPipelineConfig,
|
| 71 |
initial_noise: None,
|
| 72 |
progress: SolverProgress | None,
|
|
|
|
| 87 |
"""Validated sequential image request and frozen base configuration."""
|
| 88 |
|
| 89 |
prompt: str
|
| 90 |
+
negative_prompt: str | None
|
| 91 |
image_count: int
|
| 92 |
config: CanterPipelineConfig
|
| 93 |
|
|
|
|
| 291 |
pdg_start_step: int | float,
|
| 292 |
pdg_stop_step: int | float | str | None,
|
| 293 |
preview_enabled: bool,
|
| 294 |
+
negative_prompt: str = "",
|
| 295 |
browser_request: gr.Request | None = None,
|
| 296 |
) -> Generator[_GenerationUpdate, None, None]:
|
| 297 |
"""Yield each batch-one image as soon as its decoding completes."""
|
|
|
|
| 321 |
pdg_power=pdg_power,
|
| 322 |
pdg_start_step=pdg_start_step,
|
| 323 |
pdg_stop_step=pdg_stop_step,
|
| 324 |
+
negative_prompt=negative_prompt,
|
| 325 |
)
|
| 326 |
_log_generation_request(request)
|
| 327 |
pipeline = self._ensure_loaded()
|
|
|
|
| 359 |
image_config = _image_config(request, image_index)
|
| 360 |
output = pipeline(
|
| 361 |
request.prompt,
|
| 362 |
+
negative_prompts=request.negative_prompt,
|
| 363 |
config=image_config,
|
| 364 |
initial_noise=None,
|
| 365 |
progress=_step_progress(
|
|
|
|
| 402 |
image = _attach_png_metadata(
|
| 403 |
output.images[0],
|
| 404 |
prompt=request.prompt,
|
| 405 |
+
negative_prompt=request.negative_prompt,
|
| 406 |
config=image_config,
|
| 407 |
metadata=pipeline.metadata,
|
| 408 |
)
|
|
|
|
| 820 |
return prompt
|
| 821 |
|
| 822 |
|
| 823 |
+
def _validated_negative_prompt(prompt: str, *, enabled: bool) -> str | None:
|
| 824 |
+
"""Resolve blank negative text to learned unconditional conditioning."""
|
| 825 |
+
|
| 826 |
+
if not isinstance(prompt, str):
|
| 827 |
+
raise TypeError("negative_prompt must be a string.")
|
| 828 |
+
if not prompt.strip():
|
| 829 |
+
return None
|
| 830 |
+
if not enabled:
|
| 831 |
+
raise ValueError(
|
| 832 |
+
"A negative prompt requires active CFG or a PDG mode that consumes "
|
| 833 |
+
"contrastive text."
|
| 834 |
+
)
|
| 835 |
+
return prompt
|
| 836 |
+
|
| 837 |
+
|
| 838 |
def _pdg_mode_uses_cfg(mode: PdgMode) -> bool:
|
| 839 |
"""Return whether one public PDG mode consumes the CFG scale."""
|
| 840 |
|
|
|
|
| 847 |
| PdgMode.CFG_TO_PDG
|
| 848 |
):
|
| 849 |
return True
|
| 850 |
+
case PdgMode.FULL | PdgMode.FULL_CONTRASTIVE | PdgMode.THREE_QUARTER:
|
| 851 |
return False
|
| 852 |
case PdgMode.NONE:
|
| 853 |
raise ValueError("The enabled web PDG selection cannot use mode none.")
|
|
|
|
| 855 |
raise RuntimeError(f"Unsupported PDG mode: {unreachable}")
|
| 856 |
|
| 857 |
|
| 858 |
+
def update_negative_prompt_visibility(
|
| 859 |
+
cfg_enabled: bool,
|
| 860 |
+
pdg_enabled: bool,
|
| 861 |
+
pdg_mode: str,
|
| 862 |
+
) -> dict[str, object]:
|
| 863 |
+
"""Show negative text only when the selected guidance can consume it."""
|
| 864 |
+
|
| 865 |
+
resolved_mode = _choice(pdg_mode, _PDG_MODES, "PDG mode")
|
| 866 |
+
visible = bool(cfg_enabled) or (
|
| 867 |
+
bool(pdg_enabled)
|
| 868 |
+
and (
|
| 869 |
+
_pdg_mode_uses_cfg(resolved_mode)
|
| 870 |
+
or pdg_branch_conditioning(resolved_mode)
|
| 871 |
+
is PdgBranchConditioning.CONTRASTIVE
|
| 872 |
+
)
|
| 873 |
+
)
|
| 874 |
+
match visible:
|
| 875 |
+
case True:
|
| 876 |
+
return gr.update(visible=True, interactive=True)
|
| 877 |
+
case False:
|
| 878 |
+
return gr.update(value="", visible=False, interactive=False)
|
| 879 |
+
|
| 880 |
+
|
| 881 |
def update_pdg_clean_scale(
|
| 882 |
curve: str,
|
| 883 |
noisy_scale: int | float,
|
|
|
|
| 922 |
pdg_power: int | float,
|
| 923 |
pdg_start_step: int | float,
|
| 924 |
pdg_stop_step: int | float | str | None,
|
| 925 |
+
negative_prompt: str = "",
|
| 926 |
) -> CanterWebRequest:
|
| 927 |
"""Validate raw Gradio values and construct the frozen Canter dataclasses."""
|
| 928 |
|
| 929 |
resolved_pdg_mode = _choice(pdg_mode, _PDG_MODES, "PDG mode")
|
| 930 |
active_pdg_mode = resolved_pdg_mode if pdg_enabled else PdgMode.NONE
|
| 931 |
uses_cfg_scale = pdg_enabled and _pdg_mode_uses_cfg(resolved_pdg_mode)
|
| 932 |
+
uses_negative_prompt = cfg_enabled or (
|
| 933 |
+
pdg_enabled
|
| 934 |
+
and (
|
| 935 |
+
uses_cfg_scale
|
| 936 |
+
or pdg_branch_conditioning(resolved_pdg_mode)
|
| 937 |
+
is PdgBranchConditioning.CONTRASTIVE
|
| 938 |
+
)
|
| 939 |
+
)
|
| 940 |
+
resolved_negative_prompt = _validated_negative_prompt(
|
| 941 |
+
negative_prompt,
|
| 942 |
+
enabled=uses_negative_prompt,
|
| 943 |
+
)
|
| 944 |
resolved_cfg_scale = (
|
| 945 |
_number(cfg_scale, "CFG scale") if cfg_enabled or uses_cfg_scale else None
|
| 946 |
)
|
|
|
|
| 1011 |
)
|
| 1012 |
return CanterWebRequest(
|
| 1013 |
prompt=_validated_prompt(prompt),
|
| 1014 |
+
negative_prompt=resolved_negative_prompt,
|
| 1015 |
image_count=resolved_image_count,
|
| 1016 |
config=CanterPipelineConfig(
|
| 1017 |
inference=inference,
|
|
|
|
| 1066 |
def _png_metadata_json(
|
| 1067 |
*,
|
| 1068 |
prompt: str,
|
| 1069 |
+
negative_prompt: str | None,
|
| 1070 |
config: CanterPipelineConfig,
|
| 1071 |
metadata: CanterPipelineMetadata,
|
| 1072 |
) -> str:
|
|
|
|
| 1077 |
raise RuntimeError("PNG metadata requires the resolved per-image seed.")
|
| 1078 |
payload: dict[str, object] = {
|
| 1079 |
"prompt": prompt,
|
| 1080 |
+
"negative_prompt": negative_prompt,
|
| 1081 |
"seed": inference.seed,
|
| 1082 |
"width": inference.width,
|
| 1083 |
"height": inference.height,
|
|
|
|
| 1100 |
image: Image.Image,
|
| 1101 |
*,
|
| 1102 |
prompt: str,
|
| 1103 |
+
negative_prompt: str | None,
|
| 1104 |
config: CanterPipelineConfig,
|
| 1105 |
metadata: CanterPipelineMetadata,
|
| 1106 |
) -> Image.Image:
|
|
|
|
| 1108 |
|
| 1109 |
image.info[_PNG_METADATA_KEY] = _png_metadata_json(
|
| 1110 |
prompt=prompt,
|
| 1111 |
+
negative_prompt=negative_prompt,
|
| 1112 |
config=config,
|
| 1113 |
metadata=metadata,
|
| 1114 |
)
|
dist/canter-0.5.0-py3-none-any.whl
ADDED
|
Binary file (93.8 kB). View file
|
|
|