Add caption naturalizer profiles

This commit is contained in:
2026-06-27 01:43:48 +02:00
parent 36ce394462
commit 21da2949c6
8 changed files with 101 additions and 4 deletions
+3
View File
@@ -436,6 +436,9 @@ pool and intensity settings.
Naturalizer controls: Naturalizer controls:
- `input_hint`: `auto`, `metadata_json`, or `caption_or_prompt`. - `input_hint`: `auto`, `metadata_json`, or `caption_or_prompt`.
- `caption_profile`: `manual_controls` keeps the detail/style/trigger widgets
authoritative; `training_concise`, `training_dense`, and `browsing` apply
preset caption behavior.
- `detail_level`: `concise`, `balanced`, or `dense`. - `detail_level`: `concise`, `balanced`, or `dense`.
- `style_policy`: `drop_style_tail` removes old fixed style tails; `keep_style_terms` - `style_policy`: `drop_style_tail` removes old fixed style tails; `keep_style_terms`
keeps style descriptions in the rewritten text. keeps style descriptions in the rewritten text.
+1
View File
@@ -309,6 +309,7 @@ NODE_INPUT_TOOLTIPS = {
}, },
"SxCPCaptionNaturalizer": { "SxCPCaptionNaturalizer": {
"metadata_json": "Best input for training captions because it preserves structured generator details.", "metadata_json": "Best input for training captions because it preserves structured generator details.",
"caption_profile": "Preset behavior for the caption rewrite. manual_controls keeps detail/style/include-trigger widgets authoritative.",
"style_policy": "drop_style_tail removes generation/style boilerplate; keep_style_terms preserves more of it.", "style_policy": "drop_style_tail removes generation/style boilerplate; keep_style_terms preserves more of it.",
"include_trigger": "Keep this true for LoRA/training captions so the trigger token is learned.", "include_trigger": "Keep this true for LoRA/training captions so the trigger token is learned.",
}, },
+7 -1
View File
@@ -627,10 +627,16 @@ def naturalize_caption(
include_trigger: bool = True, include_trigger: bool = True,
detail_level: str = "balanced", detail_level: str = "balanced",
style_policy: str = "drop_style_tail", style_policy: str = "drop_style_tail",
caption_profile: str = caption_policy.CAPTION_PROFILE_DEFAULT,
) -> tuple[str, str]: ) -> tuple[str, str]:
"""Rewrite tag-style prompt/caption text into compact natural language.""" """Rewrite tag-style prompt/caption text into compact natural language."""
input_hint = input_hint if input_hint in ("auto", "metadata_json", "caption_or_prompt") else "auto" input_hint = input_hint if input_hint in ("auto", "metadata_json", "caption_or_prompt") else "auto"
detail_level = caption_policy.normalize_detail_level(detail_level) detail_level, style_policy, include_trigger = caption_policy.apply_caption_profile(
caption_profile,
detail_level=detail_level,
style_policy=style_policy,
include_trigger=include_trigger,
)
keep_style = caption_policy.keep_style_terms(style_policy) keep_style = caption_policy.keep_style_terms(style_policy)
row, row_method = _row_from_inputs(source_text, metadata_json, input_hint) row, row_method = _row_from_inputs(source_text, metadata_json, input_hint)
if row is not None: if row is not None:
+43
View File
@@ -16,6 +16,26 @@ DEFAULT_TRIGGER = "sxcppnl7"
DETAIL_LEVELS = ("balanced", "concise", "dense") DETAIL_LEVELS = ("balanced", "concise", "dense")
STYLE_POLICIES = ("drop_style_tail", "keep_style_terms") STYLE_POLICIES = ("drop_style_tail", "keep_style_terms")
CAPTION_PROFILE_DEFAULT = "manual_controls"
CAPTION_PROFILES = {
"manual_controls": {},
"training_concise": {
"detail_level": "concise",
"style_policy": "drop_style_tail",
"include_trigger": True,
},
"training_dense": {
"detail_level": "dense",
"style_policy": "drop_style_tail",
"include_trigger": True,
},
"browsing": {
"detail_level": "balanced",
"style_policy": "keep_style_terms",
"include_trigger": False,
},
}
STYLE_TAILS = [ STYLE_TAILS = [
", coloured pencil comic illustration, crisp linework, hatching, soft pastel palette, warm sensual lighting, textured parchment paper", ", coloured pencil comic illustration, crisp linework, hatching, soft pastel palette, warm sensual lighting, textured parchment paper",
@@ -59,6 +79,29 @@ def normalize_style_policy(value: str) -> str:
return value if value in STYLE_POLICIES else "drop_style_tail" return value if value in STYLE_POLICIES else "drop_style_tail"
def caption_profile_choices() -> list[str]:
return list(CAPTION_PROFILES)
def normalize_caption_profile(value: str) -> str:
return value if value in CAPTION_PROFILES else CAPTION_PROFILE_DEFAULT
def apply_caption_profile(
caption_profile: str,
*,
detail_level: str,
style_policy: str,
include_trigger: bool,
) -> tuple[str, str, bool]:
profile = CAPTION_PROFILES[normalize_caption_profile(caption_profile)]
return (
normalize_detail_level(profile.get("detail_level", detail_level)),
normalize_style_policy(profile.get("style_policy", style_policy)),
bool(profile.get("include_trigger", include_trigger)),
)
def keep_style_terms(style_policy: str) -> bool: def keep_style_terms(style_policy: str) -> bool:
return normalize_style_policy(style_policy) == "keep_style_terms" return normalize_style_policy(style_policy) == "keep_style_terms"
+5 -2
View File
@@ -309,10 +309,13 @@ Keep here:
- shared cast descriptor parsing and label replacement from `krea_cast.py`. - shared cast descriptor parsing and label replacement from `krea_cast.py`.
- caption detail-level/style-policy normalization, clothing cleanup, and - caption detail-level/style-policy normalization, clothing cleanup, and
composition cleanup from `caption_policy.py`. composition cleanup from `caption_policy.py`.
- caption profiles for manual controls, concise training captions, dense
training captions, and browsing captions live in `caption_policy.py` and are
exposed by `SxCP Caption Naturalizer`.
Improve later: Improve later:
- add more caption profiles if a new training or browsing workflow needs a
- add a `caption_profile` option for concise/dense LoRA caption styles. distinct default.
### Category JSON Path ### Category JSON Path
+1 -1
View File
@@ -97,7 +97,7 @@ Core helper ownership:
| `row_normalization.py` | Final prompt-row and pair metadata normalization: trigger prepending, extra-positive append, negative merge/dedupe, caption-part joining, and embedded soft/hard row sanitation. | | `row_normalization.py` | Final prompt-row and pair metadata normalization: trigger prepending, extra-positive append, negative merge/dedupe, caption-part joining, and embedded soft/hard row sanitation. |
| `formatter_input.py` | Shared formatter input parsing: text cleanup, metadata/source JSON detection, trigger-prefix stripping, shared prompt field-label inventory, `Avoid:` splitting, prompt-field extraction, and metadata row-value fallback. | | `formatter_input.py` | Shared formatter input parsing: text cleanup, metadata/source JSON detection, trigger-prefix stripping, shared prompt field-label inventory, `Avoid:` splitting, prompt-field extraction, and metadata row-value fallback. |
| `sdxl_presets.py` | SDXL style presets, quality presets, default negative prompt, and metadata-family tag hints used by the SDXL formatter and node choice lists. | | `sdxl_presets.py` | SDXL style presets, quality presets, default negative prompt, and metadata-family tag hints used by the SDXL formatter and node choice lists. |
| `caption_policy.py` | Caption naturalizer policy data and helpers: style tails, item labels, metadata-family caption labels, detail/style-policy normalization, clothing cleanup, and composition cleanup. | | `caption_policy.py` | Caption naturalizer policy data and helpers: caption profiles, style tails, item labels, metadata-family caption labels, detail/style-policy normalization, clothing cleanup, and composition cleanup. |
## Node IO Map ## Node IO Map
+5
View File
@@ -2,10 +2,12 @@ from __future__ import annotations
try: try:
from .caption_naturalizer import naturalize_caption from .caption_naturalizer import naturalize_caption
from .caption_policy import caption_profile_choices
from .krea_formatter import format_krea2_prompt from .krea_formatter import format_krea2_prompt
from .sdxl_formatter import format_sdxl_prompt, sdxl_quality_preset_choices, sdxl_style_preset_choices from .sdxl_formatter import format_sdxl_prompt, sdxl_quality_preset_choices, sdxl_style_preset_choices
except ImportError: # Allows local smoke tests from the repository root. except ImportError: # Allows local smoke tests from the repository root.
from caption_naturalizer import naturalize_caption from caption_naturalizer import naturalize_caption
from caption_policy import caption_profile_choices
from krea_formatter import format_krea2_prompt from krea_formatter import format_krea2_prompt
from sdxl_formatter import format_sdxl_prompt, sdxl_quality_preset_choices, sdxl_style_preset_choices from sdxl_formatter import format_sdxl_prompt, sdxl_quality_preset_choices, sdxl_style_preset_choices
@@ -17,6 +19,7 @@ class SxCPCaptionNaturalizer:
"required": { "required": {
"source_text": ("STRING", {"default": "", "multiline": True}), "source_text": ("STRING", {"default": "", "multiline": True}),
"input_hint": (["auto", "metadata_json", "caption_or_prompt"], {"default": "auto"}), "input_hint": (["auto", "metadata_json", "caption_or_prompt"], {"default": "auto"}),
"caption_profile": (caption_profile_choices(), {"default": "manual_controls"}),
"detail_level": (["balanced", "concise", "dense"], {"default": "balanced"}), "detail_level": (["balanced", "concise", "dense"], {"default": "balanced"}),
"style_policy": (["drop_style_tail", "keep_style_terms"], {"default": "drop_style_tail"}), "style_policy": (["drop_style_tail", "keep_style_terms"], {"default": "drop_style_tail"}),
"trigger": ("STRING", {"default": "sxcppnl7"}), "trigger": ("STRING", {"default": "sxcppnl7"}),
@@ -37,6 +40,7 @@ class SxCPCaptionNaturalizer:
self, self,
source_text, source_text,
input_hint, input_hint,
caption_profile,
detail_level, detail_level,
style_policy, style_policy,
trigger, trigger,
@@ -53,6 +57,7 @@ class SxCPCaptionNaturalizer:
include_trigger=include_trigger, include_trigger=include_trigger,
detail_level=detail_level, detail_level=detail_level,
style_policy=style_policy, style_policy=style_policy,
caption_profile=caption_profile,
) )
+36
View File
@@ -969,6 +969,31 @@ def smoke_caption_policy() -> None:
_expect(caption_policy.keep_style_terms("keep_style_terms") is True, "Caption style policy keep flag changed") _expect(caption_policy.keep_style_terms("keep_style_terms") is True, "Caption style policy keep flag changed")
_expect(caption_policy.detail_allows("concise") is False, "Caption concise detail gate changed") _expect(caption_policy.detail_allows("concise") is False, "Caption concise detail gate changed")
_expect(caption_policy.detail_allows("dense", dense_only=True) is True, "Caption dense-only gate changed") _expect(caption_policy.detail_allows("dense", dense_only=True) is True, "Caption dense-only gate changed")
_expect("training_concise" in caption_policy.caption_profile_choices(), "Caption profile choices lost training_concise")
_expect(
caption_policy.normalize_caption_profile("bad") == caption_policy.CAPTION_PROFILE_DEFAULT,
"Caption invalid profile fallback changed",
)
_expect(
caption_policy.apply_caption_profile(
"training_dense",
detail_level="concise",
style_policy="keep_style_terms",
include_trigger=False,
)
== ("dense", "drop_style_tail", True),
"Caption training_dense profile overrides changed",
)
_expect(
caption_policy.apply_caption_profile(
"manual_controls",
detail_level="concise",
style_policy="keep_style_terms",
include_trigger=False,
)
== ("concise", "keep_style_terms", False),
"Caption manual profile should preserve explicit controls",
)
style_tail = caption_policy.STYLE_TAILS[0] style_tail = caption_policy.STYLE_TAILS[0]
_expect( _expect(
@@ -991,6 +1016,13 @@ def smoke_caption_policy() -> None:
_expect(caption_policy.metadata_action_label(row) == "oral action", "Caption action-family label changed") _expect(caption_policy.metadata_action_label(row) == "oral action", "Caption action-family label changed")
row = {"action_family": "oral", "position_family": "anal"} row = {"action_family": "oral", "position_family": "anal"}
_expect(caption_naturalizer._metadata_action_label(row) == "anal action", "Caption position-family label priority changed") _expect(caption_naturalizer._metadata_action_label(row) == "anal action", "Caption position-family label priority changed")
browsing_caption, browsing_method = caption_naturalizer.naturalize_caption(
"woman, red dress, studio",
caption_profile="browsing",
include_trigger=True,
)
_expect(not browsing_caption.startswith(Trigger), "Caption browsing profile should disable trigger by default")
_expect(browsing_method == "text(fallback)", "Caption browsing profile changed fallback method")
def smoke_sdxl_presets_policy() -> None: def smoke_sdxl_presets_policy() -> None:
@@ -2763,6 +2795,7 @@ def smoke_node_formatter_registration() -> None:
caption, caption_method = sxcp_nodes.NODE_CLASS_MAPPINGS["SxCPCaptionNaturalizer"]().build( caption, caption_method = sxcp_nodes.NODE_CLASS_MAPPINGS["SxCPCaptionNaturalizer"]().build(
"A woman standing by a window, best quality", "A woman standing by a window, best quality",
"caption_or_prompt", "caption_or_prompt",
"manual_controls",
"concise", "concise",
"drop_style_tail", "drop_style_tail",
"sxcppnl7", "sxcppnl7",
@@ -2771,6 +2804,9 @@ def smoke_node_formatter_registration() -> None:
_expect_text("node_formatter.caption", caption, 20) _expect_text("node_formatter.caption", caption, 20)
_expect(caption.startswith("sxcppnl7"), "Caption Naturalizer did not prepend trigger") _expect(caption.startswith("sxcppnl7"), "Caption Naturalizer did not prepend trigger")
_expect("text(" in caption_method, "Caption Naturalizer method changed unexpectedly") _expect("text(" in caption_method, "Caption Naturalizer method changed unexpectedly")
caption_inputs = sxcp_nodes.NODE_CLASS_MAPPINGS["SxCPCaptionNaturalizer"].INPUT_TYPES().get("required") or {}
_expect("caption_profile" in caption_inputs, "Caption Naturalizer lost caption_profile input")
_expect("tooltip" in caption_inputs["caption_profile"][1], "Caption profile tooltip injection missing")
krea_output = krea_node().build( krea_output = krea_node().build(
"sxcppnl7 A woman standing by a window", "sxcppnl7 A woman standing by a window",