Spaces:
Running
on
Zero
Running
on
Zero
File size: 14,976 Bytes
e6a18b7 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 |
from typing import Dict, Any, List, Tuple, Optional, Union
import json
import os
from dataclasses import dataclass, field
from pathlib import Path
@dataclass
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
@dataclass
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
@dataclass
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
@dataclass
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
@dataclass
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
@dataclass
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)]
)
@dataclass
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)
@property
def feature_thresholds(self) -> FeatureThresholds:
"""Get feature extraction thresholds."""
return self._feature_thresholds
@property
def indoor_outdoor_thresholds(self) -> IndoorOutdoorThresholds:
"""Get indoor/outdoor classification thresholds."""
return self._indoor_outdoor_thresholds
@property
def lighting_thresholds(self) -> LightingThresholds:
"""Get lighting condition analysis thresholds."""
return self._lighting_thresholds
@property
def weighting_factors(self) -> WeightingFactors:
"""Get feature weighting factors."""
return self._weighting_factors
@property
def override_factors(self) -> OverrideFactors:
"""Get override and reduction factors."""
return self._override_factors
@property
def color_ranges(self) -> ColorRanges:
"""Get color range definitions."""
return self._color_ranges
@property
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")
|