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:
- `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`.
- `style_policy`: `drop_style_tail` removes old fixed style tails; `keep_style_terms`
keeps style descriptions in the rewritten text.
+1
View File
@@ -309,6 +309,7 @@ NODE_INPUT_TOOLTIPS = {
},
"SxCPCaptionNaturalizer": {
"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.",
"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,
detail_level: str = "balanced",
style_policy: str = "drop_style_tail",
caption_profile: str = caption_policy.CAPTION_PROFILE_DEFAULT,
) -> tuple[str, str]:
"""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"
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)
row, row_method = _row_from_inputs(source_text, metadata_json, input_hint)
if row is not None:
+43
View File
@@ -16,6 +16,26 @@ DEFAULT_TRIGGER = "sxcppnl7"
DETAIL_LEVELS = ("balanced", "concise", "dense")
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 = [
", 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"
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:
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`.
- caption detail-level/style-policy normalization, clothing cleanup, and
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:
- add a `caption_profile` option for concise/dense LoRA caption styles.
- add more caption profiles if a new training or browsing workflow needs a
distinct default.
### 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. |
| `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. |
| `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
+5
View File
@@ -2,10 +2,12 @@ from __future__ import annotations
try:
from .caption_naturalizer import naturalize_caption
from .caption_policy import caption_profile_choices
from .krea_formatter import format_krea2_prompt
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.
from caption_naturalizer import naturalize_caption
from caption_policy import caption_profile_choices
from krea_formatter import format_krea2_prompt
from sdxl_formatter import format_sdxl_prompt, sdxl_quality_preset_choices, sdxl_style_preset_choices
@@ -17,6 +19,7 @@ class SxCPCaptionNaturalizer:
"required": {
"source_text": ("STRING", {"default": "", "multiline": True}),
"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"}),
"style_policy": (["drop_style_tail", "keep_style_terms"], {"default": "drop_style_tail"}),
"trigger": ("STRING", {"default": "sxcppnl7"}),
@@ -37,6 +40,7 @@ class SxCPCaptionNaturalizer:
self,
source_text,
input_hint,
caption_profile,
detail_level,
style_policy,
trigger,
@@ -53,6 +57,7 @@ class SxCPCaptionNaturalizer:
include_trigger=include_trigger,
detail_level=detail_level,
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.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("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]
_expect(
@@ -991,6 +1016,13 @@ def smoke_caption_policy() -> None:
_expect(caption_policy.metadata_action_label(row) == "oral action", "Caption action-family label changed")
row = {"action_family": "oral", "position_family": "anal"}
_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:
@@ -2763,6 +2795,7 @@ def smoke_node_formatter_registration() -> None:
caption, caption_method = sxcp_nodes.NODE_CLASS_MAPPINGS["SxCPCaptionNaturalizer"]().build(
"A woman standing by a window, best quality",
"caption_or_prompt",
"manual_controls",
"concise",
"drop_style_tail",
"sxcppnl7",
@@ -2771,6 +2804,9 @@ def smoke_node_formatter_registration() -> None:
_expect_text("node_formatter.caption", caption, 20)
_expect(caption.startswith("sxcppnl7"), "Caption Naturalizer did not prepend trigger")
_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(
"sxcppnl7 A woman standing by a window",