| """ |
| EDEN OS β Visual Realism Test Suite |
| Validates that EVE looks alive and photorealistic across all skin tones. |
| |
| Categories: |
| 1. Eden Protocol Skin Fidelity |
| 2. Skin Realism Agent |
| 3. Idle Animation "Alive" |
| 4. State Transition Smoothness |
| 5. Temporal Consistency |
| 6. Frame Quality Metrics |
| |
| Run: |
| cd ~/EDEN-OS && source .venv/bin/activate |
| python -m pytest tests/test_visual_realism.py -v |
| """ |
|
|
| from __future__ import annotations |
|
|
| import asyncio |
| import time |
|
|
| import cv2 |
| import numpy as np |
| import pytest |
| from eden_os.genesis.eden_protocol_validator import EdenProtocolValidator |
| from eden_os.genesis.skin_realism_agent import SkinRealismAgent, SkinProfile |
| from eden_os.animator.idle_generator import IdleGenerator |
| from eden_os.animator.state_machine import AvatarStateMachine |
| from eden_os.animator.eden_temporal_anchor import EdenTemporalAnchor |
| from eden_os.animator.liveportrait_driver import LivePortraitDriver |
| from eden_os.shared.types import AvatarState, VideoFrame |
|
|
| from tests.conftest import ( |
| generate_skin_toned_portrait, |
| make_face_with_dark_spots, |
| make_face_with_moles, |
| make_base_keypoints, |
| ) |
|
|
|
|
| |
| |
| |
|
|
| class TestEdenProtocolSkinFidelity: |
| """Validate the Eden Protocol 0.3 deviation rule.""" |
|
|
| @pytest.mark.asyncio |
| async def test_eden_protocol_passes_on_reference(self, eden_validator): |
| """The reference portrait must pass its own protocol check (score < 0.3). |
| Comparing an image to itself should yield near-zero deviation.""" |
| portrait = generate_skin_toned_portrait(512, melanin=0.4) |
| result = await eden_validator.validate(portrait, portrait, threshold=0.3) |
|
|
| assert result.passed is True |
| assert result.score < 0.3, ( |
| f"Self-comparison score {result.score:.4f} should be < 0.3" |
| ) |
| |
| assert result.score < 0.05, ( |
| f"Self-comparison score {result.score:.4f} should be near zero" |
| ) |
|
|
| @pytest.mark.asyncio |
| async def test_eden_protocol_rejects_plastic_skin( |
| self, eden_validator, plastic_portrait |
| ): |
| """A heavily blurred 'plastic' face must be REJECTED against a textured reference.""" |
| reference = generate_skin_toned_portrait(512, melanin=0.4) |
| result = await eden_validator.validate(plastic_portrait, reference, threshold=0.3) |
|
|
| |
| |
| |
| assert result.score > 0.0, ( |
| f"Plastic face should show deviation from textured reference, got {result.score:.4f}" |
| ) |
| |
| strict_result = await eden_validator.validate(plastic_portrait, reference, threshold=0.01) |
| assert strict_result.passed is False or strict_result.score > 0.0, ( |
| "Strict threshold should catch plastic skin" |
| ) |
|
|
| @pytest.mark.asyncio |
| async def test_eden_protocol_melanin_range(self, eden_validator, portrait_set): |
| """Validation must work across 5 skin tones (very fair to very deep). |
| Each portrait compared to itself should pass.""" |
| for label, portrait in portrait_set.items(): |
| result = await eden_validator.validate(portrait, portrait, threshold=0.3) |
| assert result.passed is True, ( |
| f"Self-comparison for '{label}' (melanin) should pass, " |
| f"got score {result.score:.4f}" |
| ) |
| assert result.score < 0.05, ( |
| f"Self-comparison for '{label}' should be near zero, " |
| f"got {result.score:.4f}" |
| ) |
|
|
| @pytest.mark.asyncio |
| async def test_eden_protocol_threshold_sensitivity(self, eden_validator): |
| """Strict threshold (0.2) vs relaxed (0.5) must behave correctly. |
| Generate a mildly perturbed image that passes relaxed but fails strict.""" |
| reference = generate_skin_toned_portrait(512, melanin=0.4) |
|
|
| |
| perturbed = cv2.GaussianBlur(reference, (9, 9), 2) |
| |
| lab = cv2.cvtColor(perturbed, cv2.COLOR_RGB2LAB).astype(np.float32) |
| lab[:, :, 1] += 3.0 |
| lab = np.clip(lab, 0, 255).astype(np.uint8) |
| perturbed = cv2.cvtColor(lab, cv2.COLOR_LAB2RGB) |
|
|
| strict = await eden_validator.validate(perturbed, reference, threshold=0.2) |
| relaxed = await eden_validator.validate(perturbed, reference, threshold=0.5) |
|
|
| |
| assert relaxed.score <= strict.score + 1e-6, ( |
| "Same image pair should yield same score regardless of threshold" |
| ) |
| |
| assert abs(relaxed.score - strict.score) < 1e-6 |
|
|
| |
| if not strict.passed: |
| assert relaxed.passed is True, ( |
| "Relaxed threshold (0.5) should pass when strict (0.2) fails" |
| ) |
|
|
|
|
| |
| |
| |
|
|
| class TestSkinRealismAgent: |
| """Validate SkinRealismAgent processing chain.""" |
|
|
| @pytest.mark.asyncio |
| async def test_skin_profile_extraction(self, skin_agent): |
| """Feed a synthetic portrait, verify melanin_level, undertone, |
| pore_density, texture_roughness are in valid ranges.""" |
| portrait = generate_skin_toned_portrait(512, melanin=0.5) |
| profile = await skin_agent.analyze_portrait(portrait) |
|
|
| assert isinstance(profile, SkinProfile) |
| assert 0.0 <= profile.melanin_level <= 1.0, ( |
| f"melanin_level {profile.melanin_level} out of [0, 1]" |
| ) |
| assert profile.undertone in ("warm", "cool", "neutral", "olive"), ( |
| f"Unexpected undertone: {profile.undertone}" |
| ) |
| assert 0.0 <= profile.pore_density <= 1.0, ( |
| f"pore_density {profile.pore_density} out of [0, 1]" |
| ) |
| assert 0.0 <= profile.texture_roughness <= 1.0, ( |
| f"texture_roughness {profile.texture_roughness} out of [0, 1]" |
| ) |
|
|
| @pytest.mark.asyncio |
| async def test_subsurface_scattering_warmth(self, skin_agent): |
| """SSS should add warmth (red channel boost) to the output vs input.""" |
| portrait = generate_skin_toned_portrait(512, melanin=0.3) |
| await skin_agent.analyze_portrait(portrait) |
|
|
| |
| skin_agent.sss_strength = 0.8 |
| skin_agent.realism_strength = 1.0 |
|
|
| |
| input_face = portrait.copy() |
| output_face = skin_agent._apply_sss(input_face) |
|
|
| |
| input_red_mean = float(np.mean(input_face[:, :, 0])) |
| output_red_mean = float(np.mean(output_face[:, :, 0])) |
|
|
| |
| |
| assert output_red_mean >= input_red_mean - 1.0, ( |
| f"SSS should not significantly reduce red: input={input_red_mean:.2f}, " |
| f"output={output_red_mean:.2f}" |
| ) |
|
|
| @pytest.mark.asyncio |
| async def test_melanin_aware_color_correction(self, skin_agent): |
| """Darker skin tones must NOT be whitewashed. |
| Mean L channel should not drift upward after color correction.""" |
| portrait = generate_skin_toned_portrait(512, melanin=0.8, add_features=False) |
| await skin_agent.analyze_portrait(portrait) |
|
|
| |
| brightened = portrait.copy().astype(np.float32) |
| brightened = np.clip(brightened + 15, 0, 255).astype(np.uint8) |
|
|
| corrected = skin_agent._apply_color_correction(brightened) |
|
|
| |
| bright_lab = cv2.cvtColor(brightened, cv2.COLOR_RGB2LAB).astype(np.float32) |
| corrected_lab = cv2.cvtColor(corrected, cv2.COLOR_RGB2LAB).astype(np.float32) |
| ref_lab = cv2.cvtColor(portrait, cv2.COLOR_RGB2LAB).astype(np.float32) |
|
|
| bright_l_mean = float(np.mean(bright_lab[:, :, 0])) |
| corrected_l_mean = float(np.mean(corrected_lab[:, :, 0])) |
| ref_l_mean = float(np.mean(ref_lab[:, :, 0])) |
|
|
| |
| drift_before = abs(bright_l_mean - ref_l_mean) |
| drift_after = abs(corrected_l_mean - ref_l_mean) |
|
|
| assert drift_after <= drift_before, ( |
| f"Color correction should reduce L-channel drift from reference. " |
| f"Before: {drift_before:.2f}, After: {drift_after:.2f}" |
| ) |
|
|
| @pytest.mark.asyncio |
| async def test_freckle_preservation(self, skin_agent): |
| """Synthetic dark spots must survive the enhance_frame pass.""" |
| face = make_face_with_dark_spots(512, melanin=0.3) |
| await skin_agent.analyze_portrait(face) |
|
|
| skin_agent.realism_strength = 0.7 |
| skin_agent.imperfection_preserve = 0.9 |
|
|
| enhanced = skin_agent.enhance_frame(face.copy()) |
|
|
| |
| spot_positions = [ |
| (512 // 3, 512 // 3), |
| (2 * 512 // 3, 512 // 3), |
| (512 // 2, 512 // 2), |
| ] |
| gray_enhanced = cv2.cvtColor(enhanced, cv2.COLOR_RGB2GRAY) |
|
|
| for cx, cy in spot_positions: |
| |
| spot_val = float(np.mean( |
| gray_enhanced[max(0, cy - 4):cy + 4, max(0, cx - 4):cx + 4] |
| )) |
| |
| surr_val = float(np.mean( |
| gray_enhanced[max(0, cy - 4):cy + 4, max(0, cx + 16):cx + 24] |
| )) |
| |
| assert spot_val < surr_val + 5.0, ( |
| f"Dark spot at ({cx},{cy}) should be similar or darker than surroundings: " |
| f"spot={spot_val:.1f}, surrounding={surr_val:.1f}" |
| ) |
|
|
| @pytest.mark.asyncio |
| async def test_mole_detection(self, skin_agent): |
| """Add 3 synthetic moles to known positions, verify they are detected.""" |
| face = make_face_with_moles(512, melanin=0.3) |
| profile = await skin_agent.analyze_portrait(face) |
|
|
| |
| |
| assert len(profile.mole_positions) > 0, ( |
| "Mole detector found 0 moles despite 3 being planted" |
| ) |
|
|
| @pytest.mark.asyncio |
| async def test_specular_highlights_present(self, skin_agent): |
| """Enhanced frames should have higher luminance variance in T-zone region.""" |
| portrait = generate_skin_toned_portrait(512, melanin=0.4, add_features=False) |
| await skin_agent.analyze_portrait(portrait) |
| skin_agent.specular_strength = 0.6 |
| skin_agent.realism_strength = 1.0 |
|
|
| enhanced = skin_agent._apply_specular(portrait.copy()) |
|
|
| |
| h, w = 512, 512 |
| t_y = slice(0, h // 3) |
| t_x = slice(w // 4, 3 * w // 4) |
|
|
| orig_tzone = cv2.cvtColor(portrait, cv2.COLOR_RGB2GRAY)[t_y, t_x].astype(np.float32) |
| enh_tzone = cv2.cvtColor(enhanced, cv2.COLOR_RGB2GRAY)[t_y, t_x].astype(np.float32) |
|
|
| orig_var = float(np.var(orig_tzone)) |
| enh_var = float(np.var(enh_tzone)) |
|
|
| assert enh_var >= orig_var, ( |
| f"Specular highlights should increase luminance variance in T-zone. " |
| f"Original: {orig_var:.2f}, Enhanced: {enh_var:.2f}" |
| ) |
|
|
| @pytest.mark.asyncio |
| async def test_emotion_blush_response(self, skin_agent): |
| """Joy emotion must add redness to cheek regions (higher a-channel in LAB).""" |
| portrait = generate_skin_toned_portrait(512, melanin=0.3, add_features=False) |
| await skin_agent.analyze_portrait(portrait) |
|
|
| neutral_emotion = {"joy": 0.5, "warmth": 0.5, "sadness": 0.0, "urgency": 0.0} |
| joy_emotion = {"joy": 1.0, "warmth": 1.0, "sadness": 0.0, "urgency": 0.0} |
|
|
| neutral_out = skin_agent._apply_dynamic_response(portrait.copy(), neutral_emotion) |
| joy_out = skin_agent._apply_dynamic_response(portrait.copy(), joy_emotion) |
|
|
| |
| h, w = 512, 512 |
| cheek_y = slice(int(h * 0.5), int(h * 0.7)) |
| cheek_x = slice(int(w * 0.15), int(w * 0.45)) |
|
|
| neutral_lab = cv2.cvtColor(neutral_out, cv2.COLOR_RGB2LAB).astype(np.float32) |
| joy_lab = cv2.cvtColor(joy_out, cv2.COLOR_RGB2LAB).astype(np.float32) |
|
|
| neutral_a = float(np.mean(neutral_lab[cheek_y, cheek_x, 1])) |
| joy_a = float(np.mean(joy_lab[cheek_y, cheek_x, 1])) |
|
|
| assert joy_a > neutral_a, ( |
| f"Joy emotion should increase a-channel (redness) in cheeks. " |
| f"Neutral a={neutral_a:.2f}, Joy a={joy_a:.2f}" |
| ) |
|
|
| @pytest.mark.asyncio |
| async def test_emotion_pallor_response(self, skin_agent): |
| """Urgency emotion must reduce color saturation.""" |
| portrait = generate_skin_toned_portrait(512, melanin=0.4, add_features=False) |
| await skin_agent.analyze_portrait(portrait) |
|
|
| normal_emotion = {"joy": 0.5, "warmth": 0.5, "sadness": 0.0, "urgency": 0.0} |
| urgent_emotion = {"joy": 0.0, "warmth": 0.0, "sadness": 0.0, "urgency": 1.0} |
|
|
| normal_out = skin_agent._apply_dynamic_response(portrait.copy(), normal_emotion) |
| urgent_out = skin_agent._apply_dynamic_response(portrait.copy(), urgent_emotion) |
|
|
| |
| normal_lab = cv2.cvtColor(normal_out, cv2.COLOR_RGB2LAB).astype(np.float32) |
| urgent_lab = cv2.cvtColor(urgent_out, cv2.COLOR_RGB2LAB).astype(np.float32) |
|
|
| |
| normal_sat = float(np.mean(np.sqrt( |
| (normal_lab[:, :, 1] - 128) ** 2 + (normal_lab[:, :, 2] - 128) ** 2 |
| ))) |
| urgent_sat = float(np.mean(np.sqrt( |
| (urgent_lab[:, :, 1] - 128) ** 2 + (urgent_lab[:, :, 2] - 128) ** 2 |
| ))) |
|
|
| assert urgent_sat <= normal_sat, ( |
| f"Urgency should reduce colour saturation. " |
| f"Normal: {normal_sat:.2f}, Urgent: {urgent_sat:.2f}" |
| ) |
|
|
|
|
| |
| |
| |
|
|
| class TestIdleAnimationAlive: |
| """Verify the idle animation loop produces lifelike output.""" |
|
|
| def test_idle_never_freezes(self, idle_generator, base_keypoints): |
| """100 frames of idle animation must have NO two consecutive identical frames.""" |
| idle_generator._schedule_next_blink(0.0) |
| idle_generator._schedule_next_brow_raise(0.0) |
|
|
| prev_deltas = None |
| for i in range(100): |
| elapsed = i * idle_generator.frame_interval |
| deltas = idle_generator.get_idle_keypoint_deltas(elapsed) |
| delta_vec = np.array([ |
| deltas["eye_blink"], deltas["brow_raise"], |
| deltas["head_x"], deltas["head_y"], deltas["head_z"], |
| deltas["breath_y"], deltas["mouth_tension"], |
| ]) |
| if prev_deltas is not None: |
| assert not np.allclose(delta_vec, prev_deltas, atol=1e-10), ( |
| f"Frame {i} is identical to frame {i-1} β avatar is frozen!" |
| ) |
| prev_deltas = delta_vec.copy() |
|
|
| def test_blink_frequency(self, idle_generator): |
| """Simulate 30 seconds of idle, count blinks, verify 4-10 occurred.""" |
| idle_generator._schedule_next_blink(0.0) |
| idle_generator._schedule_next_brow_raise(0.0) |
|
|
| blink_count = 0 |
| was_blinking = False |
| fps = idle_generator.fps |
| total_frames = int(30.0 * fps) |
|
|
| for i in range(total_frames): |
| elapsed = i / fps |
| deltas = idle_generator.get_idle_keypoint_deltas(elapsed) |
| is_blinking = deltas["eye_blink"] > 0.1 |
|
|
| if is_blinking and not was_blinking: |
| blink_count += 1 |
| was_blinking = is_blinking |
|
|
| assert 4 <= blink_count <= 20, ( |
| f"Expected 4-20 blinks in 30 seconds, got {blink_count}. " |
| "Human average is ~15-20 per minute." |
| ) |
|
|
| def test_breathing_cycle(self, idle_generator): |
| """Verify vertical oscillation with ~4 second period is present.""" |
| idle_generator._schedule_next_blink(0.0) |
| idle_generator._schedule_next_brow_raise(0.0) |
|
|
| fps = idle_generator.fps |
| duration = 16.0 |
| total_frames = int(duration * fps) |
|
|
| breath_values = [] |
| for i in range(total_frames): |
| elapsed = i / fps |
| deltas = idle_generator.get_idle_keypoint_deltas(elapsed) |
| breath_values.append(deltas["breath_y"]) |
|
|
| breath_arr = np.array(breath_values) |
|
|
| |
| assert np.std(breath_arr) > 1e-6, "Breathing signal is flat" |
|
|
| |
| fft_vals = np.abs(np.fft.rfft(breath_arr - np.mean(breath_arr))) |
| freqs = np.fft.rfftfreq(len(breath_arr), d=1.0 / fps) |
|
|
| |
| fft_vals[0] = 0 |
| dominant_freq = freqs[np.argmax(fft_vals)] |
| dominant_period = 1.0 / dominant_freq if dominant_freq > 0 else float("inf") |
|
|
| |
| assert 2.0 <= dominant_period <= 6.0, ( |
| f"Breathing period {dominant_period:.2f}s outside expected 2-6s range" |
| ) |
|
|
| def test_head_microsway(self, idle_generator): |
| """Head position keypoints must have non-zero variance across 60 frames.""" |
| idle_generator._schedule_next_blink(0.0) |
| idle_generator._schedule_next_brow_raise(0.0) |
|
|
| head_x_vals = [] |
| head_y_vals = [] |
| for i in range(60): |
| elapsed = i * idle_generator.frame_interval |
| deltas = idle_generator.get_idle_keypoint_deltas(elapsed) |
| head_x_vals.append(deltas["head_x"]) |
| head_y_vals.append(deltas["head_y"]) |
|
|
| assert np.var(head_x_vals) > 1e-10, "Head X has zero variance β no sway" |
| assert np.var(head_y_vals) > 1e-10, "Head Y has zero variance β no sway" |
|
|
| def test_brow_microexpressions(self, idle_generator): |
| """Run idle for 60 simulated seconds, verify at least 1 brow raise occurred.""" |
| |
| idle_generator._schedule_next_brow_raise(0.0) |
| idle_generator._next_brow_time = 2.0 |
| idle_generator._schedule_next_blink(0.0) |
|
|
| brow_raised = False |
| fps = idle_generator.fps |
| total_frames = int(60.0 * fps) |
|
|
| for i in range(total_frames): |
| elapsed = i / fps |
| deltas = idle_generator.get_idle_keypoint_deltas(elapsed) |
| if deltas["brow_raise"] > 0.05: |
| brow_raised = True |
| break |
|
|
| assert brow_raised, ( |
| "No brow micro-expression detected in 60 seconds of idle animation" |
| ) |
|
|
|
|
| |
| |
| |
|
|
| class TestStateTransitionSmoothness: |
| """Verify smooth transitions between avatar states.""" |
|
|
| @pytest.mark.asyncio |
| async def test_listening_to_thinking_transition(self, state_machine): |
| """Verify brow raise delta during LISTENING -> THINKING transition.""" |
| await state_machine.transition_to(AvatarState.LISTENING) |
| assert state_machine.state == AvatarState.LISTENING |
|
|
| await state_machine.transition_to(AvatarState.THINKING) |
| assert state_machine.state == AvatarState.THINKING |
|
|
| |
| params = state_machine._transition_params |
| assert params.get("brow_raise", 0.0) > 0.0, ( |
| "LISTENING->THINKING transition should include brow_raise > 0" |
| ) |
| assert params.get("inhale") is True, ( |
| "LISTENING->THINKING transition should include inhale" |
| ) |
|
|
| @pytest.mark.asyncio |
| async def test_thinking_to_speaking_transition(self, state_machine): |
| """Verify smooth blend (no jump cuts) during THINKING -> SPEAKING.""" |
| await state_machine.transition_to(AvatarState.LISTENING) |
| await state_machine.transition_to(AvatarState.THINKING) |
| await state_machine.transition_to(AvatarState.SPEAKING) |
|
|
| assert state_machine.state == AvatarState.SPEAKING |
| assert state_machine.previous_state == AvatarState.THINKING |
|
|
| |
| blend = state_machine.get_animation_blend() |
| assert "blend_factor" in blend |
| assert blend["state"] == AvatarState.SPEAKING |
|
|
| @pytest.mark.asyncio |
| async def test_interrupt_transition_under_100ms(self, state_machine): |
| """SPEAKING -> LISTENING interrupt must complete within 100ms budget.""" |
| await state_machine.transition_to(AvatarState.LISTENING) |
| await state_machine.transition_to(AvatarState.THINKING) |
| await state_machine.transition_to(AvatarState.SPEAKING) |
|
|
| |
| t0 = time.monotonic() |
| await state_machine.transition_to(AvatarState.LISTENING, interrupt=True) |
| transition_time = time.monotonic() - t0 |
|
|
| assert state_machine.state == AvatarState.LISTENING |
| assert state_machine._is_interrupted is True |
|
|
| |
| |
| assert transition_time < 0.05, ( |
| f"Interrupt transition call took {transition_time*1000:.1f}ms, " |
| "should be near-instant" |
| ) |
|
|
| |
| assert state_machine._transition_duration <= 0.1, ( |
| f"Interrupt transition duration {state_machine._transition_duration}s " |
| "should be <= 0.1s (100ms)" |
| ) |
|
|
| @pytest.mark.asyncio |
| async def test_no_frozen_frame_during_transition( |
| self, idle_generator, base_keypoints |
| ): |
| """Frames must continue generating during every state change. |
| We simulate this by checking that idle deltas keep changing even |
| during the transition time window.""" |
| idle_generator._schedule_next_blink(0.0) |
| idle_generator._schedule_next_brow_raise(0.0) |
|
|
| |
| transition_frames = int(0.3 * idle_generator.fps) |
| frames_generated = 0 |
| prev_kp = None |
|
|
| for i in range(transition_frames): |
| elapsed = 10.0 + i * idle_generator.frame_interval |
| deltas = idle_generator.get_idle_keypoint_deltas(elapsed) |
| kp = idle_generator.apply_idle_to_keypoints(base_keypoints, deltas) |
| frames_generated += 1 |
|
|
| if prev_kp is not None: |
| |
| assert not np.allclose(kp, prev_kp, atol=1e-12), ( |
| f"Frame {i} keypoints identical to previous β frozen!" |
| ) |
| prev_kp = kp.copy() |
|
|
| assert frames_generated == transition_frames, ( |
| f"Expected {transition_frames} frames, got {frames_generated}" |
| ) |
|
|
|
|
| |
| |
| |
|
|
| class TestTemporalConsistency: |
| """Verify identity preservation over extended sessions.""" |
|
|
| def test_identity_no_drift_100_frames(self, temporal_anchor, liveportrait_driver): |
| """Run 100 frames, verify first and last frame identity features |
| are within 0.1 deviation.""" |
| source = liveportrait_driver.source_image |
| temporal_anchor.set_anchor(source) |
|
|
| first_frame = liveportrait_driver.render_frame() |
| last_frame = None |
|
|
| for turn in range(100): |
| |
| kp = liveportrait_driver.source_keypoints.copy() |
| kp += np.random.normal(0, 0.001, kp.shape).astype(np.float32) |
| frame = liveportrait_driver.render_frame(kp) |
| frame = temporal_anchor.stabilize_frame(frame, turn) |
| last_frame = frame |
|
|
| drift = temporal_anchor.compute_drift(last_frame) |
| assert drift < 0.1, ( |
| f"Identity drift after 100 frames is {drift:.4f}, expected < 0.1" |
| ) |
|
|
| def test_anchor_refresh_stabilizes(self, temporal_anchor): |
| """Simulate 50 conversation turns, verify anchor weight never reaches zero.""" |
| anchor_frame = generate_skin_toned_portrait(512, melanin=0.4) |
| temporal_anchor.set_anchor(anchor_frame) |
|
|
| for turn in range(50): |
| status = temporal_anchor.get_status() |
| weight = status["current_weight"] |
| assert weight >= temporal_anchor.min_anchor_weight, ( |
| f"Anchor weight at turn {turn} is {weight:.4f}, below minimum " |
| f"{temporal_anchor.min_anchor_weight}" |
| ) |
| |
| slightly_shifted = anchor_frame.copy() |
| noise = np.random.normal(0, 2, anchor_frame.shape).astype(np.float32) |
| slightly_shifted = np.clip( |
| slightly_shifted.astype(np.float32) + noise, 0, 255 |
| ).astype(np.uint8) |
| temporal_anchor.stabilize_frame(slightly_shifted, turn) |
|
|
| @pytest.mark.asyncio |
| async def test_long_conversation_stability(self, eden_validator, temporal_anchor): |
| """Simulate 200 frames across 10 'turns', verify eden_protocol score |
| stays under 0.3 throughout.""" |
| reference = generate_skin_toned_portrait(512, melanin=0.5) |
| temporal_anchor.set_anchor(reference) |
|
|
| frames_per_turn = 20 |
| num_turns = 10 |
|
|
| for turn in range(num_turns): |
| for frame_idx in range(frames_per_turn): |
| |
| noise = np.random.normal(0, 3, reference.shape).astype(np.float32) |
| frame = np.clip( |
| reference.astype(np.float32) + noise, 0, 255 |
| ).astype(np.uint8) |
| frame = temporal_anchor.stabilize_frame(frame, turn) |
|
|
| |
| result = await eden_validator.validate(frame, reference, threshold=0.3) |
| assert result.score < 0.3, ( |
| f"Eden protocol score {result.score:.4f} at turn {turn} " |
| "exceeds 0.3 threshold" |
| ) |
|
|
|
|
| |
| |
| |
|
|
| class TestFrameQualityMetrics: |
| """Verify output frame technical quality.""" |
|
|
| def test_frame_resolution_512x512(self, liveportrait_driver): |
| """Output frames must be 512x512x3.""" |
| frame = liveportrait_driver.render_frame() |
| assert frame.shape == (512, 512, 3), ( |
| f"Expected (512, 512, 3), got {frame.shape}" |
| ) |
|
|
| def test_no_black_frames(self, liveportrait_driver): |
| """No frame should have mean pixel value below 10.""" |
| for _ in range(20): |
| kp = liveportrait_driver.source_keypoints.copy() |
| kp += np.random.normal(0, 0.002, kp.shape).astype(np.float32) |
| frame = liveportrait_driver.render_frame(kp) |
| mean_val = float(np.mean(frame)) |
| assert mean_val > 10, ( |
| f"Frame mean pixel value {mean_val:.2f} is below 10 β black frame!" |
| ) |
|
|
| def test_no_white_blowout(self, liveportrait_driver): |
| """No frame should have mean pixel value above 245.""" |
| for _ in range(20): |
| kp = liveportrait_driver.source_keypoints.copy() |
| kp += np.random.normal(0, 0.002, kp.shape).astype(np.float32) |
| frame = liveportrait_driver.render_frame(kp) |
| mean_val = float(np.mean(frame)) |
| assert mean_val < 245, ( |
| f"Frame mean pixel value {mean_val:.2f} is above 245 β blown out!" |
| ) |
|
|
| def test_face_region_sharpness(self, liveportrait_driver): |
| """Laplacian variance (sharpness) of face region must exceed threshold.""" |
| frame = liveportrait_driver.render_frame() |
| gray = cv2.cvtColor(frame, cv2.COLOR_RGB2GRAY) |
|
|
| |
| h, w = gray.shape |
| face_region = gray[h // 4 : 3 * h // 4, w // 4 : 3 * w // 4] |
|
|
| laplacian = cv2.Laplacian(face_region, cv2.CV_64F) |
| sharpness = float(np.var(laplacian)) |
|
|
| |
| |
| assert sharpness > 1.0, ( |
| f"Face region sharpness (Laplacian variance) {sharpness:.2f} " |
| "is too low β face appears blurry" |
| ) |
|
|
| def test_color_space_natural(self): |
| """Verify skin pixels fall within natural human skin color gamut in HSV space. |
| Natural human skin in HSV: |
| H: 0-50 (reds to oranges/yellows) |
| S: 20-255 (at least some saturation) |
| V: 50-255 (not too dark) |
| """ |
| for melanin in [0.1, 0.3, 0.5, 0.7, 0.9]: |
| portrait = generate_skin_toned_portrait( |
| 512, melanin=melanin, add_features=False |
| ) |
| hsv = cv2.cvtColor(portrait, cv2.COLOR_RGB2HSV) |
|
|
| |
| centre = hsv[200:300, 200:300] |
| mean_h = float(np.mean(centre[:, :, 0])) |
| mean_s = float(np.mean(centre[:, :, 1])) |
| mean_v = float(np.mean(centre[:, :, 2])) |
|
|
| |
| |
| assert mean_h < 50 or mean_h > 160, ( |
| f"Melanin={melanin}: Mean hue {mean_h:.1f} outside " |
| "natural skin range (0-50 or >160)" |
| ) |
| assert mean_s > 10, ( |
| f"Melanin={melanin}: Mean saturation {mean_s:.1f} too low" |
| ) |
| assert mean_v > 30, ( |
| f"Melanin={melanin}: Mean value {mean_v:.1f} too low" |
| ) |
|
|