|
|
|
|
|
import numpy as np |
|
import cv2 |
|
import colorsys |
|
from scipy import ndimage, spatial |
|
from sklearn.cluster import DBSCAN |
|
|
|
class ImprovedContrastAnalyzer: |
|
""" |
|
Advanced contrast analyzer focused on Alzheimer's-friendly environments |
|
""" |
|
|
|
def __init__(self, wcag_threshold=4.5): |
|
self.wcag_threshold = wcag_threshold |
|
|
|
|
|
self.important_classes = { |
|
'floor': [3, 4], |
|
'wall': [0, 1], |
|
'ceiling': [5], |
|
'sofa': [10], |
|
'chair': [19], |
|
'table': [15], |
|
'door': [25], |
|
'window': [8], |
|
'stairs': [53], |
|
'bed': [7], |
|
} |
|
|
|
|
|
self.priority_relationships = { |
|
('floor', 'sofa'): 'high', |
|
('floor', 'chair'): 'high', |
|
('floor', 'table'): 'high', |
|
('wall', 'sofa'): 'medium', |
|
('wall', 'chair'): 'medium', |
|
('wall', 'door'): 'high', |
|
('floor', 'stairs'): 'critical', |
|
('floor', 'bed'): 'medium', |
|
('wall', 'window'): 'low', |
|
('ceiling', 'wall'): 'low', |
|
} |
|
|
|
def get_object_category(self, class_id): |
|
"""Map segmentation class to object category""" |
|
for category, class_ids in self.important_classes.items(): |
|
if class_id in class_ids: |
|
return category |
|
return 'other' |
|
|
|
def calculate_wcag_contrast(self, color1, color2): |
|
"""Calculate WCAG contrast ratio""" |
|
def relative_luminance(color): |
|
rgb = [c / 255.0 for c in color] |
|
return sum(c / 12.92 if c <= 0.03928 else ((c + 0.055) / 1.055) ** 2.4 |
|
for c in rgb) * [0.2126, 0.7152, 0.0722][i] for i, c in enumerate(rgb)) |
|
|
|
lum1 = sum(self.relative_luminance_component(color1)) |
|
lum2 = sum(self.relative_luminance_component(color2)) |
|
|
|
lighter = max(lum1, lum2) |
|
darker = min(lum1, lum2) |
|
|
|
return (lighter + 0.05) / (darker + 0.05) |
|
|
|
def relative_luminance_component(self, color): |
|
"""Calculate relative luminance components""" |
|
rgb = [c / 255.0 for c in color] |
|
components = [] |
|
factors = [0.2126, 0.7152, 0.0722] |
|
|
|
for i, c in enumerate(rgb): |
|
if c <= 0.03928: |
|
components.append((c / 12.92) * factors[i]) |
|
else: |
|
components.append(((c + 0.055) / 1.055) ** 2.4 * factors[i]) |
|
|
|
return components |
|
|
|
def calculate_perceptual_contrast(self, color1, color2): |
|
"""Calculate perceptual contrast including hue and saturation differences""" |
|
|
|
hsv1 = cv2.cvtColor(np.uint8([[color1]]), cv2.COLOR_RGB2HSV)[0][0] / 255.0 |
|
hsv2 = cv2.cvtColor(np.uint8([[color2]]), cv2.COLOR_RGB2HSV)[0][0] / 255.0 |
|
|
|
|
|
hue_diff = abs(hsv1[0] - hsv2[0]) |
|
if hue_diff > 0.5: |
|
hue_diff = 1 - hue_diff |
|
|
|
|
|
sat_diff = abs(hsv1[1] - hsv2[1]) |
|
|
|
|
|
val_diff = abs(hsv1[2] - hsv2[2]) |
|
|
|
|
|
perceptual_contrast = np.sqrt(hue_diff**2 + sat_diff**2 + val_diff**2) / np.sqrt(3) |
|
|
|
return perceptual_contrast |
|
|
|
def find_clean_boundaries(self, mask1, mask2, min_boundary_length=50): |
|
"""Find clean boundaries between two segments""" |
|
|
|
kernel = np.ones((3, 3), np.uint8) |
|
dilated1 = cv2.dilate(mask1.astype(np.uint8), kernel, iterations=1) |
|
dilated2 = cv2.dilate(mask2.astype(np.uint8), kernel, iterations=1) |
|
|
|
|
|
boundary = (dilated1 & dilated2).astype(bool) |
|
|
|
|
|
labeled_boundary = ndimage.label(boundary)[0] |
|
for region_id in range(1, labeled_boundary.max() + 1): |
|
region_mask = labeled_boundary == region_id |
|
if np.sum(region_mask) < min_boundary_length: |
|
boundary[region_mask] = False |
|
|
|
return boundary |
|
|
|
def get_representative_colors(self, image, mask, n_samples=1000): |
|
"""Get representative colors from a masked region using clustering""" |
|
if not np.any(mask): |
|
return np.array([0, 0, 0]) |
|
|
|
|
|
y_coords, x_coords = np.where(mask) |
|
if len(y_coords) > n_samples: |
|
indices = np.random.choice(len(y_coords), n_samples, replace=False) |
|
y_coords = y_coords[indices] |
|
x_coords = x_coords[indices] |
|
|
|
colors = image[y_coords, x_coords] |
|
|
|
|
|
if len(colors) > 10: |
|
clustering = DBSCAN(eps=30, min_samples=5).fit(colors) |
|
labels = clustering.labels_ |
|
|
|
|
|
unique_labels, counts = np.unique(labels[labels >= 0], return_counts=True) |
|
if len(unique_labels) > 0: |
|
dominant_label = unique_labels[np.argmax(counts)] |
|
dominant_colors = colors[labels == dominant_label] |
|
return np.mean(dominant_colors, axis=0).astype(int) |
|
|
|
|
|
return np.mean(colors, axis=0).astype(int) |
|
|
|
def analyze_improved_contrast(self, image, segmentation): |
|
""" |
|
Perform improved contrast analysis focused on important relationships |
|
""" |
|
h, w = segmentation.shape |
|
results = { |
|
'critical_issues': [], |
|
'high_priority_issues': [], |
|
'medium_priority_issues': [], |
|
'statistics': {}, |
|
'visualization': image.copy() |
|
} |
|
|
|
|
|
unique_segments = np.unique(segmentation) |
|
segment_categories = {} |
|
segment_colors = {} |
|
|
|
for seg_id in unique_segments: |
|
if seg_id == 0: |
|
continue |
|
|
|
mask = segmentation == seg_id |
|
category = self.get_object_category(seg_id) |
|
segment_categories[seg_id] = category |
|
segment_colors[seg_id] = self.get_representative_colors(image, mask) |
|
|
|
|
|
total_issues = 0 |
|
critical_count = 0 |
|
high_count = 0 |
|
medium_count = 0 |
|
|
|
for i, seg_id1 in enumerate(unique_segments): |
|
if seg_id1 == 0: |
|
continue |
|
|
|
category1 = segment_categories.get(seg_id1, 'other') |
|
if category1 == 'other': |
|
continue |
|
|
|
for seg_id2 in unique_segments[i+1:]: |
|
if seg_id2 == 0: |
|
continue |
|
|
|
category2 = segment_categories.get(seg_id2, 'other') |
|
if category2 == 'other': |
|
continue |
|
|
|
|
|
relationship = tuple(sorted([category1, category2])) |
|
priority = self.priority_relationships.get(relationship) |
|
|
|
if priority is None: |
|
continue |
|
|
|
|
|
mask1 = segmentation == seg_id1 |
|
mask2 = segmentation == seg_id2 |
|
boundary = self.find_clean_boundaries(mask1, mask2) |
|
|
|
if not np.any(boundary): |
|
continue |
|
|
|
|
|
color1 = segment_colors[seg_id1] |
|
color2 = segment_colors[seg_id2] |
|
|
|
wcag_contrast = self.calculate_wcag_contrast(color1, color2) |
|
perceptual_contrast = self.calculate_perceptual_contrast(color1, color2) |
|
|
|
|
|
wcag_issue = wcag_contrast < self.wcag_threshold |
|
perceptual_issue = perceptual_contrast < 0.3 |
|
|
|
if wcag_issue or perceptual_issue: |
|
issue = { |
|
'categories': (category1, category2), |
|
'segment_ids': (seg_id1, seg_id2), |
|
'wcag_contrast': wcag_contrast, |
|
'perceptual_contrast': perceptual_contrast, |
|
'boundary_pixels': np.sum(boundary), |
|
'priority': priority |
|
} |
|
|
|
|
|
if priority == 'critical': |
|
results['critical_issues'].append(issue) |
|
results['visualization'][boundary] = [255, 0, 0] |
|
critical_count += 1 |
|
elif priority == 'high': |
|
results['high_priority_issues'].append(issue) |
|
results['visualization'][boundary] = [255, 128, 0] |
|
high_count += 1 |
|
elif priority == 'medium': |
|
results['medium_priority_issues'].append(issue) |
|
results['visualization'][boundary] = [255, 255, 0] |
|
medium_count += 1 |
|
|
|
total_issues += 1 |
|
|
|
|
|
results['statistics'] = { |
|
'total_issues': total_issues, |
|
'critical_issues': critical_count, |
|
'high_priority_issues': high_count, |
|
'medium_priority_issues': medium_count, |
|
'segments_analyzed': len([cat for cat in segment_categories.values() if cat != 'other']) |
|
} |
|
|
|
return results |
|
|
|
|
|
class PrioritizedContrastDetector: |
|
"""Wrapper for the improved contrast analyzer""" |
|
|
|
def __init__(self, threshold=4.5): |
|
self.analyzer = ImprovedContrastAnalyzer(wcag_threshold=threshold) |
|
|
|
def analyze(self, image, segmentation, threshold, highlight_color=(255, 0, 0)): |
|
"""Analyze with improved logic""" |
|
results = self.analyzer.analyze_improved_contrast(image, segmentation) |
|
|
|
|
|
contrast_image = results['visualization'] |
|
|
|
|
|
problem_areas = np.any([ |
|
contrast_image[:, :, 0] == 255, |
|
], axis=0) |
|
|
|
|
|
stats = results['statistics'].copy() |
|
stats['threshold'] = threshold |
|
stats['problem_count'] = stats['total_issues'] |
|
|
|
|
|
if results['critical_issues']: |
|
stats['critical_details'] = [ |
|
f"{issue['categories'][0]}-{issue['categories'][1]}: WCAG {issue['wcag_contrast']:.1f}:1" |
|
for issue in results['critical_issues'] |
|
] |
|
|
|
if results['high_priority_issues']: |
|
stats['high_priority_details'] = [ |
|
f"{issue['categories'][0]}-{issue['categories'][1]}: WCAG {issue['wcag_contrast']:.1f}:1" |
|
for issue in results['high_priority_issues'] |
|
] |
|
|
|
return contrast_image, problem_areas, stats |
|
|