Normalize built-in row appearance metadata
This commit is contained in:
@@ -132,7 +132,7 @@ Core helper ownership:
|
|||||||
| `krea_row_fields.py` | Shared Krea normal-row field extraction for item, scene, pose, expression, composition/source-composition, camera, and style used by normal and configured-cast routes. |
|
| `krea_row_fields.py` | Shared Krea normal-row field extraction for item, scene, pose, expression, composition/source-composition, camera, and style used by normal and configured-cast routes. |
|
||||||
| `krea_cast.py` | Shared formatter cast descriptor parsing, cast labels, cast prose, natural cast descriptor text, and label replacement used by Krea2 and caption routes. |
|
| `krea_cast.py` | Shared formatter cast descriptor parsing, cast labels, cast prose, natural cast descriptor text, and label replacement used by Krea2 and caption routes. |
|
||||||
| `prompt_hygiene.py` | Generic prompt, caption, and negative-prompt cleanup, including route-agnostic negative-prompt merge/dedupe. |
|
| `prompt_hygiene.py` | Generic prompt, caption, and negative-prompt cleanup, including route-agnostic negative-prompt merge/dedupe. |
|
||||||
| `row_normalization.py` | Final prompt-row and pair metadata normalization: legacy built-in subject/count/scene/item/pose/expression metadata enrichment, trigger prepending, extra-positive append, negative merge/dedupe, caption-part joining, embedded soft/hard row output and side-metadata synchronization, and embedded row sanitation. |
|
| `row_normalization.py` | Final prompt-row and pair metadata normalization: legacy built-in subject/count/scene/appearance/item/pose/expression metadata enrichment, trigger prepending, extra-positive append, negative merge/dedupe, caption-part joining, embedded soft/hard row output and side-metadata synchronization, and embedded row sanitation. |
|
||||||
| `formatter_detail.py` | Shared formatter detail-level choices, normalization, and concise/balanced/dense gates used by Krea2 and caption routes. |
|
| `formatter_detail.py` | Shared formatter detail-level choices, normalization, and concise/balanced/dense gates used by Krea2 and caption routes. |
|
||||||
| `formatter_input.py` | Shared formatter input parsing: text cleanup, metadata/source JSON detection, trigger-prefix stripping, shared prompt field-label inventory, fallback field-label stripping, `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, fallback field-label stripping, `Avoid:` splitting, prompt-field extraction, and metadata row-value fallback. |
|
||||||
| `formatter_target.py` | Shared formatter target choices and normalization for `auto`, `single`, `softcore`, and `hardcore`, including pair-side selection and combined-caption inclusion policy. |
|
| `formatter_target.py` | Shared formatter target choices and normalization for `auto`, `single`, `softcore`, and `hardcore`, including pair-side selection and combined-caption inclusion policy. |
|
||||||
|
|||||||
@@ -4,9 +4,11 @@ import re
|
|||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
from . import generate_prompt_batches as prompt_batches
|
||||||
from . import row_location as row_location_policy
|
from . import row_location as row_location_policy
|
||||||
from .prompt_hygiene import combine_negative_text, sanitize_caption_text, sanitize_negative_text, sanitize_prompt_text
|
from .prompt_hygiene import combine_negative_text, sanitize_caption_text, sanitize_negative_text, sanitize_prompt_text
|
||||||
except ImportError: # Allows local smoke tests with `python tools/prompt_smoke.py`.
|
except ImportError: # Allows local smoke tests with `python tools/prompt_smoke.py`.
|
||||||
|
import generate_prompt_batches as prompt_batches
|
||||||
import row_location as row_location_policy
|
import row_location as row_location_policy
|
||||||
from prompt_hygiene import combine_negative_text, sanitize_caption_text, sanitize_negative_text, sanitize_prompt_text
|
from prompt_hygiene import combine_negative_text, sanitize_caption_text, sanitize_negative_text, sanitize_prompt_text
|
||||||
|
|
||||||
@@ -113,6 +115,64 @@ def _clean_legacy_clothing(value: Any) -> str:
|
|||||||
return text.strip(" ,")
|
return text.strip(" ,")
|
||||||
|
|
||||||
|
|
||||||
|
def _legacy_body_phrase(row: dict[str, Any]) -> str:
|
||||||
|
body_phrase = _clean_text(row.get("body_phrase"))
|
||||||
|
if body_phrase:
|
||||||
|
return body_phrase
|
||||||
|
body = _clean_text(row.get("body_type") or row.get("body"))
|
||||||
|
if not body:
|
||||||
|
return ""
|
||||||
|
figure_note = _clean_text(row.get("figure") or row.get("figure_note"))
|
||||||
|
return _clean_text(prompt_batches.make_body_phrase(body, figure_note))
|
||||||
|
|
||||||
|
|
||||||
|
def _strip_legacy_caption_lead(caption: str) -> str:
|
||||||
|
pieces = caption.split(", ", 1)
|
||||||
|
if len(pieces) == 2 and pieces[0].strip().lower() not in ("woman", "man"):
|
||||||
|
return pieces[1].strip()
|
||||||
|
return caption
|
||||||
|
|
||||||
|
|
||||||
|
def _legacy_single_caption_front(row: dict[str, Any]) -> dict[str, str]:
|
||||||
|
caption = _strip_legacy_caption_lead(_clean_text(row.get("caption")))
|
||||||
|
if not caption:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
subject = _clean_text(row.get("primary_subject") or row.get("subject"))
|
||||||
|
age = _clean_text(row.get("age_band") or row.get("age"))
|
||||||
|
body_phrase = _legacy_body_phrase(row)
|
||||||
|
if subject.lower() in ("woman", "man") and age and body_phrase:
|
||||||
|
prefix = f"{subject}, {age}, {body_phrase}, "
|
||||||
|
if caption.lower().startswith(prefix.lower()):
|
||||||
|
try:
|
||||||
|
skin, hair, eyes, _rest = caption[len(prefix) :].split(", ", 3)
|
||||||
|
except ValueError:
|
||||||
|
return {}
|
||||||
|
return {
|
||||||
|
"caption_subject": subject,
|
||||||
|
"caption_age": age,
|
||||||
|
"caption_body_phrase": body_phrase,
|
||||||
|
"caption_skin": skin,
|
||||||
|
"caption_hair": hair,
|
||||||
|
"caption_eyes": eyes,
|
||||||
|
}
|
||||||
|
|
||||||
|
pieces = [piece.strip() for piece in caption.split(", ", 6)]
|
||||||
|
if len(pieces) < 7:
|
||||||
|
return {}
|
||||||
|
subject, age, body_phrase, skin, hair, eyes, _rest = pieces
|
||||||
|
if subject.lower() not in ("woman", "man"):
|
||||||
|
return {}
|
||||||
|
return {
|
||||||
|
"caption_subject": subject,
|
||||||
|
"caption_age": age,
|
||||||
|
"caption_body_phrase": body_phrase,
|
||||||
|
"caption_skin": skin,
|
||||||
|
"caption_hair": hair,
|
||||||
|
"caption_eyes": eyes,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def enrich_legacy_row_metadata(row: dict[str, Any]) -> dict[str, Any]:
|
def enrich_legacy_row_metadata(row: dict[str, Any]) -> dict[str, Any]:
|
||||||
if row.get("source") != "built_in_generator":
|
if row.get("source") != "built_in_generator":
|
||||||
return row
|
return row
|
||||||
@@ -133,6 +193,12 @@ def enrich_legacy_row_metadata(row: dict[str, Any]) -> dict[str, Any]:
|
|||||||
if scene_text:
|
if scene_text:
|
||||||
row["scene_text"] = scene_text
|
row["scene_text"] = scene_text
|
||||||
row.setdefault("scene_entry", {"slug": scene_slug, "prompt": scene_text})
|
row.setdefault("scene_entry", {"slug": scene_slug, "prompt": scene_text})
|
||||||
|
if subject_type in ("woman", "man"):
|
||||||
|
front = _legacy_single_caption_front(row)
|
||||||
|
_setdefault_nonempty(row, "body_phrase", front.get("caption_body_phrase", ""))
|
||||||
|
_setdefault_nonempty(row, "skin", front.get("caption_skin", ""))
|
||||||
|
_setdefault_nonempty(row, "hair", front.get("caption_hair", ""))
|
||||||
|
_setdefault_nonempty(row, "eyes", front.get("caption_eyes", ""))
|
||||||
pose = _clean_legacy_pose(_legacy_prompt_field(row, "Pose"))
|
pose = _clean_legacy_pose(_legacy_prompt_field(row, "Pose"))
|
||||||
_setdefault_nonempty(row, "pose", pose)
|
_setdefault_nonempty(row, "pose", pose)
|
||||||
expression = _legacy_prompt_field(row, "Facial expression") or _legacy_prompt_field(row, "Facial expressions")
|
expression = _legacy_prompt_field(row, "Facial expression") or _legacy_prompt_field(row, "Facial expressions")
|
||||||
|
|||||||
@@ -580,6 +580,10 @@ def smoke_builtin_single() -> None:
|
|||||||
_expect(row.get("scene_entry", {}).get("slug") == row.get("scene"), "builtin single row lost scene_entry slug")
|
_expect(row.get("scene_entry", {}).get("slug") == row.get("scene"), "builtin single row lost scene_entry slug")
|
||||||
item = _expect_text("builtin_single_woman.item", row.get("item"), 8)
|
item = _expect_text("builtin_single_woman.item", row.get("item"), 8)
|
||||||
pose = _expect_text("builtin_single_woman.pose", row.get("pose"), 8)
|
pose = _expect_text("builtin_single_woman.pose", row.get("pose"), 8)
|
||||||
|
body_phrase = _expect_text("builtin_single_woman.body_phrase", row.get("body_phrase"), 8)
|
||||||
|
skin = _expect_text("builtin_single_woman.skin", row.get("skin"), 8)
|
||||||
|
hair = _expect_text("builtin_single_woman.hair", row.get("hair"), 5)
|
||||||
|
eyes = _expect_text("builtin_single_woman.eyes", row.get("eyes"), 4)
|
||||||
_expect(row.get("item_label") == "Clothing", "builtin single row lost item label")
|
_expect(row.get("item_label") == "Clothing", "builtin single row lost item label")
|
||||||
_expect(row.get("clothing") == item, "builtin single row did not mirror clothing into item metadata")
|
_expect(row.get("clothing") == item, "builtin single row did not mirror clothing into item metadata")
|
||||||
_expect("fashion editorial styling" not in item.lower(), "builtin single item kept generic styling suffix")
|
_expect("fashion editorial styling" not in item.lower(), "builtin single item kept generic styling suffix")
|
||||||
@@ -613,13 +617,24 @@ def smoke_builtin_single() -> None:
|
|||||||
)
|
)
|
||||||
_expect(item in str(krea_metadata.get("krea_prompt", "")), "Krea metadata-only built-in route lost explicit item")
|
_expect(item in str(krea_metadata.get("krea_prompt", "")), "Krea metadata-only built-in route lost explicit item")
|
||||||
_expect(pose in str(krea_metadata.get("krea_prompt", "")), "Krea metadata-only built-in route lost explicit pose")
|
_expect(pose in str(krea_metadata.get("krea_prompt", "")), "Krea metadata-only built-in route lost explicit pose")
|
||||||
|
_expect(body_phrase in str(krea_metadata.get("krea_prompt", "")), "Krea metadata-only built-in route lost body phrase")
|
||||||
|
_expect(skin in str(krea_metadata.get("krea_prompt", "")), "Krea metadata-only built-in route lost skin")
|
||||||
|
_expect(hair in str(krea_metadata.get("krea_prompt", "")), "Krea metadata-only built-in route lost hair")
|
||||||
|
_expect(eyes in str(krea_metadata.get("krea_prompt", "")), "Krea metadata-only built-in route lost eyes")
|
||||||
item_anchor = " ".join(re.findall(r"[a-z0-9]+", item.lower())[:3])
|
item_anchor = " ".join(re.findall(r"[a-z0-9]+", item.lower())[:3])
|
||||||
pose_anchor = " ".join(re.findall(r"[a-z0-9]+", pose.lower())[:4])
|
pose_anchor = " ".join(re.findall(r"[a-z0-9]+", pose.lower())[:4])
|
||||||
sdxl_metadata_prompt = str(sdxl_metadata.get("sdxl_prompt", "")).lower()
|
sdxl_metadata_prompt = str(sdxl_metadata.get("sdxl_prompt", "")).lower()
|
||||||
_expect(item_anchor in sdxl_metadata_prompt, "SDXL metadata-only built-in route lost explicit item")
|
_expect(item_anchor in sdxl_metadata_prompt, "SDXL metadata-only built-in route lost explicit item")
|
||||||
_expect(pose_anchor in sdxl_metadata_prompt, "SDXL metadata-only built-in route lost explicit pose")
|
_expect(pose_anchor in sdxl_metadata_prompt, "SDXL metadata-only built-in route lost explicit pose")
|
||||||
|
for body_tag in sdxl_tag_policy.split_tag_text(body_phrase):
|
||||||
|
_expect(body_tag.lower() in sdxl_metadata_prompt, f"SDXL metadata-only built-in route lost body tag: {body_tag}")
|
||||||
|
_expect(skin.lower() in sdxl_metadata_prompt, "SDXL metadata-only built-in route lost skin")
|
||||||
|
_expect(hair.lower() in sdxl_metadata_prompt, "SDXL metadata-only built-in route lost hair")
|
||||||
|
_expect(eyes.lower() in sdxl_metadata_prompt, "SDXL metadata-only built-in route lost eyes")
|
||||||
_expect(caption_metadata_method.endswith("metadata(single)"), "Caption metadata-only built-in route did not use single metadata branch")
|
_expect(caption_metadata_method.endswith("metadata(single)"), "Caption metadata-only built-in route did not use single metadata branch")
|
||||||
_expect(item in caption_metadata and pose in caption_metadata, "Caption metadata-only built-in route lost explicit item or pose")
|
_expect(item in caption_metadata and pose in caption_metadata, "Caption metadata-only built-in route lost explicit item or pose")
|
||||||
|
_expect(body_phrase in caption_metadata and skin in caption_metadata, "Caption metadata-only built-in route lost appearance")
|
||||||
|
_expect(hair in caption_metadata and eyes in caption_metadata, "Caption metadata-only built-in route lost hair or eyes")
|
||||||
_expect_formatter_outputs(row, "builtin_single_woman", target="single")
|
_expect_formatter_outputs(row, "builtin_single_woman", target="single")
|
||||||
|
|
||||||
|
|
||||||
@@ -2877,6 +2892,33 @@ def smoke_row_normalization_policy() -> None:
|
|||||||
_expect("calm smile" in str(legacy_couple.get("expression", "")), "Legacy couple row lost expression metadata")
|
_expect("calm smile" in str(legacy_couple.get("expression", "")), "Legacy couple row lost expression metadata")
|
||||||
_expect("cast_summary" not in legacy_couple, "Legacy couple row should not gain configured-cast summary")
|
_expect("cast_summary" not in legacy_couple, "Legacy couple row should not gain configured-cast summary")
|
||||||
|
|
||||||
|
legacy_single = row_normalization.normalize_prompt_row(
|
||||||
|
{
|
||||||
|
"source": "built_in_generator",
|
||||||
|
"primary_subject": "woman",
|
||||||
|
"scene": "studio",
|
||||||
|
"age_band": "25-year-old adult",
|
||||||
|
"body_type": "curvy",
|
||||||
|
"figure": "soft curves, defined waist",
|
||||||
|
"caption": (
|
||||||
|
f"{Trigger}, woman, 25-year-old adult, curvy figure with soft curves, defined waist, "
|
||||||
|
"warm skin, short blonde hair, blue eyes, pose, expression, clothing, scene, composition"
|
||||||
|
),
|
||||||
|
"prompt": (
|
||||||
|
"A woman. Scene: old studio. Pose: standing calmly. "
|
||||||
|
"Facial expression: direct look. Clothing: fitted dress, fashion editorial styling. "
|
||||||
|
"Composition: vertical portrait."
|
||||||
|
),
|
||||||
|
"negative_prompt": "bad anatomy",
|
||||||
|
},
|
||||||
|
active_trigger=Trigger,
|
||||||
|
prepend_trigger_to_prompt=False,
|
||||||
|
)
|
||||||
|
_expect(legacy_single.get("body_phrase") == "curvy figure with soft curves, defined waist", "Legacy single row lost body phrase")
|
||||||
|
_expect(legacy_single.get("skin") == "warm skin", "Legacy single row lost skin metadata")
|
||||||
|
_expect(legacy_single.get("hair") == "short blonde hair", "Legacy single row lost hair metadata")
|
||||||
|
_expect(legacy_single.get("eyes") == "blue eyes", "Legacy single row lost eye metadata")
|
||||||
|
|
||||||
legacy_group = row_normalization.normalize_prompt_row(
|
legacy_group = row_normalization.normalize_prompt_row(
|
||||||
{
|
{
|
||||||
"source": "built_in_generator",
|
"source": "built_in_generator",
|
||||||
|
|||||||
Reference in New Issue
Block a user