Spaces:
Paused
Paused
File size: 12,043 Bytes
c1fc5b2 c811f2f c1fc5b2 c811f2f c1fc5b2 5cb5de0 c1fc5b2 5cb5de0 c1fc5b2 01f651c c1fc5b2 01f651c c1fc5b2 |
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 |
"""
Centralized Configuration for Path Management
This module provides environment-aware path management to ensure
compatibility between local development and HuggingFace Space deployment.
Usage:
from code.cube3d.config import DATA_DIR, LABEL_MAPPINGS, get_mapping_paths
# Get paths for a specific mapping set
forward_path, inverse_path = get_mapping_paths("subset_1k")
"""
import os
import json
import re
import tempfile
from pathlib import Path
from typing import Dict, Tuple, Optional
# ============================================================================
# Environment Detection
# ============================================================================
def detect_environment() -> str:
"""
Detect current runtime environment
Returns:
"huggingface" if running on HF Space, "local" otherwise
"""
if os.getenv("SPACE_ID") or os.getenv("SPACE_AUTHOR_NAME"):
return "huggingface"
return "local"
# ============================================================================
# Path Configuration
# ============================================================================
ENVIRONMENT = detect_environment()
# Project root detection
if ENVIRONMENT == "huggingface":
# HuggingFace Space: app runs from /home/user/app
PROJECT_ROOT = Path("/home/user/app")
else:
# Local: calculate from this file's location
# config.py is at: code/cube3d/config.py
# So PROJECT_ROOT = ../../.. from here
PROJECT_ROOT = Path(__file__).parent.parent.parent.resolve()
# Data directory
DATA_DIR = PROJECT_ROOT / "data"
# Subdirectories
CAR_1K_DIR = DATA_DIR / "car_1k"
CAR_DATA_DIR = DATA_DIR / "1313个筛选车结构和对照渲染图"
# HuggingFace model cache directory
# CRITICAL: Must match where preload_from_hub downloads models
if ENVIRONMENT == "huggingface":
# HuggingFace Spaces: Use HF_HUB_CACHE (matches preload_from_hub behavior)
# preload_from_hub ALWAYS downloads to ~/.cache/huggingface/hub regardless of HF_HOME
# See: https://huggingface.co/docs/hub/spaces-config-reference
HF_CACHE_DIR = os.getenv(
"HF_HUB_CACHE",
os.path.expanduser("~/.cache/huggingface/hub")
)
print(f"✅ [Config] HuggingFace cache directory: {HF_CACHE_DIR}")
else:
# Local development: use standard user cache
HF_CACHE_DIR = os.path.expanduser("~/.cache/huggingface")
try:
os.makedirs(HF_CACHE_DIR, exist_ok=True)
print(f"[Config] Local HuggingFace cache directory: {HF_CACHE_DIR}")
except (PermissionError, OSError) as e:
# Fallback to temp directory
HF_CACHE_DIR = os.path.join(tempfile.gettempdir(), "huggingface")
os.makedirs(HF_CACHE_DIR, exist_ok=True)
print(f"⚠️ [Config] Using temp directory due to permission error: {HF_CACHE_DIR}")
# ============================================================================
# Label Mapping Paths
# ============================================================================
LABEL_MAPPINGS: Dict[str, Dict[str, Path]] = {
"subset_self": {
"forward": CAR_1K_DIR / "subset_self" / "label_mapping.json",
"inverse": CAR_1K_DIR / "subset_self" / "label_inverse_mapping.json",
},
"subset_1k": {
"forward": CAR_1K_DIR / "subset_1k" / "label_mapping_merge.json",
"inverse": CAR_1K_DIR / "subset_1k" / "label_inverse_mapping_merge.json",
},
}
# Runtime-generated mapping cache (for HuggingFace Space with storage limits)
_RUNTIME_MAPPING_CACHE: Dict[str, Tuple[str, str]] = {}
# ============================================================================
# Helper Functions
# ============================================================================
def generate_label_mappings_from_ldr(ldr_dir: Path, mapping_type: str = "subset_1k") -> Tuple[str, str]:
"""
Generate label mappings by scanning LDR files at runtime
This is a fallback for HuggingFace Spaces where storage limits prevent
pre-uploading large mapping files. Mappings are cached in memory.
Args:
ldr_dir: Directory containing LDR files
mapping_type: Type of mapping to generate
Returns:
Tuple of (forward_mapping_path, inverse_mapping_path) in /tmp
"""
print(f"🔧 Generating label mappings from LDR files in {ldr_dir}...")
# Check cache first
if mapping_type in _RUNTIME_MAPPING_CACHE:
print(f"✅ Using cached mappings for {mapping_type}")
return _RUNTIME_MAPPING_CACHE[mapping_type]
# Scan LDR files
label_mapping = {} # part_name -> ID
label_inverse_mapping = {} # ID -> part_name
label_counter = 0
ldr_files = list(ldr_dir.glob("**/*.ldr"))
print(f"📂 Found {len(ldr_files)} LDR files to process")
for ldr_file in ldr_files:
try:
with open(ldr_file, 'r', encoding='utf-8', errors='ignore') as f:
for line in f:
if line.startswith('1'): # Part data line
parts = line.split()
if len(parts) < 15:
continue
# Extract part identifier (lowercase, starting digits)
filename = parts[14].lower()
match = re.match(r'^\d+', filename)
part_identifier = match.group() if match else filename
if part_identifier not in label_mapping:
label_mapping[part_identifier] = label_counter
label_inverse_mapping[label_counter] = part_identifier
label_counter += 1
except Exception as e:
print(f"⚠️ Error processing {ldr_file}: {e}")
continue
print(f"✅ Generated {len(label_mapping)} unique part mappings")
# Save to /tmp directory
tmp_dir = Path(tempfile.gettempdir()) / "lego_mappings" / mapping_type
tmp_dir.mkdir(parents=True, exist_ok=True)
forward_path = tmp_dir / "label_mapping_merge.json"
inverse_path = tmp_dir / "label_inverse_mapping_merge.json"
with open(forward_path, 'w', encoding='utf-8') as f:
json.dump(label_mapping, f, ensure_ascii=False, indent=2)
# Convert int keys to str keys for JSON
inverse_str_keys = {str(k): v for k, v in label_inverse_mapping.items()}
with open(inverse_path, 'w', encoding='utf-8') as f:
json.dump(inverse_str_keys, f, ensure_ascii=False, indent=2)
print(f"💾 Saved mappings to:")
print(f" {forward_path}")
print(f" {inverse_path}")
# Cache the paths
result = (str(forward_path), str(inverse_path))
_RUNTIME_MAPPING_CACHE[mapping_type] = result
return result
def get_mapping_paths(mapping_type: str = "subset_1k") -> Tuple[str, str]:
"""
Get label mapping file paths for a given mapping type
Automatically generates mappings from LDR files if not found.
Args:
mapping_type: Either "subset_self" or "subset_1k"
Returns:
Tuple of (forward_mapping_path, inverse_mapping_path) as strings
Raises:
ValueError: If mapping_type is invalid
"""
if mapping_type not in LABEL_MAPPINGS:
raise ValueError(
f"Invalid mapping_type: {mapping_type}. "
f"Must be one of: {list(LABEL_MAPPINGS.keys())}"
)
forward_path = LABEL_MAPPINGS[mapping_type]["forward"]
inverse_path = LABEL_MAPPINGS[mapping_type]["inverse"]
# Diagnostic logging for HF Spaces debugging
print(f"🔍 [DEBUG] get_mapping_paths() called for: {mapping_type}")
print(f" PROJECT_ROOT: {PROJECT_ROOT}")
print(f" Forward path: {forward_path}")
print(f" Inverse path: {inverse_path}")
print(f" Forward exists: {forward_path.exists()}")
print(f" Inverse exists: {inverse_path.exists()}")
# Check if files exist
if forward_path.exists() and inverse_path.exists():
print(f" ✅ Both files exist, returning paths")
return str(forward_path), str(inverse_path)
# Files don't exist - generate from LDR files as fallback
print(f"⚠️ Label mapping files not found for {mapping_type}")
print(f" Missing: {forward_path}")
print(f" Missing: {inverse_path}")
print(f"🔄 Generating label mappings from LDR files (this may take 1-2 minutes)...")
# Determine LDR directory to scan
if mapping_type == "subset_1k":
ldr_dir = CAR_DATA_DIR / "ldr"
if not ldr_dir.exists():
ldr_dir = CAR_DATA_DIR # Try parent directory
else:
ldr_dir = CAR_1K_DIR / mapping_type
if not ldr_dir.exists():
raise FileNotFoundError(
f"Cannot generate mappings: LDR directory not found: {ldr_dir}\n"
f"Please ensure LDR files are available."
)
return generate_label_mappings_from_ldr(ldr_dir, mapping_type)
def create_default_mappings(mapping_type: str = "subset_1k") -> Tuple[Dict, Dict]:
"""
Create minimal default label mappings if files are missing
This is a fallback for development/testing. Production should have real files.
Args:
mapping_type: Mapping type identifier
Returns:
Tuple of (label_mapping, label_inverse_mapping) dictionaries
"""
print(f"⚠️ WARNING: Creating default empty mappings for {mapping_type}")
print(" This is for fallback only. Production should have real mapping files.")
# Minimal mapping structure
label_mapping = {}
label_inverse_mapping = {}
return label_mapping, label_inverse_mapping
def load_mappings_safe(mapping_type: str = "subset_1k") -> Tuple[Dict, Dict]:
"""
Safely load label mappings with fallback
Attempts to load from files, falls back to defaults if missing.
Args:
mapping_type: Either "subset_self" or "subset_1k"
Returns:
Tuple of (label_mapping, label_inverse_mapping) dictionaries
"""
try:
forward_path, inverse_path = get_mapping_paths(mapping_type)
with open(forward_path, 'r', encoding='utf-8') as f:
label_mapping = json.load(f)
with open(inverse_path, 'r', encoding='utf-8') as f:
label_inverse_mapping = json.load(f)
return label_mapping, label_inverse_mapping
except FileNotFoundError as e:
print(f"⚠️ {e}")
return create_default_mappings(mapping_type)
# ============================================================================
# Debug Information
# ============================================================================
def print_config_info():
"""Print current configuration for debugging"""
print("=" * 60)
print("Configuration Information")
print("=" * 60)
print(f"Environment: {ENVIRONMENT}")
print(f"Project Root: {PROJECT_ROOT}")
print(f"Data Directory: {DATA_DIR}")
print(f"Data Dir Exists: {DATA_DIR.exists()}")
print("\nLabel Mapping Paths:")
for mapping_type, paths in LABEL_MAPPINGS.items():
print(f"\n {mapping_type}:")
for key, path in paths.items():
exists = "✅" if path.exists() else "❌"
print(f" {key}: {exists} {path}")
print("=" * 60)
# ============================================================================
# Module Test
# ============================================================================
if __name__ == "__main__":
print_config_info()
# Test loading mappings
print("\n\nTesting mapping load:")
try:
forward, inverse = get_mapping_paths("subset_1k")
print(f"✅ subset_1k paths retrieved successfully")
print(f" Forward: {forward}")
print(f" Inverse: {inverse}")
except Exception as e:
print(f"❌ Error: {e}")
try:
forward, inverse = get_mapping_paths("subset_self")
print(f"✅ subset_self paths retrieved successfully")
print(f" Forward: {forward}")
print(f" Inverse: {inverse}")
except Exception as e:
print(f"❌ Error: {e}")
|