Spaces:
Running
on
Zero
Running
on
Zero
from typing import Dict, Any, List, Tuple, Optional, Union | |
import json | |
import os | |
from dataclasses import dataclass, field | |
from pathlib import Path | |
class FeatureThresholds: | |
"""Configuration class for feature extraction thresholds.""" | |
dark_pixel_threshold: float = 50.0 | |
bright_pixel_threshold: float = 220.0 | |
sky_blue_hue_min: float = 95.0 | |
sky_blue_hue_max: float = 135.0 | |
sky_blue_sat_min: float = 40.0 | |
sky_blue_val_min: float = 90.0 | |
gray_sat_max: float = 70.0 | |
gray_val_min: float = 60.0 | |
gray_val_max: float = 220.0 | |
light_source_abs_thresh: float = 220.0 | |
class IndoorOutdoorThresholds: | |
"""Configuration class for indoor/outdoor classification thresholds.""" | |
sky_blue_dominance_thresh: float = 0.18 | |
sky_brightness_ratio_thresh: float = 1.25 | |
openness_top_thresh: float = 0.68 | |
sky_texture_complexity_thresh: float = 0.35 | |
ceiling_likelihood_thresh: float = 0.4 | |
boundary_clarity_thresh: float = 0.38 | |
brightness_uniformity_thresh_indoor: float = 0.6 | |
brightness_uniformity_thresh_outdoor: float = 0.40 | |
many_bright_spots_thresh: int = 6 | |
dim_scene_for_spots_thresh: float = 115.0 | |
home_pattern_thresh_strong: float = 2.0 | |
home_pattern_thresh_moderate: float = 1.0 | |
warm_indoor_max_brightness_thresh: float = 135.0 | |
aerial_top_dark_ratio_thresh: float = 0.9 | |
aerial_top_complex_thresh: float = 0.60 | |
aerial_min_avg_brightness_thresh: float = 65.0 | |
class LightingThresholds: | |
"""Configuration class for lighting condition analysis thresholds.""" | |
outdoor_night_thresh_brightness: float = 80.0 | |
outdoor_night_lights_thresh: int = 2 | |
outdoor_dusk_dawn_thresh_brightness: float = 130.0 | |
outdoor_dusk_dawn_color_thresh: float = 0.10 | |
outdoor_day_bright_thresh: float = 140.0 | |
outdoor_day_blue_thresh: float = 0.05 | |
outdoor_day_cloudy_thresh: float = 120.0 | |
outdoor_day_gray_thresh: float = 0.18 | |
indoor_bright_thresh: float = 130.0 | |
indoor_moderate_thresh: float = 95.0 | |
commercial_min_brightness_thresh: float = 105.0 | |
commercial_min_spots_thresh: int = 3 | |
stadium_min_spots_thresh: int = 6 | |
neon_yellow_orange_thresh: float = 0.12 | |
neon_bright_spots_thresh: int = 4 | |
neon_avg_saturation_thresh: float = 60.0 | |
class WeightingFactors: | |
"""Configuration class for feature weighting factors.""" | |
# Sky/Openness weights (negative values push towards outdoor) | |
sky_blue_dominance_w: float = 3.5 | |
sky_brightness_ratio_w: float = 3.0 | |
openness_top_w: float = 2.8 | |
sky_texture_w: float = 2.0 | |
# Ceiling/Enclosure weights (positive values push towards indoor) | |
ceiling_likelihood_w: float = 1.5 | |
boundary_clarity_w: float = 1.2 | |
# Brightness weights | |
brightness_uniformity_w: float = 0.6 | |
brightness_non_uniformity_outdoor_w: float = 1.0 | |
brightness_non_uniformity_indoor_penalty_w: float = 0.1 | |
# Light source weights | |
circular_lights_w: float = 1.2 | |
indoor_light_score_w: float = 0.8 | |
many_bright_spots_indoor_w: float = 0.3 | |
# Color atmosphere weights | |
warm_atmosphere_indoor_w: float = 0.15 | |
# Environment pattern weights | |
home_env_strong_w: float = 1.5 | |
home_env_moderate_w: float = 0.7 | |
# Structural pattern weights | |
aerial_street_w: float = 2.5 | |
places365_outdoor_scene_w: float = 4.0 | |
places365_indoor_scene_w: float = 3.0 | |
places365_attribute_w: float = 1.5 | |
class OverrideFactors: | |
"""Configuration class for override and reduction factors.""" | |
sky_override_factor_ceiling: float = 0.1 | |
sky_override_factor_boundary: float = 0.2 | |
sky_override_factor_uniformity: float = 0.15 | |
sky_override_factor_lights: float = 0.05 | |
sky_override_factor_p365_indoor_decision: float = 0.3 | |
aerial_enclosure_reduction_factor: float = 0.75 | |
ceiling_sky_override_factor: float = 0.1 | |
p365_outdoor_reduces_enclosure_factor: float = 0.3 | |
p365_indoor_boosts_ceiling_factor: float = 1.5 | |
class ColorRanges: | |
"""Configuration class for color range definitions.""" | |
warm_hue_ranges: List[Tuple[float, float]] = field( | |
default_factory=lambda: [(0, 50), (330, 360)] | |
) | |
cool_hue_ranges: List[Tuple[float, float]] = field( | |
default_factory=lambda: [(90, 270)] | |
) | |
class AlgorithmParameters: | |
"""Configuration class for algorithm-specific parameters.""" | |
indoor_score_sigmoid_scale: float = 0.3 | |
indoor_decision_threshold: float = 0.5 | |
places365_high_confidence_thresh: float = 0.75 | |
places365_moderate_confidence_thresh: float = 0.5 | |
places365_attribute_confidence_thresh: float = 0.6 | |
include_diagnostics: bool = True | |
class ConfigurationManager: | |
""" | |
這主要是管理光線分析的參數,會有很多不同情況, 做parameters配置 | |
This class provides type-safe access to all configuration parameters, | |
supports loading from external files, and includes validation mechanisms. | |
""" | |
def __init__(self, config_path: Optional[Union[str, Path]] = None): | |
""" | |
Initialize the configuration manager. | |
Args: | |
config_path: Optional path to external configuration file. | |
If None, uses default configuration. | |
""" | |
self._feature_thresholds = FeatureThresholds() | |
self._indoor_outdoor_thresholds = IndoorOutdoorThresholds() | |
self._lighting_thresholds = LightingThresholds() | |
self._weighting_factors = WeightingFactors() | |
self._override_factors = OverrideFactors() | |
self._color_ranges = ColorRanges() | |
self._algorithm_parameters = AlgorithmParameters() | |
if config_path is not None: | |
self.load_from_file(config_path) | |
def feature_thresholds(self) -> FeatureThresholds: | |
"""Get feature extraction thresholds.""" | |
return self._feature_thresholds | |
def indoor_outdoor_thresholds(self) -> IndoorOutdoorThresholds: | |
"""Get indoor/outdoor classification thresholds.""" | |
return self._indoor_outdoor_thresholds | |
def lighting_thresholds(self) -> LightingThresholds: | |
"""Get lighting condition analysis thresholds.""" | |
return self._lighting_thresholds | |
def weighting_factors(self) -> WeightingFactors: | |
"""Get feature weighting factors.""" | |
return self._weighting_factors | |
def override_factors(self) -> OverrideFactors: | |
"""Get override and reduction factors.""" | |
return self._override_factors | |
def color_ranges(self) -> ColorRanges: | |
"""Get color range definitions.""" | |
return self._color_ranges | |
def algorithm_parameters(self) -> AlgorithmParameters: | |
"""Get algorithm-specific parameters.""" | |
return self._algorithm_parameters | |
def get_legacy_config_dict(self) -> Dict[str, Any]: | |
""" | |
Generate legacy configuration dictionary for backward compatibility. | |
Returns: | |
Dictionary containing all configuration parameters in the original format. | |
""" | |
config_dict = {} | |
# Feature thresholds | |
for field_name, field_value in self._feature_thresholds.__dict__.items(): | |
config_dict[field_name] = field_value | |
# Indoor/outdoor thresholds | |
for field_name, field_value in self._indoor_outdoor_thresholds.__dict__.items(): | |
config_dict[field_name] = field_value | |
# Lighting thresholds | |
for field_name, field_value in self._lighting_thresholds.__dict__.items(): | |
config_dict[field_name] = field_value | |
# Override factors | |
for field_name, field_value in self._override_factors.__dict__.items(): | |
config_dict[field_name] = field_value | |
# Color ranges | |
for field_name, field_value in self._color_ranges.__dict__.items(): | |
config_dict[field_name] = field_value | |
# Algorithm parameters | |
for field_name, field_value in self._algorithm_parameters.__dict__.items(): | |
config_dict[field_name] = field_value | |
# Weighting factors - stored under 'indoor_outdoor_weights' key | |
config_dict["indoor_outdoor_weights"] = self._weighting_factors.__dict__.copy() | |
return config_dict | |
def load_from_file(self, config_path: Union[str, Path]) -> None: | |
""" | |
Load configuration from external JSON file. | |
Args: | |
config_path: Path to the configuration file. | |
Raises: | |
FileNotFoundError: If the configuration file doesn't exist. | |
ValueError: If the configuration file contains invalid data. | |
""" | |
config_path = Path(config_path) | |
if not config_path.exists(): | |
raise FileNotFoundError(f"Configuration file not found: {config_path}") | |
try: | |
with open(config_path, 'r', encoding='utf-8') as file: | |
config_data = json.load(file) | |
self._update_from_dict(config_data) | |
except json.JSONDecodeError as e: | |
raise ValueError(f"Invalid JSON in configuration file: {e}") | |
except Exception as e: | |
raise ValueError(f"Error loading configuration: {e}") | |
def save_to_file(self, config_path: Union[str, Path]) -> None: | |
""" | |
Save current configuration to JSON file. | |
Args: | |
config_path: Path where to save the configuration file. | |
""" | |
config_path = Path(config_path) | |
config_path.parent.mkdir(parents=True, exist_ok=True) | |
config_dict = self.get_legacy_config_dict() | |
with open(config_path, 'w', encoding='utf-8') as file: | |
json.dump(config_dict, file, indent=2, ensure_ascii=False) | |
def _update_from_dict(self, config_data: Dict[str, Any]) -> None: | |
""" | |
Update configuration from dictionary data. | |
Args: | |
config_data: Dictionary containing configuration parameters. | |
""" | |
# Update feature thresholds | |
self._update_dataclass_from_dict(self._feature_thresholds, config_data) | |
# Update indoor/outdoor thresholds | |
self._update_dataclass_from_dict(self._indoor_outdoor_thresholds, config_data) | |
# Update lighting thresholds | |
self._update_dataclass_from_dict(self._lighting_thresholds, config_data) | |
# Update override factors | |
self._update_dataclass_from_dict(self._override_factors, config_data) | |
# Update color ranges | |
self._update_dataclass_from_dict(self._color_ranges, config_data) | |
# Update algorithm parameters | |
self._update_dataclass_from_dict(self._algorithm_parameters, config_data) | |
# Update weighting factors from nested dictionary | |
if "indoor_outdoor_weights" in config_data: | |
self._update_dataclass_from_dict( | |
self._weighting_factors, | |
config_data["indoor_outdoor_weights"] | |
) | |
def _update_dataclass_from_dict(self, dataclass_instance: object, data_dict: Dict[str, Any]) -> None: | |
""" | |
Update dataclass instance fields from dictionary. | |
Args: | |
dataclass_instance: The dataclass instance to update. | |
data_dict: Dictionary containing the update values. | |
""" | |
for field_name, field_value in data_dict.items(): | |
if hasattr(dataclass_instance, field_name): | |
# Type validation could be added here | |
setattr(dataclass_instance, field_name, field_value) | |
def validate_configuration(self) -> List[str]: | |
""" | |
Validate the current configuration for logical consistency. | |
Returns: | |
List of validation error messages. Empty list if configuration is valid. | |
""" | |
errors = [] | |
# Validate threshold ranges | |
ft = self._feature_thresholds | |
if ft.dark_pixel_threshold >= ft.bright_pixel_threshold: | |
errors.append("Dark pixel threshold must be less than bright pixel threshold") | |
if ft.sky_blue_hue_min >= ft.sky_blue_hue_max: | |
errors.append("Sky blue hue min must be less than sky blue hue max") | |
if ft.gray_val_min >= ft.gray_val_max: | |
errors.append("Gray value min must be less than gray value max") | |
# Validate probability thresholds | |
ap = self._algorithm_parameters | |
if not (0.0 <= ap.indoor_decision_threshold <= 1.0): | |
errors.append("Indoor decision threshold must be between 0 and 1") | |
if not (0.0 <= ap.places365_high_confidence_thresh <= 1.0): | |
errors.append("Places365 high confidence threshold must be between 0 and 1") | |
# Validate color ranges | |
for warm_range in self._color_ranges.warm_hue_ranges: | |
if warm_range[0] >= warm_range[1]: | |
errors.append(f"Invalid warm hue range: {warm_range}") | |
for cool_range in self._color_ranges.cool_hue_ranges: | |
if cool_range[0] >= cool_range[1]: | |
errors.append(f"Invalid cool hue range: {cool_range}") | |
return errors | |
def get_threshold_value(self, threshold_name: str) -> Any: | |
""" | |
Get a specific threshold value by name. | |
Args: | |
threshold_name: Name of the threshold parameter. | |
Returns: | |
The threshold value. | |
Raises: | |
AttributeError: If the threshold name doesn't exist. | |
""" | |
# Search through all configuration sections | |
for config_section in [ | |
self._feature_thresholds, | |
self._indoor_outdoor_thresholds, | |
self._lighting_thresholds, | |
self._override_factors, | |
self._algorithm_parameters | |
]: | |
if hasattr(config_section, threshold_name): | |
return getattr(config_section, threshold_name) | |
# Check weighting factors | |
if hasattr(self._weighting_factors, threshold_name): | |
return getattr(self._weighting_factors, threshold_name) | |
raise AttributeError(f"Threshold '{threshold_name}' not found") | |
def update_threshold(self, threshold_name: str, value: Any) -> None: | |
""" | |
Update a specific threshold value. | |
Args: | |
threshold_name: Name of the threshold parameter. | |
value: New value for the threshold. | |
Raises: | |
AttributeError: If the threshold name doesn't exist. | |
""" | |
# Search through all configuration sections | |
for config_section in [ | |
self._feature_thresholds, | |
self._indoor_outdoor_thresholds, | |
self._lighting_thresholds, | |
self._override_factors, | |
self._algorithm_parameters, | |
self._weighting_factors | |
]: | |
if hasattr(config_section, threshold_name): | |
setattr(config_section, threshold_name, value) | |
return | |
raise AttributeError(f"Threshold '{threshold_name}' not found") | |