0xZohar commited on
Commit
c1fc5b2
·
verified ·
1 Parent(s): fe50a8f

Upload code/cube3d/config.py

Browse files
Files changed (1) hide show
  1. code/cube3d/config.py +351 -0
code/cube3d/config.py ADDED
@@ -0,0 +1,351 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Centralized Configuration for Path Management
3
+
4
+ This module provides environment-aware path management to ensure
5
+ compatibility between local development and HuggingFace Space deployment.
6
+
7
+ Usage:
8
+ from code.cube3d.config import DATA_DIR, LABEL_MAPPINGS, get_mapping_paths
9
+
10
+ # Get paths for a specific mapping set
11
+ forward_path, inverse_path = get_mapping_paths("subset_1k")
12
+ """
13
+
14
+ import os
15
+ import json
16
+ import re
17
+ import tempfile
18
+ from pathlib import Path
19
+ from typing import Dict, Tuple, Optional
20
+
21
+
22
+ # ============================================================================
23
+ # Environment Detection
24
+ # ============================================================================
25
+
26
+ def detect_environment() -> str:
27
+ """
28
+ Detect current runtime environment
29
+
30
+ Returns:
31
+ "huggingface" if running on HF Space, "local" otherwise
32
+ """
33
+ if os.getenv("SPACE_ID") or os.getenv("SPACE_AUTHOR_NAME"):
34
+ return "huggingface"
35
+ return "local"
36
+
37
+
38
+ # ============================================================================
39
+ # Path Configuration
40
+ # ============================================================================
41
+
42
+ ENVIRONMENT = detect_environment()
43
+
44
+ # Project root detection
45
+ if ENVIRONMENT == "huggingface":
46
+ # HuggingFace Space: app runs from /home/user/app
47
+ PROJECT_ROOT = Path("/home/user/app")
48
+ else:
49
+ # Local: calculate from this file's location
50
+ # config.py is at: code/cube3d/config.py
51
+ # So PROJECT_ROOT = ../../.. from here
52
+ PROJECT_ROOT = Path(__file__).parent.parent.parent.resolve()
53
+
54
+ # Data directory
55
+ DATA_DIR = PROJECT_ROOT / "data"
56
+
57
+ # Subdirectories
58
+ CAR_1K_DIR = DATA_DIR / "car_1k"
59
+ CAR_DATA_DIR = DATA_DIR / "1313个筛选车结构和对照渲染图"
60
+
61
+ # HuggingFace model cache directory
62
+ # Note: preload_from_hub ALWAYS saves to ~/.cache/huggingface/hub
63
+ # It does NOT respect HF_HOME environment variable (HF official docs confirmed)
64
+ if ENVIRONMENT == "huggingface":
65
+ # Use default HF cache location where preload_from_hub saves models
66
+ HF_CACHE_DIR = os.path.expanduser("~/.cache/huggingface")
67
+ print(f"[Config] Using HF cache directory: {HF_CACHE_DIR}")
68
+
69
+ # Ensure directory exists with proper permissions
70
+ cache_path = Path(HF_CACHE_DIR)
71
+ try:
72
+ cache_path.mkdir(parents=True, exist_ok=True, mode=0o755)
73
+ print(f"[Config] Cache directory created/verified with mode 755")
74
+ except Exception as e:
75
+ print(f"[Config] Warning: Could not set cache directory permissions: {e}")
76
+
77
+ # Clean stale lock files that may prevent model loading
78
+ try:
79
+ lock_files = list(cache_path.glob("**/*.lock"))
80
+ if lock_files:
81
+ print(f"[Config] Found {len(lock_files)} lock files, attempting cleanup...")
82
+ cleaned = 0
83
+ for lock_file in lock_files:
84
+ try:
85
+ lock_file.unlink()
86
+ cleaned += 1
87
+ except Exception as e:
88
+ print(f"[Config] Could not remove {lock_file.name}: {e}")
89
+ print(f"[Config] Cleaned {cleaned}/{len(lock_files)} lock files")
90
+ else:
91
+ print(f"[Config] No stale lock files found")
92
+ except Exception as e:
93
+ print(f"[Config] Lock file cleanup failed: {e}")
94
+ else:
95
+ # Local development
96
+ HF_CACHE_DIR = os.path.expanduser("~/.cache/huggingface")
97
+
98
+ # Ensure cache directory exists (fallback for local)
99
+ if ENVIRONMENT != "huggingface":
100
+ os.makedirs(HF_CACHE_DIR, exist_ok=True)
101
+
102
+
103
+ # ============================================================================
104
+ # Label Mapping Paths
105
+ # ============================================================================
106
+
107
+ LABEL_MAPPINGS: Dict[str, Dict[str, Path]] = {
108
+ "subset_self": {
109
+ "forward": CAR_1K_DIR / "subset_self" / "label_mapping.json",
110
+ "inverse": CAR_1K_DIR / "subset_self" / "label_inverse_mapping.json",
111
+ },
112
+ "subset_1k": {
113
+ "forward": CAR_1K_DIR / "subset_1k" / "label_mapping_merge.json",
114
+ "inverse": CAR_1K_DIR / "subset_1k" / "label_inverse_mapping_merge.json",
115
+ },
116
+ }
117
+
118
+ # Runtime-generated mapping cache (for HuggingFace Space with storage limits)
119
+ _RUNTIME_MAPPING_CACHE: Dict[str, Tuple[str, str]] = {}
120
+
121
+
122
+ # ============================================================================
123
+ # Helper Functions
124
+ # ============================================================================
125
+
126
+ def generate_label_mappings_from_ldr(ldr_dir: Path, mapping_type: str = "subset_1k") -> Tuple[str, str]:
127
+ """
128
+ Generate label mappings by scanning LDR files at runtime
129
+
130
+ This is a fallback for HuggingFace Spaces where storage limits prevent
131
+ pre-uploading large mapping files. Mappings are cached in memory.
132
+
133
+ Args:
134
+ ldr_dir: Directory containing LDR files
135
+ mapping_type: Type of mapping to generate
136
+
137
+ Returns:
138
+ Tuple of (forward_mapping_path, inverse_mapping_path) in /tmp
139
+ """
140
+ print(f"🔧 Generating label mappings from LDR files in {ldr_dir}...")
141
+
142
+ # Check cache first
143
+ if mapping_type in _RUNTIME_MAPPING_CACHE:
144
+ print(f"✅ Using cached mappings for {mapping_type}")
145
+ return _RUNTIME_MAPPING_CACHE[mapping_type]
146
+
147
+ # Scan LDR files
148
+ label_mapping = {} # part_name -> ID
149
+ label_inverse_mapping = {} # ID -> part_name
150
+ label_counter = 0
151
+
152
+ ldr_files = list(ldr_dir.glob("**/*.ldr"))
153
+ print(f"📂 Found {len(ldr_files)} LDR files to process")
154
+
155
+ for ldr_file in ldr_files:
156
+ try:
157
+ with open(ldr_file, 'r', encoding='utf-8', errors='ignore') as f:
158
+ for line in f:
159
+ if line.startswith('1'): # Part data line
160
+ parts = line.split()
161
+ if len(parts) < 15:
162
+ continue
163
+
164
+ # Extract part identifier (lowercase, starting digits)
165
+ filename = parts[14].lower()
166
+ match = re.match(r'^\d+', filename)
167
+ part_identifier = match.group() if match else filename
168
+
169
+ if part_identifier not in label_mapping:
170
+ label_mapping[part_identifier] = label_counter
171
+ label_inverse_mapping[label_counter] = part_identifier
172
+ label_counter += 1
173
+ except Exception as e:
174
+ print(f"⚠️ Error processing {ldr_file}: {e}")
175
+ continue
176
+
177
+ print(f"✅ Generated {len(label_mapping)} unique part mappings")
178
+
179
+ # Save to /tmp directory
180
+ tmp_dir = Path(tempfile.gettempdir()) / "lego_mappings" / mapping_type
181
+ tmp_dir.mkdir(parents=True, exist_ok=True)
182
+
183
+ forward_path = tmp_dir / "label_mapping_merge.json"
184
+ inverse_path = tmp_dir / "label_inverse_mapping_merge.json"
185
+
186
+ with open(forward_path, 'w', encoding='utf-8') as f:
187
+ json.dump(label_mapping, f, ensure_ascii=False, indent=2)
188
+
189
+ # Convert int keys to str keys for JSON
190
+ inverse_str_keys = {str(k): v for k, v in label_inverse_mapping.items()}
191
+ with open(inverse_path, 'w', encoding='utf-8') as f:
192
+ json.dump(inverse_str_keys, f, ensure_ascii=False, indent=2)
193
+
194
+ print(f"💾 Saved mappings to:")
195
+ print(f" {forward_path}")
196
+ print(f" {inverse_path}")
197
+
198
+ # Cache the paths
199
+ result = (str(forward_path), str(inverse_path))
200
+ _RUNTIME_MAPPING_CACHE[mapping_type] = result
201
+
202
+ return result
203
+
204
+
205
+ def get_mapping_paths(mapping_type: str = "subset_1k") -> Tuple[str, str]:
206
+ """
207
+ Get label mapping file paths for a given mapping type
208
+
209
+ Automatically generates mappings from LDR files if not found.
210
+
211
+ Args:
212
+ mapping_type: Either "subset_self" or "subset_1k"
213
+
214
+ Returns:
215
+ Tuple of (forward_mapping_path, inverse_mapping_path) as strings
216
+
217
+ Raises:
218
+ ValueError: If mapping_type is invalid
219
+ """
220
+ if mapping_type not in LABEL_MAPPINGS:
221
+ raise ValueError(
222
+ f"Invalid mapping_type: {mapping_type}. "
223
+ f"Must be one of: {list(LABEL_MAPPINGS.keys())}"
224
+ )
225
+
226
+ forward_path = LABEL_MAPPINGS[mapping_type]["forward"]
227
+ inverse_path = LABEL_MAPPINGS[mapping_type]["inverse"]
228
+
229
+ # Check if files exist
230
+ if forward_path.exists() and inverse_path.exists():
231
+ return str(forward_path), str(inverse_path)
232
+
233
+ # Files don't exist - generate from LDR files as fallback
234
+ print(f"⚠️ Label mapping files not found for {mapping_type}")
235
+ print(f" Missing: {forward_path}")
236
+ print(f" Missing: {inverse_path}")
237
+ print(f"🔄 Generating label mappings from LDR files (this may take 1-2 minutes)...")
238
+
239
+ # Determine LDR directory to scan
240
+ if mapping_type == "subset_1k":
241
+ ldr_dir = CAR_DATA_DIR / "ldr"
242
+ if not ldr_dir.exists():
243
+ ldr_dir = CAR_DATA_DIR # Try parent directory
244
+ else:
245
+ ldr_dir = CAR_1K_DIR / mapping_type
246
+
247
+ if not ldr_dir.exists():
248
+ raise FileNotFoundError(
249
+ f"Cannot generate mappings: LDR directory not found: {ldr_dir}\n"
250
+ f"Please ensure LDR files are available."
251
+ )
252
+
253
+ return generate_label_mappings_from_ldr(ldr_dir, mapping_type)
254
+
255
+
256
+ def create_default_mappings(mapping_type: str = "subset_1k") -> Tuple[Dict, Dict]:
257
+ """
258
+ Create minimal default label mappings if files are missing
259
+
260
+ This is a fallback for development/testing. Production should have real files.
261
+
262
+ Args:
263
+ mapping_type: Mapping type identifier
264
+
265
+ Returns:
266
+ Tuple of (label_mapping, label_inverse_mapping) dictionaries
267
+ """
268
+ print(f"⚠️ WARNING: Creating default empty mappings for {mapping_type}")
269
+ print(" This is for fallback only. Production should have real mapping files.")
270
+
271
+ # Minimal mapping structure
272
+ label_mapping = {}
273
+ label_inverse_mapping = {}
274
+
275
+ return label_mapping, label_inverse_mapping
276
+
277
+
278
+ def load_mappings_safe(mapping_type: str = "subset_1k") -> Tuple[Dict, Dict]:
279
+ """
280
+ Safely load label mappings with fallback
281
+
282
+ Attempts to load from files, falls back to defaults if missing.
283
+
284
+ Args:
285
+ mapping_type: Either "subset_self" or "subset_1k"
286
+
287
+ Returns:
288
+ Tuple of (label_mapping, label_inverse_mapping) dictionaries
289
+ """
290
+ try:
291
+ forward_path, inverse_path = get_mapping_paths(mapping_type)
292
+
293
+ with open(forward_path, 'r', encoding='utf-8') as f:
294
+ label_mapping = json.load(f)
295
+
296
+ with open(inverse_path, 'r', encoding='utf-8') as f:
297
+ label_inverse_mapping = json.load(f)
298
+
299
+ return label_mapping, label_inverse_mapping
300
+
301
+ except FileNotFoundError as e:
302
+ print(f"⚠️ {e}")
303
+ return create_default_mappings(mapping_type)
304
+
305
+
306
+ # ============================================================================
307
+ # Debug Information
308
+ # ============================================================================
309
+
310
+ def print_config_info():
311
+ """Print current configuration for debugging"""
312
+ print("=" * 60)
313
+ print("Configuration Information")
314
+ print("=" * 60)
315
+ print(f"Environment: {ENVIRONMENT}")
316
+ print(f"Project Root: {PROJECT_ROOT}")
317
+ print(f"Data Directory: {DATA_DIR}")
318
+ print(f"Data Dir Exists: {DATA_DIR.exists()}")
319
+ print("\nLabel Mapping Paths:")
320
+ for mapping_type, paths in LABEL_MAPPINGS.items():
321
+ print(f"\n {mapping_type}:")
322
+ for key, path in paths.items():
323
+ exists = "✅" if path.exists() else "❌"
324
+ print(f" {key}: {exists} {path}")
325
+ print("=" * 60)
326
+
327
+
328
+ # ============================================================================
329
+ # Module Test
330
+ # ============================================================================
331
+
332
+ if __name__ == "__main__":
333
+ print_config_info()
334
+
335
+ # Test loading mappings
336
+ print("\n\nTesting mapping load:")
337
+ try:
338
+ forward, inverse = get_mapping_paths("subset_1k")
339
+ print(f"✅ subset_1k paths retrieved successfully")
340
+ print(f" Forward: {forward}")
341
+ print(f" Inverse: {inverse}")
342
+ except Exception as e:
343
+ print(f"❌ Error: {e}")
344
+
345
+ try:
346
+ forward, inverse = get_mapping_paths("subset_self")
347
+ print(f"✅ subset_self paths retrieved successfully")
348
+ print(f" Forward: {forward}")
349
+ print(f" Inverse: {inverse}")
350
+ except Exception as e:
351
+ print(f"❌ Error: {e}")