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") | |