Full-stack application for predicting where humans look in images using DeepGaze saliency models. Includes heatmap overlays, gaze sequence prediction, hotspot detection, AOI analysis, rule-based insights, optional Claude AI design analysis, and professional PDF report generation. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
94 lines
3.5 KiB
Python
94 lines
3.5 KiB
Python
import numpy as np
|
|
import pytest
|
|
|
|
from app.services.gaze_sequence import extract_gaze_sequence
|
|
from app.services.aoi_analysis import compute_aoi_attention
|
|
from app.services.heatmap import generate_heatmap_overlay, generate_standalone_heatmap
|
|
from app.services.image_processing import prepare_for_inference, upscale_saliency
|
|
from PIL import Image
|
|
|
|
|
|
class TestGazeSequence:
|
|
def test_extracts_correct_number_of_fixations(self):
|
|
saliency = np.random.rand(100, 100)
|
|
result = extract_gaze_sequence(saliency, num_fixations=5)
|
|
assert len(result) == 5
|
|
|
|
def test_fixations_are_ranked(self):
|
|
saliency = np.random.rand(100, 100)
|
|
result = extract_gaze_sequence(saliency, num_fixations=3)
|
|
ranks = [p["rank"] for p in result]
|
|
assert ranks == [1, 2, 3]
|
|
|
|
def test_first_fixation_is_at_peak(self):
|
|
saliency = np.zeros((100, 100))
|
|
saliency[50, 75] = 1.0 # Set a clear peak
|
|
result = extract_gaze_sequence(saliency, num_fixations=1)
|
|
assert result[0]["x"] == 75
|
|
assert result[0]["y"] == 50
|
|
|
|
def test_coordinates_have_percentages(self):
|
|
saliency = np.random.rand(200, 300)
|
|
result = extract_gaze_sequence(saliency, num_fixations=1)
|
|
assert 0 <= result[0]["x_pct"] <= 100
|
|
assert 0 <= result[0]["y_pct"] <= 100
|
|
|
|
|
|
class TestAOIAnalysis:
|
|
def test_full_image_aoi_gets_100_percent(self):
|
|
saliency = np.ones((100, 100))
|
|
regions = [{"x": 0, "y": 0, "width": 100, "height": 100}]
|
|
result = compute_aoi_attention(saliency, regions)
|
|
assert abs(result[0]["attention_pct"] - 100.0) < 0.1
|
|
|
|
def test_half_image_aoi(self):
|
|
saliency = np.ones((100, 100))
|
|
regions = [{"x": 0, "y": 0, "width": 50, "height": 100}]
|
|
result = compute_aoi_attention(saliency, regions)
|
|
assert abs(result[0]["attention_pct"] - 50.0) < 1.0
|
|
|
|
def test_density_calculation(self):
|
|
saliency = np.zeros((100, 100))
|
|
saliency[0:10, 0:10] = 1.0 # High saliency in small region
|
|
regions = [{"x": 0, "y": 0, "width": 10, "height": 10}]
|
|
result = compute_aoi_attention(saliency, regions)
|
|
assert result[0]["attention_density"] > 1.0
|
|
|
|
def test_zero_saliency(self):
|
|
saliency = np.zeros((100, 100))
|
|
regions = [{"x": 0, "y": 0, "width": 50, "height": 50}]
|
|
result = compute_aoi_attention(saliency, regions)
|
|
assert result[0]["attention_pct"] == 0.0
|
|
|
|
|
|
class TestHeatmap:
|
|
def test_generates_overlay(self):
|
|
img = Image.new("RGB", (100, 100), color="white")
|
|
saliency = np.random.rand(100, 100)
|
|
result = generate_heatmap_overlay(img, saliency)
|
|
assert result.size == (100, 100)
|
|
assert result.mode == "RGB"
|
|
|
|
def test_generates_standalone(self):
|
|
saliency = np.random.rand(100, 100)
|
|
result = generate_standalone_heatmap(saliency)
|
|
assert result.size == (100, 100)
|
|
|
|
|
|
class TestImageProcessing:
|
|
def test_resize_large_image(self):
|
|
img = Image.new("RGB", (2000, 1000))
|
|
resized, scale = prepare_for_inference(img)
|
|
assert max(resized.size) <= 1024
|
|
assert scale < 1.0
|
|
|
|
def test_no_resize_small_image(self):
|
|
img = Image.new("RGB", (500, 300))
|
|
resized, scale = prepare_for_inference(img)
|
|
assert resized.size == (500, 300)
|
|
assert scale == 1.0
|
|
|
|
def test_upscale_saliency(self):
|
|
saliency = np.random.rand(50, 50)
|
|
result = upscale_saliency(saliency, 200, 300)
|
|
assert result.shape == (200, 300)
|