- Frontend v4 accessible sur réseau local (192.168.1.40) - Ports ouverts: 3002 (frontend), 5001 (backend), 5004 (dashboard) - Ollama GPU fonctionnel - Self-healing interactif - Dashboard confiance Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
275 lines
9.0 KiB
Python
275 lines
9.0 KiB
Python
"""
|
|
Tests pour ROI Optimizer
|
|
|
|
Valide:
|
|
- Redimensionnement intelligent des screenshots
|
|
- Détection rapide des ROIs
|
|
- Cache des résultats
|
|
- Fusion des ROIs qui se chevauchent
|
|
"""
|
|
|
|
import pytest
|
|
import numpy as np
|
|
import cv2
|
|
from pathlib import Path
|
|
import tempfile
|
|
import shutil
|
|
|
|
from core.detection.roi_optimizer import (
|
|
ROIOptimizer,
|
|
ROICache,
|
|
ROI,
|
|
OptimizedFrame
|
|
)
|
|
|
|
|
|
class TestROIOptimizer:
|
|
"""Tests pour l'optimiseur ROI"""
|
|
|
|
def setup_method(self):
|
|
"""Setup avant chaque test"""
|
|
self.temp_dir = Path(tempfile.mkdtemp())
|
|
self.optimizer = ROIOptimizer(
|
|
max_width=1920,
|
|
max_height=1080,
|
|
enable_cache=True,
|
|
cache_size=10
|
|
)
|
|
|
|
def teardown_method(self):
|
|
"""Cleanup après chaque test"""
|
|
if self.temp_dir.exists():
|
|
shutil.rmtree(self.temp_dir)
|
|
|
|
def _create_test_image(self, width: int, height: int) -> str:
|
|
"""Créer une image de test"""
|
|
# Créer une image avec quelques formes
|
|
image = np.ones((height, width, 3), dtype=np.uint8) * 255
|
|
|
|
# Ajouter des rectangles (simulent des boutons)
|
|
cv2.rectangle(image, (50, 50), (150, 100), (0, 0, 255), -1)
|
|
cv2.rectangle(image, (200, 50), (300, 100), (0, 255, 0), -1)
|
|
cv2.rectangle(image, (50, 150), (150, 200), (255, 0, 0), -1)
|
|
|
|
# Ajouter du texte
|
|
cv2.putText(image, "Button 1", (60, 80), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255), 1)
|
|
cv2.putText(image, "Button 2", (210, 80), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255), 1)
|
|
|
|
# Sauvegarder
|
|
path = self.temp_dir / f"test_{width}x{height}.png"
|
|
cv2.imwrite(str(path), image)
|
|
|
|
return str(path)
|
|
|
|
def test_resize_small_image(self):
|
|
"""Test redimensionnement d'une petite image (pas de resize)"""
|
|
# Créer une petite image
|
|
image_path = self._create_test_image(800, 600)
|
|
|
|
# Optimiser
|
|
optimized = self.optimizer.optimize_frame(image_path)
|
|
|
|
# Vérifier qu'il n'y a pas eu de redimensionnement
|
|
assert optimized.scale_factor == 1.0
|
|
assert optimized.resized_size == (800, 600)
|
|
assert optimized.original_size == (800, 600)
|
|
|
|
def test_resize_large_image(self):
|
|
"""Test redimensionnement d'une grande image"""
|
|
# Créer une grande image
|
|
image_path = self._create_test_image(2560, 1440)
|
|
|
|
# Optimiser
|
|
optimized = self.optimizer.optimize_frame(image_path)
|
|
|
|
# Vérifier qu'il y a eu redimensionnement
|
|
assert optimized.scale_factor < 1.0
|
|
assert optimized.resized_size[0] <= 1920
|
|
assert optimized.resized_size[1] <= 1080
|
|
assert optimized.original_size == (2560, 1440)
|
|
|
|
def test_roi_detection(self):
|
|
"""Test détection des ROIs"""
|
|
# Créer une image avec des éléments
|
|
image_path = self._create_test_image(800, 600)
|
|
|
|
# Optimiser
|
|
optimized = self.optimizer.optimize_frame(image_path)
|
|
|
|
# Vérifier qu'on a détecté des ROIs
|
|
assert len(optimized.rois) > 0
|
|
|
|
# Vérifier que les ROIs sont valides
|
|
for roi in optimized.rois:
|
|
assert roi.x >= 0
|
|
assert roi.y >= 0
|
|
assert roi.w > 0
|
|
assert roi.h > 0
|
|
assert 0.0 <= roi.confidence <= 1.0
|
|
assert roi.roi_type in ["contour", "text", "merged", "full_frame"]
|
|
|
|
def test_cache_hit(self):
|
|
"""Test cache hit sur même image"""
|
|
# Créer une image
|
|
image_path = self._create_test_image(800, 600)
|
|
|
|
# Premier appel (cache miss)
|
|
optimized1 = self.optimizer.optimize_frame(image_path)
|
|
cache_stats1 = self.optimizer.cache.get_stats()
|
|
assert cache_stats1["misses"] == 1
|
|
assert cache_stats1["hits"] == 0
|
|
|
|
# Deuxième appel (cache hit)
|
|
optimized2 = self.optimizer.optimize_frame(image_path)
|
|
cache_stats2 = self.optimizer.cache.get_stats()
|
|
assert cache_stats2["hits"] == 1
|
|
assert cache_stats2["misses"] == 1
|
|
|
|
# Vérifier que les ROIs sont les mêmes
|
|
assert len(optimized1.rois) == len(optimized2.rois)
|
|
|
|
def test_cache_miss_different_images(self):
|
|
"""Test cache miss sur images différentes"""
|
|
# Créer deux images différentes
|
|
image_path1 = self._create_test_image(800, 600)
|
|
image_path2 = self._create_test_image(1024, 768)
|
|
|
|
# Premier appel
|
|
self.optimizer.optimize_frame(image_path1)
|
|
|
|
# Deuxième appel avec image différente
|
|
self.optimizer.optimize_frame(image_path2)
|
|
|
|
# Vérifier les stats
|
|
cache_stats = self.optimizer.cache.get_stats()
|
|
assert cache_stats["misses"] == 2
|
|
assert cache_stats["hits"] == 0
|
|
|
|
def test_scale_coordinates(self):
|
|
"""Test conversion de coordonnées"""
|
|
# Test avec scale_factor = 0.5
|
|
x_orig, y_orig = self.optimizer.scale_coordinates(100, 200, 0.5)
|
|
assert x_orig == 200
|
|
assert y_orig == 400
|
|
|
|
# Test avec scale_factor = 1.0 (pas de scaling)
|
|
x_orig, y_orig = self.optimizer.scale_coordinates(100, 200, 1.0)
|
|
assert x_orig == 100
|
|
assert y_orig == 200
|
|
|
|
def test_roi_merge(self):
|
|
"""Test fusion de ROIs qui se chevauchent"""
|
|
# Créer des ROIs qui se chevauchent fortement
|
|
roi1 = ROI(x=10, y=10, w=50, h=50, confidence=0.9, roi_type="contour")
|
|
roi2 = ROI(x=20, y=20, w=50, h=50, confidence=0.8, roi_type="contour") # Chevauchement plus important
|
|
roi3 = ROI(x=200, y=200, w=50, h=50, confidence=0.9, roi_type="contour")
|
|
|
|
rois = [roi1, roi2, roi3]
|
|
|
|
# Fusionner avec seuil bas
|
|
merged = self.optimizer._merge_overlapping_rois(rois, iou_threshold=0.2)
|
|
|
|
# Devrait avoir fusionné roi1 et roi2, mais pas roi3
|
|
assert len(merged) == 2
|
|
|
|
# Vérifier qu'un des ROIs fusionnés contient les deux originaux
|
|
merged_areas = [r.w * r.h for r in merged]
|
|
# Le ROI fusionné devrait être plus grand que les originaux
|
|
assert any(area > 2500 for area in merged_areas) # 50*50 = 2500
|
|
|
|
def test_stats(self):
|
|
"""Test statistiques de l'optimiseur"""
|
|
# Créer et traiter quelques images
|
|
for i in range(3):
|
|
image_path = self._create_test_image(800 + i * 100, 600)
|
|
self.optimizer.optimize_frame(image_path)
|
|
|
|
# Obtenir les stats
|
|
stats = self.optimizer.get_stats()
|
|
|
|
# Vérifier
|
|
assert stats["total_frames_processed"] == 3
|
|
assert "avg_processing_time_ms" in stats
|
|
assert "cache" in stats
|
|
assert stats["cache"]["size"] == 3
|
|
|
|
|
|
class TestROICache:
|
|
"""Tests pour le cache ROI"""
|
|
|
|
def test_cache_put_get(self):
|
|
"""Test ajout et récupération"""
|
|
cache = ROICache(max_size=5)
|
|
|
|
# Créer une image de test
|
|
image = np.random.randint(0, 255, (100, 100, 3), dtype=np.uint8)
|
|
|
|
# Créer des ROIs
|
|
rois = [
|
|
ROI(x=10, y=10, w=50, h=50, confidence=0.9, roi_type="test")
|
|
]
|
|
|
|
# Ajouter au cache
|
|
cache.put(image, rois, processing_time=0.1)
|
|
|
|
# Récupérer
|
|
cached_rois = cache.get(image)
|
|
|
|
# Vérifier
|
|
assert cached_rois is not None
|
|
assert len(cached_rois) == 1
|
|
assert cached_rois[0].x == 10
|
|
|
|
def test_cache_miss(self):
|
|
"""Test cache miss"""
|
|
cache = ROICache(max_size=5)
|
|
|
|
# Créer une image
|
|
image = np.random.randint(0, 255, (100, 100, 3), dtype=np.uint8)
|
|
|
|
# Essayer de récupérer (devrait être None)
|
|
cached_rois = cache.get(image)
|
|
|
|
assert cached_rois is None
|
|
|
|
def test_cache_eviction(self):
|
|
"""Test éviction LRU"""
|
|
cache = ROICache(max_size=3)
|
|
|
|
# Ajouter 4 images (devrait évincer la première)
|
|
for i in range(4):
|
|
image = np.ones((100, 100, 3), dtype=np.uint8) * i
|
|
rois = [ROI(x=i, y=i, w=10, h=10, confidence=0.9, roi_type="test")]
|
|
cache.put(image, rois)
|
|
|
|
# Vérifier la taille
|
|
assert len(cache.cache) == 3
|
|
|
|
def test_cache_stats(self):
|
|
"""Test statistiques du cache"""
|
|
cache = ROICache(max_size=5)
|
|
|
|
# Créer une image
|
|
image = np.random.randint(0, 255, (100, 100, 3), dtype=np.uint8)
|
|
rois = [ROI(x=10, y=10, w=50, h=50, confidence=0.9, roi_type="test")]
|
|
|
|
# Miss
|
|
cache.get(image)
|
|
|
|
# Put
|
|
cache.put(image, rois, processing_time=0.1)
|
|
|
|
# Hit
|
|
cache.get(image)
|
|
|
|
# Vérifier stats
|
|
stats = cache.get_stats()
|
|
assert stats["hits"] == 1
|
|
assert stats["misses"] == 1
|
|
assert stats["hit_rate"] == 0.5
|
|
assert stats["total_time_saved_ms"] == 100.0 # 0.1s = 100ms
|
|
|
|
|
|
if __name__ == "__main__":
|
|
pytest.main([__file__, "-v"])
|