Add analysis profiles with distance/proximity-aware axes
A discrete verdict collapses magnitude and a generic axis can hide what you're calibrating (a blowjob where the head is 20cm away still reads sexual_act=oral -> MATCH). New 'profile' input selects an act-specialized axis set (general / oral / penetration / handjob / solo) whose act-critical axes capture distance explicitly (mouth_genital_distance: touching/<5cm/10-20cm/>20cm, oral_depth, insertion_depth, stroke_position, ...). axes now overrides the profile when set. agent_bridge gains --profile; workflows + docs updated. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+63
-4
@@ -78,8 +78,63 @@ AXIS_DEFS = {
|
||||
"scene": "location, furniture, props, background",
|
||||
"lighting_color": "lighting quality and color palette / grade",
|
||||
"art_style": "rendering style and realism (photoreal, anime, illustration, 3D)",
|
||||
|
||||
# --- DISTANCE-AWARE act-specific axes (used by analysis profiles) ---
|
||||
# Oral: capture proximity/depth explicitly so 'mouth touching' vs 'head far away'
|
||||
# is a measurable difference, not hidden behind a coarse 'oral' label.
|
||||
"mouth_genital_contact": "is the mouth in contact with the penis/genitals? options: lips on tip / tip in mouth / shaft in mouth / licking shaft / kissing / NOT in contact",
|
||||
"mouth_genital_distance": "if NOT in contact, how far is the mouth from the genitals: touching (~0), very close (<5cm), near (~10-20cm), far (>20cm). If in contact, say 'contact'.",
|
||||
"oral_depth": "how much of the penis is inside the mouth: none / tip only / about half / deep (throat)",
|
||||
"tongue": "tongue visible and where: not visible / on tip / on shaft / flat / extended",
|
||||
"hand_on_shaft": "hands on the penis/shaft: none / one hand (base/mid/tip) / two hands",
|
||||
"gaze_up": "is the giver looking up at the partner, at the camera, down, or eyes closed",
|
||||
# Penetration depth/angle as measurable values
|
||||
"insertion_depth": "how deep is penetration: tip only / shallow / half / full/hilt / pulling out",
|
||||
"insertion_angle": "angle of penetration relative to the body: vertical / horizontal / oblique",
|
||||
# Handjob
|
||||
"grip_style": "how the penis is held: loose / firm / two-handed / fingertips / not held",
|
||||
"stroke_position": "where the hand is along the shaft: base / mid / tip / gliding full length",
|
||||
# Solo
|
||||
"self_touch_location": "where the subject is touching themselves: clitoris / labia / breasts / penetration / none",
|
||||
"toy_use": "any toy/object in use and where: none / dildo / vibrator / other (location)",
|
||||
}
|
||||
DEFAULT_AXES = ", ".join(AXIS_DEFS)
|
||||
|
||||
# Shared identity/body/wardrobe/affect/camera/render axes (act-independent).
|
||||
_BASE_AXES = [
|
||||
"subject_count", "gender_mix", "age_appearance", "ethnicity_skin", "body_type",
|
||||
"breast_size", "distinctive_features", "hair", "clothing_state",
|
||||
"facial_expression", "gaze", "framing", "camera_angle", "scene",
|
||||
"lighting_color", "art_style",
|
||||
]
|
||||
|
||||
# Analysis profiles: act-specialized axis sets. The act-critical axes are made
|
||||
# distance/proximity-aware so magnitude (e.g. mouth-to-penis distance) is captured.
|
||||
# Pick on the node via `profile`; leave `axes` empty to use the profile, or set
|
||||
# `axes` to override entirely.
|
||||
PROFILES = {
|
||||
"general": _BASE_AXES + [
|
||||
"sexual_act", "body_orientation", "limb_arrangement", "penetration",
|
||||
"contact_points", "genital_visibility", "pose",
|
||||
],
|
||||
"oral": _BASE_AXES + [
|
||||
"body_orientation", "mouth_genital_contact", "mouth_genital_distance",
|
||||
"oral_depth", "tongue", "hand_on_shaft", "gaze_up", "genital_visibility", "pose",
|
||||
],
|
||||
"penetration": _BASE_AXES + [
|
||||
"sexual_act", "body_orientation", "limb_arrangement", "insertion_depth",
|
||||
"insertion_angle", "penetration", "contact_points", "genital_visibility", "pose",
|
||||
],
|
||||
"handjob": _BASE_AXES + [
|
||||
"body_orientation", "hand_on_shaft", "grip_style", "stroke_position",
|
||||
"mouth_genital_contact", "gaze_up", "genital_visibility",
|
||||
],
|
||||
"solo": _BASE_AXES + [
|
||||
"self_touch_location", "toy_use", "insertion_depth", "limb_arrangement",
|
||||
"genital_visibility", "pose",
|
||||
],
|
||||
}
|
||||
|
||||
DEFAULT_AXES = ", ".join(PROFILES["general"])
|
||||
|
||||
# Cache loaded (model, processor) keyed by (path, precision) so the loop does not
|
||||
# reload weights every iteration.
|
||||
@@ -607,9 +662,12 @@ class QwenVLImageJudge:
|
||||
# describe = reference only -> target description (first pass, seeds the
|
||||
# initial prompt). compare = ref vs generated -> per-axis scoring.
|
||||
"mode": (["compare", "describe"], {"default": "compare"}),
|
||||
# Analysis profile: act-specialized axis set (distance-aware where it
|
||||
# matters). `axes` below overrides it when non-empty.
|
||||
"profile": (list(PROFILES.keys()), {"default": "general"}),
|
||||
"model_path": ("STRING", {"default": DEFAULT_MODEL_PATH}),
|
||||
"precision": (["bf16", "fp16", "fp8", "nf4"], {"default": "bf16"}),
|
||||
"axes": ("STRING", {"default": DEFAULT_AXES, "multiline": True}),
|
||||
"axes": ("STRING", {"default": "", "multiline": True}),
|
||||
"max_new_tokens": ("INT", {"default": 1024, "min": 64, "max": 4096}),
|
||||
"temperature": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.5, "step": 0.05}),
|
||||
"swap_eval": ("BOOLEAN", {"default": True}),
|
||||
@@ -629,12 +687,13 @@ class QwenVLImageJudge:
|
||||
}
|
||||
|
||||
def judge(self, reference_image, mode, model_path, precision, axes,
|
||||
max_new_tokens, temperature, swap_eval, generated_image=None,
|
||||
max_new_tokens, temperature, swap_eval, profile="general", generated_image=None,
|
||||
keep_loaded=True, auto_download=True,
|
||||
report_dir="", run_tag="", prompt_used="", reference_description=""):
|
||||
# `axes` overrides the profile when provided; otherwise use the profile's axis set.
|
||||
axis_list = [a.strip() for a in re.split(r"[,\n]", axes) if a.strip()]
|
||||
if not axis_list:
|
||||
axis_list = [a.strip() for a in DEFAULT_AXES.split(",")]
|
||||
axis_list = list(PROFILES.get(profile, PROFILES["general"]))
|
||||
|
||||
try:
|
||||
resolved_path = _resolve_model_source(model_path, auto_download)
|
||||
|
||||
Reference in New Issue
Block a user