"""Validation methods for biological accuracy verification."""
import numpy as np
import cv2
from typing import List, Dict, Optional
[docs]
class ValidationMixin:
"""
Mixin class for validation methods.
Validates filter outputs against published biological data
on cat vision characteristics.
"""
[docs]
def validate_biological_accuracy(
self,
test_images: List[np.ndarray],
ground_truth_data: Optional[Dict] = None
) -> Dict:
"""
Validate biological accuracy against published cat vision characteristics.
Compares filter behavior with peer-reviewed research on:
- Spectral sensitivity curves
- Spatial acuity measurements
- Temporal response characteristics
- Motion detection capabilities
Args:
test_images: List of test images for validation
ground_truth_data: Optional ground truth data for comparison
Returns:
Validation results dictionary with accuracy scores
"""
results = {
'spectral_sensitivity_validation': self._validate_spectral_sensitivity(),
'spatial_acuity_validation': self._validate_spatial_acuity(test_images),
'temporal_response_validation': self._validate_temporal_response(),
'motion_detection_validation': self._validate_motion_detection(test_images),
'overall_accuracy_score': 0.0
}
# Calculate overall accuracy score (exclude overall_accuracy_score itself)
scores = [v for k, v in results.items()
if isinstance(v, (int, float)) and k != 'overall_accuracy_score']
if scores:
results['overall_accuracy_score'] = np.mean(scores)
return results
def _validate_spectral_sensitivity(self) -> float:
"""
Validate spectral sensitivity curves against biological data.
Compares peak wavelengths against published values:
- S-cone peak: 450nm (±10nm tolerance)
- L-cone peak: 556nm (±15nm tolerance)
- Rod peak: 498nm (±10nm tolerance)
Returns:
Accuracy score (0.0 to 1.0)
"""
# Expected peak wavelengths from literature
expected_s_peak = 450 # ±10nm
expected_l_peak = 556 # ±15nm
expected_rod_peak = 498 # ±10nm
# Calculate accuracy based on peak positions
s_accuracy = 1.0 - min(abs(self.s_cone_peak - expected_s_peak) / 10.0, 1.0)
l_accuracy = 1.0 - min(abs(self.l_cone_peak - expected_l_peak) / 15.0, 1.0)
rod_accuracy = 1.0 - min(abs(self.rod_peak - expected_rod_peak) / 10.0, 1.0)
return np.mean([s_accuracy, l_accuracy, rod_accuracy])
def _validate_spatial_acuity(self, test_images: List[np.ndarray]) -> float:
"""
Validate spatial acuity reduction against behavioral measurements.
Validates that spatial acuity reduction matches the expected
1/6 ratio compared to human vision.
Args:
test_images: List of test images
Returns:
Accuracy score (0.0 to 1.0)
"""
if not test_images:
return 0.0
# Test acuity reduction on a sample image
test_image = test_images[0]
gray_test = (
cv2.cvtColor(test_image, cv2.COLOR_BGR2GRAY)
if len(test_image.shape) == 3
else test_image
)
original_fft = np.fft.fft2(gray_test)
# Apply acuity reduction
acuity_reduced = self.simulate_spatial_acuity_reduction(test_image)
gray_reduced = (
cv2.cvtColor(acuity_reduced, cv2.COLOR_BGR2GRAY)
if len(acuity_reduced.shape) == 3
else acuity_reduced
)
reduced_fft = np.fft.fft2(gray_reduced)
# Calculate frequency content reduction
original_power = np.mean(np.abs(original_fft))
reduced_power = np.mean(np.abs(reduced_fft))
reduction_ratio = reduced_power / original_power
# Expected reduction should be around 0.167 (1/6 of human acuity)
expected_ratio = self.spatial_acuity_factor
accuracy = 1.0 - min(abs(reduction_ratio - expected_ratio) / expected_ratio, 1.0)
return accuracy
def _validate_temporal_response(self) -> float:
"""
Validate temporal frequency response characteristics.
Tests the temporal sensitivity function at key frequency points
to ensure it matches expected cat vision behavior.
Returns:
Accuracy score (0.0 to 1.0)
"""
# Test key frequency points
test_frequencies = [10, 24, 55, 80] # Hz
expected_responses = [1.5, 1.2, 1.0, 0.1] # Approximate expected values
actual_responses = [
self._temporal_sensitivity_function(f) for f in test_frequencies
]
# Calculate accuracy based on response matching
accuracies = []
for actual, expected in zip(actual_responses, expected_responses):
accuracy = 1.0 - min(abs(actual - expected) / expected, 1.0)
accuracies.append(accuracy)
return np.mean(accuracies)
def _validate_motion_detection(self, test_images: List[np.ndarray]) -> float:
"""
Validate motion detection enhancement.
Verifies that motion detection enhancement matches the expected
sensitivity factor (1.8x human).
Args:
test_images: List of test images
Returns:
Accuracy score (0.0 to 1.0)
"""
if len(test_images) < 2:
return 0.0
# Create simple motion test
frame1 = test_images[0]
frame2 = test_images[1] if len(test_images) > 1 else test_images[0]
# Ensure frames have same size
if frame1.shape != frame2.shape:
frame2 = cv2.resize(frame2, (frame1.shape[1], frame1.shape[0]))
# Test enhanced motion detection
enhanced_frames = self.enhanced_motion_detection([frame1, frame2])
# Calculate motion enhancement factor
gray1 = (
cv2.cvtColor(frame1, cv2.COLOR_BGR2GRAY)
if len(frame1.shape) == 3
else frame1
)
gray2 = (
cv2.cvtColor(frame2, cv2.COLOR_BGR2GRAY)
if len(frame2.shape) == 3
else frame2
)
original_diff = cv2.absdiff(gray1, gray2)
enh_gray1 = (
cv2.cvtColor(enhanced_frames[0], cv2.COLOR_BGR2GRAY)
if len(enhanced_frames[0].shape) == 3
else enhanced_frames[0]
)
enh_gray2 = (
cv2.cvtColor(enhanced_frames[1], cv2.COLOR_BGR2GRAY)
if len(enhanced_frames[1].shape) == 3
else enhanced_frames[1]
)
enhanced_diff = cv2.absdiff(enh_gray1, enh_gray2)
enhancement_ratio = np.mean(enhanced_diff) / (np.mean(original_diff) + 1e-6)
# Expected enhancement should be around motion_sensitivity factor
expected_enhancement = self.motion_sensitivity
accuracy = 1.0 - min(
abs(enhancement_ratio - expected_enhancement) / expected_enhancement, 1.0
)
return accuracy