Spaces:
Running
on
Zero
Running
on
Zero
import numpy as np | |
import logging | |
import traceback | |
from typing import Dict, Any, Optional, List, Tuple | |
from configuration_manager import ConfigurationManager | |
class LightingConditionAnalyzer: | |
""" | |
Determines specific lighting conditions and time of day based on scene analysis. | |
此class 會判斷一些光線的特定場景 | |
This class analyzes lighting characteristics including natural and artificial illumination, | |
color temperature patterns, and temporal indicators to classify scenes into specific | |
lighting categories such as day clear, night with lights, indoor artificial, etc. | |
""" | |
def __init__(self, config_manager: ConfigurationManager): | |
""" | |
Initialize the lighting condition analyzer. | |
Args: | |
config_manager: Configuration manager instance for accessing thresholds and parameters. | |
""" | |
self.config_manager = config_manager | |
self.logger = self._setup_logger() | |
# Internal threshold constants for Places365 analysis | |
self.P365_ATTRIBUTE_CONF_THRESHOLD = 0.60 | |
self.P365_SCENE_MODERATE_CONF_THRESHOLD = 0.45 | |
self.P365_SCENE_HIGH_CONF_THRESHOLD = 0.70 | |
# Scene type keyword definitions | |
self.P365_OUTDOOR_SCENE_KEYWORDS = [ | |
"street", "road", "highway", "park", "beach", "mountain", "forest", "field", | |
"outdoor", "sky", "coast", "courtyard", "square", "plaza", "bridge", | |
"parking", "playground", "stadium", "construction", "river", "ocean", "desert", | |
"garden", "trail", "natural_landmark", "airport_outdoor", "train_station_outdoor", | |
"bus_station_outdoor", "intersection", "crosswalk", "sidewalk", "pathway" | |
] | |
self.P365_INDOOR_RESTAURANT_KEYWORDS = [ | |
"restaurant", "bar", "cafe", "dining_room", "pub", "bistro", "eatery" | |
] | |
def _setup_logger(self) -> logging.Logger: | |
"""Set up logger for lighting condition analysis operations.""" | |
logger = logging.getLogger(f"{__name__}.LightingConditionAnalyzer") | |
if not logger.handlers: | |
handler = logging.StreamHandler() | |
formatter = logging.Formatter( | |
'%(asctime)s - %(name)s - %(levelname)s - %(message)s' | |
) | |
handler.setFormatter(formatter) | |
logger.addHandler(handler) | |
logger.setLevel(logging.INFO) | |
return logger | |
def analyze_lighting_conditions(self, features: Dict[str, Any], is_indoor: bool, | |
places365_info: Optional[Dict] = None) -> Dict[str, Any]: | |
""" | |
Determine specific lighting conditions based on features and scene context. | |
Args: | |
features: Dictionary containing extracted image features. | |
is_indoor: Boolean indicating whether the scene is indoor (from previous classification). | |
places365_info: Optional Places365 classification information. | |
Returns: | |
Dictionary containing lighting analysis results including time_of_day, confidence, | |
and diagnostic information. | |
""" | |
try: | |
self.logger.debug(f"Starting lighting analysis for {'indoor' if is_indoor else 'outdoor'} scene") | |
# Initialize analysis results | |
time_of_day = "unknown" | |
confidence = 0.5 | |
diagnostics = {} | |
# Extract Places365 context | |
p365_context = self._extract_places365_context(places365_info, diagnostics) | |
# Priority 1: Use Places365 attributes if highly confident | |
attribute_result = self._analyze_places365_attributes( | |
p365_context, is_indoor, features, diagnostics | |
) | |
if attribute_result["determined"] and attribute_result["confidence"] >= 0.75: | |
self.logger.debug(f"High-confidence Places365 attribute determination: {attribute_result['time_of_day']}") | |
return { | |
"time_of_day": attribute_result["time_of_day"], | |
"confidence": attribute_result["confidence"], | |
"diagnostics": diagnostics | |
} | |
# Priority 2: Visual feature analysis with Places365 scene context | |
visual_result = self._analyze_visual_features( | |
features, is_indoor, p365_context, diagnostics | |
) | |
time_of_day = visual_result["time_of_day"] | |
confidence = visual_result["confidence"] | |
# Combine with attribute result if it exists but wasn't decisive | |
if attribute_result["determined"]: | |
combined_result = self._combine_attribute_and_visual_results( | |
attribute_result, visual_result, diagnostics | |
) | |
time_of_day = combined_result["time_of_day"] | |
confidence = combined_result["confidence"] | |
# Priority 3: Special lighting refinement (neon, sodium vapor) | |
refined_result = self._apply_special_lighting_refinement( | |
time_of_day, confidence, features, is_indoor, p365_context, diagnostics | |
) | |
time_of_day = refined_result["time_of_day"] | |
confidence = refined_result["confidence"] | |
# Final confidence clamping | |
confidence = min(0.95, max(0.50, confidence)) | |
# Record final results | |
diagnostics["final_lighting_time_of_day"] = time_of_day | |
diagnostics["final_lighting_confidence"] = round(confidence, 3) | |
self.logger.debug(f"Lighting analysis complete: {time_of_day} (confidence: {confidence:.3f})") | |
return { | |
"time_of_day": time_of_day, | |
"confidence": confidence, | |
"diagnostics": diagnostics | |
} | |
except Exception as e: | |
self.logger.error(f"Error in lighting condition analysis: {str(e)}") | |
self.logger.error(f"Traceback: {traceback.format_exc()}") | |
return self._get_default_lighting_result() | |
def _extract_places365_context(self, places365_info: Optional[Dict], | |
diagnostics: Dict[str, Any]) -> Dict[str, Any]: | |
"""Extract and validate Places365 context information for lighting analysis.""" | |
context = { | |
"mapped_scene": "unknown", | |
"attributes": [], | |
"confidence": 0.0 | |
} | |
if places365_info: | |
context["mapped_scene"] = places365_info.get('mapped_scene_type', 'unknown').lower() | |
context["attributes"] = [attr.lower() for attr in places365_info.get('attributes', [])] | |
context["confidence"] = places365_info.get('confidence', 0.0) | |
diagnostics["p365_context_for_lighting"] = ( | |
f"P365 Scene: {context['mapped_scene']}, Attrs: {context['attributes']}, " | |
f"Conf: {context['confidence']:.2f}" | |
) | |
return context | |
def _analyze_places365_attributes(self, p365_context: Dict[str, Any], is_indoor: bool, | |
features: Dict[str, Any], diagnostics: Dict[str, Any]) -> Dict[str, Any]: | |
"""Analyze Places365 attributes for lighting condition determination.""" | |
if (not p365_context["attributes"] or | |
p365_context["confidence"] <= self.P365_ATTRIBUTE_CONF_THRESHOLD): | |
return {"determined": False, "time_of_day": "unknown", "confidence": 0.5} | |
confidence = p365_context["confidence"] | |
attributes = p365_context["attributes"] | |
mapped_scene = p365_context["mapped_scene"] | |
# Outdoor attribute analysis | |
if not is_indoor: | |
outdoor_result = self._analyze_outdoor_attributes( | |
attributes, mapped_scene, confidence, diagnostics | |
) | |
if outdoor_result["determined"]: | |
return outdoor_result | |
# Indoor attribute analysis | |
if is_indoor: | |
indoor_result = self._analyze_indoor_attributes( | |
attributes, mapped_scene, features, confidence, diagnostics | |
) | |
if indoor_result["determined"]: | |
return indoor_result | |
return {"determined": False, "time_of_day": "unknown", "confidence": 0.5} | |
def _analyze_outdoor_attributes(self, attributes: List[str], mapped_scene: str, | |
confidence: float, diagnostics: Dict[str, Any]) -> Dict[str, Any]: | |
"""Analyze Places365 attributes for outdoor lighting conditions.""" | |
base_confidence_boost = (confidence - self.P365_ATTRIBUTE_CONF_THRESHOLD) * 0.25 | |
if "sunny" in attributes or "clear sky" in attributes: | |
final_confidence = 0.85 + base_confidence_boost | |
diagnostics["reason"] = "P365 attribute: sunny/clear sky (Outdoor)." | |
return { | |
"determined": True, | |
"time_of_day": "day_clear", | |
"confidence": final_confidence | |
} | |
elif "nighttime" in attributes or "night" in attributes: | |
if ("artificial lighting" in attributes or "man-made lighting" in attributes or | |
any(kw in mapped_scene for kw in ["street", "city", "road", "urban", "downtown"])): | |
final_confidence = 0.82 + base_confidence_boost * 0.8 | |
diagnostics["reason"] = "P365 attribute: nighttime with artificial/street lights (Outdoor)." | |
return { | |
"determined": True, | |
"time_of_day": "night_with_lights", | |
"confidence": final_confidence | |
} | |
else: | |
final_confidence = 0.78 + base_confidence_boost * 0.8 | |
diagnostics["reason"] = "P365 attribute: nighttime, dark (Outdoor)." | |
return { | |
"determined": True, | |
"time_of_day": "night_dark", | |
"confidence": final_confidence | |
} | |
elif "cloudy" in attributes or "overcast" in attributes: | |
final_confidence = 0.80 + base_confidence_boost | |
diagnostics["reason"] = "P365 attribute: cloudy/overcast (Outdoor)." | |
return { | |
"determined": True, | |
"time_of_day": "day_cloudy_overcast", | |
"confidence": final_confidence | |
} | |
return {"determined": False, "time_of_day": "unknown", "confidence": 0.5} | |
def _analyze_indoor_attributes(self, attributes: List[str], mapped_scene: str, | |
features: Dict[str, Any], confidence: float, | |
diagnostics: Dict[str, Any]) -> Dict[str, Any]: | |
"""Analyze Places365 attributes for indoor lighting conditions.""" | |
base_confidence_boost = (confidence - self.P365_ATTRIBUTE_CONF_THRESHOLD) * 0.20 | |
avg_brightness = features.get("avg_brightness", 128.0) | |
if "artificial lighting" in attributes or "man-made lighting" in attributes: | |
base_indoor_conf = 0.70 + base_confidence_boost | |
thresholds = self.config_manager.lighting_thresholds | |
if avg_brightness > thresholds.indoor_bright_thresh: | |
time_of_day = "indoor_bright_artificial" | |
final_confidence = base_indoor_conf + 0.10 | |
elif avg_brightness > thresholds.indoor_moderate_thresh: | |
time_of_day = "indoor_moderate_artificial" | |
final_confidence = base_indoor_conf | |
else: | |
time_of_day = "indoor_dim_artificial" | |
final_confidence = base_indoor_conf - 0.05 | |
diagnostics["reason"] = ( | |
f"P365 attribute: artificial lighting (Indoor), " | |
f"brightness based category: {time_of_day}." | |
) | |
return { | |
"determined": True, | |
"time_of_day": time_of_day, | |
"confidence": final_confidence | |
} | |
elif "natural lighting" in attributes: | |
is_applicable_scene = ( | |
self._check_home_environment_pattern(features) or | |
any(kw in mapped_scene for kw in ["living_room", "bedroom", "sunroom"]) | |
) | |
if is_applicable_scene: | |
final_confidence = 0.80 + base_confidence_boost | |
diagnostics["reason"] = "P365 attribute: natural lighting in residential/applicable indoor scene." | |
return { | |
"determined": True, | |
"time_of_day": "indoor_residential_natural", | |
"confidence": final_confidence | |
} | |
return {"determined": False, "time_of_day": "unknown", "confidence": 0.5} | |
def _analyze_visual_features(self, features: Dict[str, Any], is_indoor: bool, | |
p365_context: Dict[str, Any], diagnostics: Dict[str, Any]) -> Dict[str, Any]: | |
"""Analyze visual features for lighting condition determination.""" | |
if is_indoor: | |
return self._analyze_indoor_visual_features(features, p365_context, diagnostics) | |
else: | |
return self._analyze_outdoor_visual_features(features, p365_context, diagnostics) | |
def _analyze_indoor_visual_features(self, features: Dict[str, Any], p365_context: Dict[str, Any], | |
diagnostics: Dict[str, Any]) -> Dict[str, Any]: | |
"""Analyze visual features for indoor lighting conditions.""" | |
avg_brightness = features.get("avg_brightness", 128.0) | |
thresholds = self.config_manager.lighting_thresholds | |
# Extract relevant features | |
sky_blue_in_sky_region = features.get("sky_region_blue_dominance", 0.0) | |
sky_region_is_brighter = features.get("sky_region_brightness_ratio", 1.0) > 1.05 | |
is_likely_home_environment = self._check_home_environment_pattern(features) | |
# Lighting and structural features | |
circular_lights = features.get("circular_light_count", 0) | |
bright_spots_overall = features.get("bright_spot_count", 0) | |
brightness_uniformity = features.get("brightness_uniformity", 0.0) | |
warm_ratio = features.get("warm_ratio", 0.0) | |
# Natural light hints calculation | |
natural_light_hints = 0.0 | |
if sky_blue_in_sky_region > 0.05 and sky_region_is_brighter: | |
natural_light_hints += 1.0 | |
if brightness_uniformity > 0.65 and features.get("brightness_std", 100.0) < 70: | |
natural_light_hints += 1.0 | |
if warm_ratio > 0.15 and avg_brightness > 110: | |
natural_light_hints += 0.5 | |
# Designer lighting detection | |
is_designer_lit = ( | |
(circular_lights > 0 or bright_spots_overall > 2) and | |
brightness_uniformity > 0.6 and warm_ratio > 0.2 and avg_brightness > 90 | |
) | |
# Brightness-based classification | |
if avg_brightness > thresholds.indoor_bright_thresh: | |
return self._classify_bright_indoor( | |
features, natural_light_hints, is_designer_lit, is_likely_home_environment, | |
p365_context, diagnostics | |
) | |
elif avg_brightness > thresholds.indoor_moderate_thresh: | |
return self._classify_moderate_indoor( | |
features, is_designer_lit, is_likely_home_environment, p365_context, diagnostics | |
) | |
else: | |
return self._classify_dim_indoor(features, diagnostics) | |
def _classify_bright_indoor(self, features: Dict[str, Any], natural_light_hints: float, | |
is_designer_lit: bool, is_likely_home_environment: bool, | |
p365_context: Dict[str, Any], diagnostics: Dict[str, Any]) -> Dict[str, Any]: | |
"""Classify bright indoor lighting conditions.""" | |
mapped_scene = p365_context["mapped_scene"] | |
sky_blue_in_sky_region = features.get("sky_region_blue_dominance", 0.0) | |
sky_region_is_brighter = features.get("sky_region_brightness_ratio", 1.0) > 1.05 | |
# Natural residential lighting | |
if (natural_light_hints >= 1.5 and | |
(is_likely_home_environment or any(kw in mapped_scene for kw in ["home", "residential", "living", "bedroom"]))): | |
return { | |
"time_of_day": "indoor_residential_natural", | |
"confidence": 0.82 | |
} | |
# Designer residential lighting | |
elif (is_designer_lit and | |
(is_likely_home_environment or any(kw in mapped_scene for kw in ["home", "designer", "modern_interior"]))): | |
return { | |
"time_of_day": "indoor_designer_residential", | |
"confidence": 0.85 | |
} | |
# Mixed natural/artificial lighting | |
elif sky_blue_in_sky_region > 0.03 and sky_region_is_brighter: | |
return { | |
"time_of_day": "indoor_bright_natural_mix", | |
"confidence": 0.78 | |
} | |
# Pure artificial lighting | |
else: | |
return { | |
"time_of_day": "indoor_bright_artificial", | |
"confidence": 0.75 | |
} | |
def _classify_moderate_indoor(self, features: Dict[str, Any], is_designer_lit: bool, | |
is_likely_home_environment: bool, p365_context: Dict[str, Any], | |
diagnostics: Dict[str, Any]) -> Dict[str, Any]: | |
"""Classify moderate brightness indoor lighting conditions.""" | |
mapped_scene = p365_context["mapped_scene"] | |
confidence = p365_context["confidence"] | |
warm_ratio = features.get("warm_ratio", 0.0) | |
yellow_orange_ratio = features.get("yellow_orange_ratio", 0.0) | |
# Designer residential lighting | |
if (is_designer_lit and | |
(is_likely_home_environment or any(kw in mapped_scene for kw in ["home", "designer"]))): | |
return { | |
"time_of_day": "indoor_designer_residential", | |
"confidence": 0.78 | |
} | |
# Restaurant/bar lighting | |
elif warm_ratio > 0.35 and yellow_orange_ratio > 0.1: | |
return self._classify_restaurant_bar_lighting( | |
p365_context, features, diagnostics | |
) | |
# Standard moderate artificial | |
else: | |
return { | |
"time_of_day": "indoor_moderate_artificial", | |
"confidence": 0.70 | |
} | |
def _classify_restaurant_bar_lighting(self, p365_context: Dict[str, Any], | |
features: Dict[str, Any], | |
diagnostics: Dict[str, Any]) -> Dict[str, Any]: | |
"""Classify restaurant/bar specific lighting conditions.""" | |
mapped_scene = p365_context["mapped_scene"] | |
confidence = p365_context["confidence"] | |
# Strong P365 restaurant/bar confirmation | |
if (any(kw in mapped_scene for kw in self.P365_INDOOR_RESTAURANT_KEYWORDS) and | |
confidence > self.P365_SCENE_MODERATE_CONF_THRESHOLD): | |
diagnostics["visual_analysis_reason"] = ( | |
"Visual: Moderate warm tones. P365 context confirms restaurant/bar." | |
) | |
return { | |
"time_of_day": "indoor_restaurant_bar", | |
"confidence": 0.80 + confidence * 0.15 | |
} | |
# P365 outdoor conflict detection | |
elif (any(kw in mapped_scene for kw in self.P365_OUTDOOR_SCENE_KEYWORDS) and | |
confidence > self.P365_SCENE_MODERATE_CONF_THRESHOLD): | |
diagnostics["visual_analysis_reason"] = ( | |
"Visual: Moderate warm. CONFLICT: LA says indoor but P365 scene is outdoor. " | |
"Defaulting to general indoor artificial." | |
) | |
diagnostics["conflict_is_indoor_vs_p365_scene_for_restaurant_bar"] = True | |
return { | |
"time_of_day": "indoor_moderate_artificial", | |
"confidence": 0.55 | |
} | |
# Neutral P365 context | |
else: | |
diagnostics["visual_analysis_reason"] = ( | |
"Visual: Moderate warm tones, typical of restaurant/bar. P365 context neutral or weak." | |
) | |
return { | |
"time_of_day": "indoor_restaurant_bar", | |
"confidence": 0.70 | |
} | |
def _classify_dim_indoor(self, features: Dict[str, Any], | |
diagnostics: Dict[str, Any]) -> Dict[str, Any]: | |
"""Classify dim indoor lighting conditions.""" | |
warm_ratio = features.get("warm_ratio", 0.0) | |
yellow_orange_ratio = features.get("yellow_orange_ratio", 0.0) | |
if warm_ratio > 0.45 and yellow_orange_ratio > 0.15: | |
return { | |
"time_of_day": "indoor_dim_warm", | |
"confidence": 0.75 | |
} | |
else: | |
return { | |
"time_of_day": "indoor_dim_general", | |
"confidence": 0.70 | |
} | |
def _analyze_outdoor_visual_features(self, features: Dict[str, Any], p365_context: Dict[str, Any], | |
diagnostics: Dict[str, Any]) -> Dict[str, Any]: | |
"""Analyze visual features for outdoor lighting conditions.""" | |
avg_brightness = features.get("avg_brightness", 128.0) | |
thresholds = self.config_manager.lighting_thresholds | |
# P365 enhanced street scene analysis | |
street_result = self._analyze_p365_enhanced_street_scenes( | |
features, p365_context, diagnostics | |
) | |
if street_result["determined"]: | |
return street_result | |
# Brightness-based outdoor classification | |
if avg_brightness < thresholds.outdoor_night_thresh_brightness: | |
return self._classify_night_outdoor(features, diagnostics) | |
elif (avg_brightness < thresholds.outdoor_dusk_dawn_thresh_brightness and | |
self._check_warm_sunset_conditions(features)): | |
return self._classify_sunset_sunrise(features, p365_context, diagnostics) | |
elif avg_brightness > thresholds.outdoor_day_bright_thresh: | |
return self._classify_bright_day_outdoor(features, diagnostics) | |
elif avg_brightness > thresholds.outdoor_day_cloudy_thresh: | |
return self._classify_cloudy_day_outdoor(features, diagnostics) | |
else: | |
return self._classify_general_outdoor(features, diagnostics) | |
def _analyze_p365_enhanced_street_scenes(self, features: Dict[str, Any], p365_context: Dict[str, Any], | |
diagnostics: Dict[str, Any]) -> Dict[str, Any]: | |
"""Analyze outdoor scenes with Places365 street context enhancement.""" | |
mapped_scene = p365_context["mapped_scene"] | |
confidence = p365_context["confidence"] | |
thresholds = self.config_manager.lighting_thresholds | |
# Check for street scene with warm lighting | |
is_street_scene = ( | |
any(kw in mapped_scene for kw in ["street", "city", "road", "urban", "downtown", "intersection"]) and | |
confidence > self.P365_SCENE_MODERATE_CONF_THRESHOLD and | |
features.get("color_atmosphere") == "warm" | |
) | |
if not is_street_scene: | |
return {"determined": False, "time_of_day": "unknown", "confidence": 0.5} | |
avg_brightness = features.get("avg_brightness", 128.0) | |
bright_spots_overall = features.get("bright_spot_count", 0) | |
# Night with street lights | |
if (avg_brightness < thresholds.outdoor_night_thresh_brightness and | |
bright_spots_overall > thresholds.outdoor_night_lights_thresh): | |
diagnostics["visual_analysis_reason"] = ( | |
f"P365 outdoor scene '{mapped_scene}' + visual low-warm light with spots -> night_with_lights." | |
) | |
return { | |
"determined": True, | |
"time_of_day": "night_with_lights", | |
"confidence": 0.88 + confidence * 0.1 | |
} | |
# Sunset/sunrise conditions | |
elif avg_brightness >= thresholds.outdoor_night_thresh_brightness: | |
diagnostics["visual_analysis_reason"] = ( | |
f"P365 outdoor scene '{mapped_scene}' + visual moderate-warm light -> sunset/sunrise." | |
) | |
return { | |
"determined": True, | |
"time_of_day": "sunset_sunrise", | |
"confidence": 0.88 + confidence * 0.1 | |
} | |
# Very dark conditions | |
else: | |
diagnostics["visual_analysis_reason"] = ( | |
f"P365 outdoor scene '{mapped_scene}' + visual very low light -> night_dark." | |
) | |
return { | |
"determined": True, | |
"time_of_day": "night_dark", | |
"confidence": 0.75 + confidence * 0.1 | |
} | |
def _classify_night_outdoor(self, features: Dict[str, Any], | |
diagnostics: Dict[str, Any]) -> Dict[str, Any]: | |
"""Classify nighttime outdoor conditions.""" | |
bright_spots_overall = features.get("bright_spot_count", 0) | |
dark_pixel_ratio = features.get("dark_pixel_ratio", 0.0) | |
thresholds = self.config_manager.lighting_thresholds | |
if bright_spots_overall > thresholds.outdoor_night_lights_thresh: | |
confidence = 0.82 + min(0.13, dark_pixel_ratio / 2.5) | |
diagnostics["visual_analysis_reason"] = "Visual: Low brightness with light sources (street/car lights)." | |
return { | |
"time_of_day": "night_with_lights", | |
"confidence": confidence | |
} | |
else: | |
confidence = 0.78 + min(0.17, dark_pixel_ratio / 1.8) | |
diagnostics["visual_analysis_reason"] = "Visual: Very low brightness outdoor, deep night." | |
return { | |
"time_of_day": "night_dark", | |
"confidence": confidence | |
} | |
def _classify_sunset_sunrise(self, features: Dict[str, Any], p365_context: Dict[str, Any], | |
diagnostics: Dict[str, Any]) -> Dict[str, Any]: | |
"""Classify sunset/sunrise outdoor conditions.""" | |
yellow_orange_ratio = features.get("yellow_orange_ratio", 0.0) | |
confidence = 0.75 + min(0.20, yellow_orange_ratio / 1.5) | |
diagnostics["visual_analysis_reason"] = "Visual: Moderate brightness, warm tones -> sunset/sunrise." | |
# P365 natural scene boost | |
mapped_scene = p365_context["mapped_scene"] | |
p365_confidence = p365_context["confidence"] | |
if (any(kw in mapped_scene for kw in ["beach", "mountain", "lake", "ocean", "desert", "field", "natural_landmark", "sky"]) and | |
p365_confidence > self.P365_SCENE_MODERATE_CONF_THRESHOLD): | |
confidence = min(0.95, confidence + 0.15) | |
diagnostics["visual_analysis_reason"] += f" P365 natural scene '{mapped_scene}' supports." | |
return { | |
"time_of_day": "sunset_sunrise", | |
"confidence": confidence | |
} | |
def _classify_bright_day_outdoor(self, features: Dict[str, Any], | |
diagnostics: Dict[str, Any]) -> Dict[str, Any]: | |
"""Classify bright daytime outdoor conditions.""" | |
sky_like_blue_in_sky_region = features.get("sky_region_blue_dominance", 0.0) | |
sky_region_brightness_ratio = features.get("sky_region_brightness_ratio", 1.0) | |
texture_complexity = features.get("top_region_texture_complexity", 0.5) | |
thresholds = self.config_manager.lighting_thresholds | |
# Clear sky conditions | |
if (sky_like_blue_in_sky_region > thresholds.outdoor_day_blue_thresh or | |
(sky_region_brightness_ratio > 1.05 and texture_complexity < 0.4)): | |
confidence = 0.80 + min(0.15, sky_like_blue_in_sky_region * 2 + | |
(sky_like_blue_in_sky_region * 1.5 if sky_region_brightness_ratio > 1.05 else 0)) | |
diagnostics["visual_analysis_reason"] = "Visual: High brightness with blue/sky tones or bright smooth top." | |
return { | |
"time_of_day": "day_clear", | |
"confidence": confidence | |
} | |
# Stadium/floodlit detection | |
brightness_uniformity = features.get("brightness_uniformity", 0.0) | |
bright_spots_overall = features.get("bright_spot_count", 0) | |
if (brightness_uniformity > 0.70 and | |
bright_spots_overall > thresholds.stadium_min_spots_thresh): | |
diagnostics["visual_analysis_reason"] = ( | |
"Visual: Very bright, uniform lighting with multiple sources, suggests floodlights (Outdoor)." | |
) | |
return { | |
"time_of_day": "stadium_or_floodlit_area", | |
"confidence": 0.78 | |
} | |
# General bright day | |
diagnostics["visual_analysis_reason"] = "Visual: High brightness outdoor, specific sky features unclear." | |
return { | |
"time_of_day": "day_bright_general", | |
"confidence": 0.68 | |
} | |
def _classify_cloudy_day_outdoor(self, features: Dict[str, Any], | |
diagnostics: Dict[str, Any]) -> Dict[str, Any]: | |
"""Classify cloudy daytime outdoor conditions.""" | |
sky_region_brightness_ratio = features.get("sky_region_brightness_ratio", 1.0) | |
texture_complexity = features.get("top_region_texture_complexity", 0.5) | |
avg_saturation = features.get("avg_saturation", 100.0) | |
gray_ratio = features.get("gray_ratio", 0.0) | |
brightness_uniformity = features.get("brightness_uniformity", 0.0) | |
thresholds = self.config_manager.lighting_thresholds | |
# Overcast conditions | |
if (sky_region_brightness_ratio > 1.05 and texture_complexity < 0.45 and avg_saturation < 70): | |
confidence = 0.75 + min(0.20, gray_ratio / 1.5 + (brightness_uniformity - 0.5) / 1.5) | |
diagnostics["visual_analysis_reason"] = ( | |
"Visual: Good brightness, uniform bright top, lower saturation -> overcast." | |
) | |
return { | |
"time_of_day": "day_cloudy_overcast", | |
"confidence": confidence | |
} | |
# Gray cloudy conditions | |
elif gray_ratio > thresholds.outdoor_day_gray_thresh: | |
confidence = 0.72 + min(0.23, gray_ratio / 1.8) | |
diagnostics["visual_analysis_reason"] = "Visual: Good brightness with higher gray tones." | |
return { | |
"time_of_day": "day_cloudy_gray", | |
"confidence": confidence | |
} | |
# General bright outdoor | |
else: | |
diagnostics["visual_analysis_reason"] = "Visual: Bright outdoor, specific type less clear." | |
return { | |
"time_of_day": "day_bright_general", | |
"confidence": 0.68 | |
} | |
def _classify_general_outdoor(self, features: Dict[str, Any], | |
diagnostics: Dict[str, Any]) -> Dict[str, Any]: | |
"""Classify general outdoor conditions when specific patterns are unclear.""" | |
color_atmosphere = features.get("color_atmosphere", "neutral") | |
yellow_orange_ratio = features.get("yellow_orange_ratio", 0.0) | |
sky_like_blue_in_sky_region = features.get("sky_region_blue_dominance", 0.0) | |
# Potential sunset/sunrise with low confidence | |
if color_atmosphere == "warm" and yellow_orange_ratio > 0.08: | |
diagnostics["visual_analysis_reason"] = ( | |
"Visual: Outdoor, specific conditions less clear; broader visual cues suggest warm lighting." | |
) | |
return { | |
"time_of_day": "sunset_sunrise_low_confidence", | |
"confidence": 0.62 | |
} | |
# Potential hazy day conditions | |
elif sky_like_blue_in_sky_region > 0.02: | |
diagnostics["visual_analysis_reason"] = ( | |
"Visual: Outdoor, specific conditions less clear; some blue tones suggest daylight." | |
) | |
return { | |
"time_of_day": "day_hazy_or_partly_cloudy", | |
"confidence": 0.62 | |
} | |
# Unknown outdoor daylight | |
else: | |
diagnostics["visual_analysis_reason"] = ( | |
"Visual: Outdoor, specific conditions less clear; broader visual cues." | |
) | |
return { | |
"time_of_day": "outdoor_unknown_daylight", | |
"confidence": 0.58 | |
} | |
def _apply_commercial_indoor_refinement(self, features: Dict[str, Any], p365_context: Dict[str, Any], | |
time_of_day: str, confidence: float) -> Dict[str, Any]: | |
"""Apply commercial indoor lighting refinement if conditions are met.""" | |
# Skip if already classified as residential, restaurant, or bar | |
if any(category in time_of_day for category in ["residential", "restaurant", "bar"]): | |
return {"time_of_day": time_of_day, "confidence": confidence} | |
# Skip if P365 suggests home environment | |
mapped_scene = p365_context["mapped_scene"] | |
if any(kw in mapped_scene for kw in ["home", "residential"]): | |
return {"time_of_day": time_of_day, "confidence": confidence} | |
# Check commercial lighting indicators | |
avg_brightness = features.get("avg_brightness", 100.0) | |
bright_spots_overall = features.get("bright_spot_count", 0) | |
light_dist_uniformity = features.get("light_distribution_uniformity", 0.5) | |
ceiling_likelihood = features.get("ceiling_likelihood", 0.0) | |
thresholds = self.config_manager.lighting_thresholds | |
if (avg_brightness > thresholds.commercial_min_brightness_thresh and | |
bright_spots_overall > thresholds.commercial_min_spots_thresh and | |
(light_dist_uniformity > 0.5 or ceiling_likelihood > 0.4)): | |
refined_confidence = 0.70 + min(0.2, bright_spots_overall * 0.02) | |
return { | |
"time_of_day": "indoor_commercial", | |
"confidence": refined_confidence | |
} | |
return {"time_of_day": time_of_day, "confidence": confidence} | |
def _apply_special_lighting_refinement(self, time_of_day: str, confidence: float, | |
features: Dict[str, Any], is_indoor: bool, | |
p365_context: Dict[str, Any], | |
diagnostics: Dict[str, Any]) -> Dict[str, Any]: | |
"""Apply special lighting refinement for neon and sodium vapor lighting.""" | |
# Apply commercial refinement for indoor scenes first | |
if is_indoor: | |
commercial_result = self._apply_commercial_indoor_refinement( | |
features, p365_context, time_of_day, confidence | |
) | |
time_of_day = commercial_result["time_of_day"] | |
confidence = commercial_result["confidence"] | |
# Check for neon/sodium vapor lighting conditions | |
is_current_night_or_dim_warm = "night" in time_of_day or time_of_day == "indoor_dim_warm" | |
if not is_current_night_or_dim_warm: | |
return {"time_of_day": time_of_day, "confidence": confidence} | |
# Extract features for neon detection | |
yellow_orange_ratio = features.get("yellow_orange_ratio", 0.0) | |
bright_spots_overall = features.get("bright_spot_count", 0) | |
color_atmosphere = features.get("color_atmosphere", "neutral") | |
avg_saturation = features.get("avg_saturation", 0.0) | |
# Get neon detection thresholds | |
thresholds = self.config_manager.lighting_thresholds | |
# Check neon lighting conditions | |
if (yellow_orange_ratio > thresholds.neon_yellow_orange_thresh and | |
bright_spots_overall > thresholds.neon_bright_spots_thresh and | |
color_atmosphere == "warm" and | |
avg_saturation > thresholds.neon_avg_saturation_thresh): | |
old_time_of_day = time_of_day | |
old_confidence = confidence | |
# Check P365 context for neon scenes | |
mapped_scene = p365_context["mapped_scene"] | |
attributes = p365_context["attributes"] | |
is_p365_neon_context = ( | |
any(kw in mapped_scene for kw in ["neon", "nightclub", "bar_neon"]) or | |
"neon" in attributes | |
) | |
if is_indoor: | |
if (is_p365_neon_context or | |
any(kw in mapped_scene for kw in self.P365_INDOOR_RESTAURANT_KEYWORDS)): | |
time_of_day = "indoor_neon_lit" | |
confidence = max(confidence, 0.80) | |
else: | |
time_of_day = "indoor_dim_warm_neon_accent" | |
confidence = max(confidence, 0.77) | |
else: | |
if (is_p365_neon_context or | |
any(kw in mapped_scene for kw in ["street_night", "city_night", "downtown_night"])): | |
time_of_day = "neon_or_sodium_vapor_night" | |
confidence = max(confidence, 0.82) | |
else: | |
time_of_day = "night_with_neon_lights" | |
confidence = max(confidence, 0.79) | |
# Record the refinement | |
diagnostics["special_lighting_detected"] = ( | |
f"Refined from {old_time_of_day} (Conf:{old_confidence:.2f}) " | |
f"to {time_of_day} (Conf:{confidence:.2f}) due to neon/sodium vapor light characteristics. " | |
f"P365 Context: {mapped_scene if is_p365_neon_context else 'N/A'}." | |
) | |
return {"time_of_day": time_of_day, "confidence": confidence} | |
def _combine_attribute_and_visual_results(self, attribute_result: Dict[str, Any], | |
visual_result: Dict[str, Any], | |
diagnostics: Dict[str, Any]) -> Dict[str, Any]: | |
"""Combine Places365 attribute and visual analysis results.""" | |
# If visual analysis provided a different and potentially more nuanced result | |
if (attribute_result["time_of_day"] != visual_result["time_of_day"] and | |
visual_result["confidence"] > 0.65): | |
diagnostics["final_decision_source"] = "Visual features (potentially P365-context-refined)." | |
diagnostics["p365_attr_overridden_by_visual"] = ( | |
f"P365 Attr ToD {attribute_result['time_of_day']} " | |
f"(Conf {attribute_result['confidence']:.2f}) was less certain or overridden by " | |
f"visual logic result {visual_result['time_of_day']} (Conf {visual_result['confidence']:.2f})." | |
) | |
return visual_result | |
# Use attribute result if it was more confident | |
elif attribute_result["confidence"] >= visual_result["confidence"]: | |
diagnostics["final_decision_source"] = "High-confidence P365 attribute." | |
return attribute_result | |
# Use visual result | |
else: | |
diagnostics["final_decision_source"] = "Visual features (potentially P365-context-refined)." | |
return visual_result | |
def _check_home_environment_pattern(self, features: Dict[str, Any]) -> bool: | |
"""Check if features indicate a home/residential environment pattern.""" | |
thresholds = self.config_manager.indoor_outdoor_thresholds | |
return features.get("home_environment_pattern", 0.0) > thresholds.home_pattern_thresh_moderate * 0.7 | |
def _check_warm_sunset_conditions(self, features: Dict[str, Any]) -> bool: | |
"""Check if features indicate warm sunset/sunrise lighting conditions.""" | |
thresholds = self.config_manager.lighting_thresholds | |
yellow_orange_ratio = features.get("yellow_orange_ratio", 0.0) | |
color_atmosphere = features.get("color_atmosphere", "neutral") | |
sky_brightness_ratio = features.get("sky_region_brightness_ratio", 1.0) | |
return (yellow_orange_ratio > thresholds.outdoor_dusk_dawn_color_thresh and | |
color_atmosphere == "warm" and | |
sky_brightness_ratio < 1.5) | |
def _get_default_lighting_result(self) -> Dict[str, Any]: | |
"""Return default lighting analysis result in case of errors.""" | |
return { | |
"time_of_day": "unknown", | |
"confidence": 0.5, | |
"diagnostics": { | |
"error": "Lighting analysis failed, using default values" | |
} | |
} | |