Coverage for openhcs/core/config.py: 67.3%

268 statements  

« prev     ^ index     » next       coverage.py v7.10.3, created at 2025-08-14 05:57 +0000

1""" 

2Global configuration dataclasses for OpenHCS. 

3 

4This module defines the primary configuration objects used throughout the application, 

5such as VFSConfig, PathPlanningConfig, and the overarching GlobalPipelineConfig. 

6Configuration is intended to be immutable and provided as Python objects. 

7""" 

8 

9import logging 

10import os # For a potentially more dynamic default for num_workers 

11import threading 

12import dataclasses 

13from dataclasses import dataclass, field 

14from pathlib import Path 

15from typing import Literal, Optional, Union, Dict, Any, List, Type 

16from enum import Enum 

17from openhcs.constants import Microscope 

18from openhcs.constants.constants import Backend 

19 

20# Import TilingLayout for TUI configuration 

21try: 

22 from textual_window import TilingLayout 

23except ImportError: 

24 # Fallback for when textual-window is not available 

25 from enum import Enum 

26 class TilingLayout(Enum): 

27 FLOATING = "floating" 

28 MASTER_DETAIL = "master_detail" 

29 

30logger = logging.getLogger(__name__) 

31 

32 

33class ZarrCompressor(Enum): 

34 """Available compression algorithms for zarr storage.""" 

35 BLOSC = "blosc" 

36 ZLIB = "zlib" 

37 LZ4 = "lz4" 

38 ZSTD = "zstd" 

39 NONE = "none" 

40 

41 def create_compressor(self, compression_level: int, shuffle: bool = True) -> Optional[Any]: 

42 """Create the actual zarr compressor instance. 

43 

44 Args: 

45 compression_level: Compression level (1-22 for ZSTD, 1-9 for others) 

46 shuffle: Enable byte shuffling for better compression (blosc only) 

47 

48 Returns: 

49 Configured zarr compressor instance or None for no compression 

50 """ 

51 import zarr 

52 

53 match self: 

54 case ZarrCompressor.NONE: 54 ↛ 55line 54 didn't jump to line 55 because the pattern on line 54 never matched

55 return None 

56 case ZarrCompressor.BLOSC: 56 ↛ 57line 56 didn't jump to line 57 because the pattern on line 56 never matched

57 return zarr.Blosc(cname='lz4', clevel=compression_level, shuffle=shuffle) 

58 case ZarrCompressor.ZLIB: 58 ↛ 59line 58 didn't jump to line 59 because the pattern on line 58 never matched

59 return zarr.Zlib(level=compression_level) 

60 case ZarrCompressor.LZ4: 60 ↛ 62line 60 didn't jump to line 62 because the pattern on line 60 always matched

61 return zarr.LZ4(acceleration=compression_level) 

62 case ZarrCompressor.ZSTD: 

63 return zarr.Zstd(level=compression_level) 

64 

65 

66class ZarrChunkStrategy(Enum): 

67 """Chunking strategies for zarr arrays.""" 

68 SINGLE = "single" # Single chunk per array (optimal for batch I/O) 

69 AUTO = "auto" # Let zarr decide chunk size 

70 CUSTOM = "custom" # User-defined chunk sizes 

71 

72 

73class MaterializationBackend(Enum): 

74 """Available backends for materialization (persistent storage only).""" 

75 ZARR = "zarr" 

76 DISK = "disk" 

77 

78 

79class WellFilterMode(Enum): 

80 """Well filtering modes for selective materialization.""" 

81 INCLUDE = "include" # Materialize only specified wells 

82 EXCLUDE = "exclude" # Materialize all wells except specified ones 

83 

84@dataclass(frozen=True) 

85class ZarrConfig: 

86 """Configuration for Zarr storage backend.""" 

87 store_name: str = "images" 

88 """Name of the zarr store directory.""" 

89 

90 compressor: ZarrCompressor = ZarrCompressor.LZ4 

91 """Compression algorithm to use.""" 

92 

93 compression_level: int = 9 

94 """Compression level (1-9 for LZ4, higher = more compression).""" 

95 

96 shuffle: bool = True 

97 """Enable byte shuffling for better compression (blosc only).""" 

98 

99 chunk_strategy: ZarrChunkStrategy = ZarrChunkStrategy.SINGLE 

100 """Chunking strategy for zarr arrays.""" 

101 

102 ome_zarr_metadata: bool = True 

103 """Generate OME-ZARR compatible metadata and structure.""" 

104 

105 write_plate_metadata: bool = True 

106 """Write plate-level metadata for HCS viewing (required for OME-ZARR viewers like napari).""" 

107 

108 

109@dataclass(frozen=True) 

110class VFSConfig: 

111 """Configuration for Virtual File System (VFS) related operations.""" 

112 intermediate_backend: Backend = Backend.MEMORY 

113 """Backend for storing intermediate step results that are not explicitly materialized.""" 

114 

115 materialization_backend: MaterializationBackend = MaterializationBackend.DISK 

116 """Backend for explicitly materialized outputs (e.g., final results, user-requested saves).""" 

117 

118@dataclass(frozen=True) 

119class AnalysisConsolidationConfig: 

120 """Configuration for automatic analysis results consolidation.""" 

121 enabled: bool = True 

122 """Whether to automatically run analysis consolidation after pipeline completion.""" 

123 

124 metaxpress_style: bool = True 

125 """Whether to generate MetaXpress-compatible output format with headers.""" 

126 

127 well_pattern: str = r"([A-Z]\d{2})" 

128 """Regex pattern for extracting well IDs from filenames.""" 

129 

130 file_extensions: tuple[str, ...] = (".csv",) 

131 """File extensions to include in consolidation.""" 

132 

133 exclude_patterns: tuple[str, ...] = (r".*consolidated.*", r".*metaxpress.*", r".*summary.*") 

134 """Filename patterns to exclude from consolidation.""" 

135 

136 output_filename: str = "metaxpress_style_summary.csv" 

137 """Name of the consolidated output file.""" 

138 

139 

140@dataclass(frozen=True) 

141class PlateMetadataConfig: 

142 """Configuration for plate metadata in MetaXpress-style output.""" 

143 barcode: Optional[str] = None 

144 """Plate barcode. If None, will be auto-generated from plate name.""" 

145 

146 plate_name: Optional[str] = None 

147 """Plate name. If None, will be derived from plate path.""" 

148 

149 plate_id: Optional[str] = None 

150 """Plate ID. If None, will be auto-generated.""" 

151 

152 description: Optional[str] = None 

153 """Experiment description. If None, will be auto-generated.""" 

154 

155 acquisition_user: str = "OpenHCS" 

156 """User who acquired the data.""" 

157 

158 z_step: str = "1" 

159 """Z-step information for MetaXpress compatibility.""" 

160 

161 

162@dataclass(frozen=True) 

163class PathPlanningConfig: 

164 """ 

165 Configuration for pipeline path planning and directory structure. 

166 

167 This class handles path construction concerns including plate root directories, 

168 output directory suffixes, and subdirectory organization. It does not handle 

169 analysis results location, which is controlled at the pipeline level. 

170 """ 

171 output_dir_suffix: str = "_outputs" 

172 """Default suffix for general step output directories.""" 

173 

174 global_output_folder: Optional[Path] = None 

175 """ 

176 Optional global output folder where all plate workspaces and outputs will be created. 

177 If specified, plate workspaces will be created as {global_output_folder}/{plate_name}_workspace/ 

178 and outputs as {global_output_folder}/{plate_name}_workspace_outputs/. 

179 If None, uses the current behavior (workspace and outputs in same directory as plate). 

180 Example: "/data/results" or "/mnt/hcs_output" 

181 """ 

182 

183 sub_dir: str = "images" 

184 """ 

185 Subdirectory within plate folder for storing processed data. 

186 Automatically adds .zarr suffix when using zarr backend. 

187 Examples: "images", "processed", "data/images" 

188 """ 

189 

190 

191@dataclass(frozen=True) 

192class StepMaterializationConfig(PathPlanningConfig): 

193 """ 

194 Configuration for per-step materialization - configurable in UI. 

195 

196 This dataclass appears in the UI like any other configuration, allowing users 

197 to set pipeline-level defaults for step materialization behavior. All step 

198 materialization instances will inherit these defaults unless explicitly overridden. 

199 

200 Inherits from PathPlanningConfig to ensure all required path planning fields 

201 (like global_output_folder) are available for the lazy loading system. 

202 

203 Well Filtering Options: 

204 - well_filter=1 materializes first well only (enables quick checkpointing) 

205 - well_filter=None materializes all wells 

206 - well_filter=["A01", "B03"] materializes only specified wells 

207 - well_filter="A01:A12" materializes well range 

208 - well_filter=5 materializes first 5 wells processed 

209 - well_filter_mode controls include/exclude behavior 

210 """ 

211 

212 # Well filtering defaults 

213 well_filter: Optional[Union[List[str], str, int]] = 1 

214 """ 

215 Well filtering for selective step materialization: 

216 - 1: Materialize first well only (default - enables quick checkpointing) 

217 - None: Materialize all wells 

218 - List[str]: Specific well IDs ["A01", "B03", "D12"] 

219 - str: Pattern/range "A01:A12", "row:A", "col:01-06" 

220 - int: Maximum number of wells (first N processed) 

221 """ 

222 

223 well_filter_mode: WellFilterMode = WellFilterMode.INCLUDE 

224 """ 

225 Well filtering mode for step materialization: 

226 - INCLUDE: Materialize only wells matching the filter 

227 - EXCLUDE: Materialize all wells except those matching the filter 

228 """ 

229 

230 # Override PathPlanningConfig defaults to prevent collisions 

231 output_dir_suffix: str = "" # Uses same output plate path as main pipeline 

232 sub_dir: str = "checkpoints" # vs global "images" 

233 

234 

235# Generic thread-local storage for any global config type 

236_global_config_contexts: Dict[Type, threading.local] = {} 

237 

238def set_current_global_config(config_type: Type, config_instance: Any) -> None: 

239 """Set current global config for any dataclass type.""" 

240 if config_type not in _global_config_contexts: 

241 _global_config_contexts[config_type] = threading.local() 

242 _global_config_contexts[config_type].value = config_instance 

243 

244def get_current_global_config(config_type: Type) -> Optional[Any]: 

245 """Get current global config for any dataclass type.""" 

246 context = _global_config_contexts.get(config_type) 

247 return getattr(context, 'value', None) if context else None 

248 

249def get_current_materialization_defaults() -> StepMaterializationConfig: 

250 """Get current step materialization config from pipeline config.""" 

251 current_config = get_current_global_config(GlobalPipelineConfig) 

252 if current_config: 

253 return current_config.materialization_defaults 

254 # Fallback to default instance if no pipeline config is set 

255 return StepMaterializationConfig() 

256 

257 

258# Type registry for lazy dataclass to base class mapping 

259_lazy_type_registry: Dict[Type, Type] = {} 

260 

261def register_lazy_type_mapping(lazy_type: Type, base_type: Type) -> None: 

262 """Register mapping between lazy dataclass type and its base type.""" 

263 _lazy_type_registry[lazy_type] = base_type 

264 

265def get_base_type_for_lazy(lazy_type: Type) -> Optional[Type]: 

266 """Get the base type for a lazy dataclass type.""" 

267 return _lazy_type_registry.get(lazy_type) 

268 

269 

270class LazyDefaultPlaceholderService: 

271 """ 

272 Enhanced service supporting factory-created lazy classes with flexible resolution. 

273 

274 Provides consistent placeholder pattern for both static and dynamic lazy configuration classes. 

275 """ 

276 

277 # Configurable placeholder prefix - set to empty string for cleaner appearance 

278 PLACEHOLDER_PREFIX = "" 

279 

280 @staticmethod 

281 def has_lazy_resolution(dataclass_type: type) -> bool: 

282 """Check if dataclass has lazy resolution methods (created by factory).""" 

283 return (hasattr(dataclass_type, '_resolve_field_value') and 

284 hasattr(dataclass_type, 'to_base_config')) 

285 

286 @staticmethod 

287 def get_lazy_resolved_placeholder( 

288 dataclass_type: type, 

289 field_name: str, 

290 app_config: Optional[Any] = None, 

291 force_static_defaults: bool = False 

292 ) -> Optional[str]: 

293 """ 

294 Get placeholder text for lazy-resolved field with flexible resolution. 

295 

296 Args: 

297 dataclass_type: The lazy dataclass type (created by factory) 

298 field_name: Name of the field to resolve 

299 app_config: Optional app config for dynamic resolution 

300 force_static_defaults: If True, always use static defaults regardless of thread-local context 

301 

302 Returns: 

303 Placeholder text with configurable prefix for consistent UI experience. 

304 """ 

305 if not LazyDefaultPlaceholderService.has_lazy_resolution(dataclass_type): 

306 return None 

307 

308 if force_static_defaults: 

309 # For global config editing: always use static defaults 

310 if hasattr(dataclass_type, 'to_base_config'): 

311 # This is a lazy dataclass - get the base class and create instance with static defaults 

312 base_class = LazyDefaultPlaceholderService._get_base_class_from_lazy(dataclass_type) 

313 static_instance = base_class() 

314 resolved_value = getattr(static_instance, field_name, None) 

315 else: 

316 # Regular dataclass - create instance with static defaults 

317 static_instance = dataclass_type() 

318 resolved_value = getattr(static_instance, field_name, None) 

319 elif app_config: 

320 # For dynamic resolution, create lazy class with current app config 

321 from openhcs.core.lazy_config import LazyDataclassFactory 

322 dynamic_lazy_class = LazyDataclassFactory.create_lazy_dataclass( 

323 defaults_source=app_config, # Use the app_config directly 

324 lazy_class_name=f"Dynamic{dataclass_type.__name__}" 

325 ) 

326 temp_instance = dynamic_lazy_class() 

327 resolved_value = getattr(temp_instance, field_name) 

328 else: 

329 # Use existing lazy class (thread-local resolution) 

330 temp_instance = dataclass_type() 

331 resolved_value = getattr(temp_instance, field_name) 

332 

333 if resolved_value is not None: 

334 # Format nested dataclasses with key field values 

335 if hasattr(resolved_value, '__dataclass_fields__'): 

336 # For nested dataclasses, show key field values instead of generic info 

337 summary = LazyDefaultPlaceholderService._format_nested_dataclass_summary(resolved_value) 

338 return f"{LazyDefaultPlaceholderService.PLACEHOLDER_PREFIX}{summary}" 

339 else: 

340 return f"{LazyDefaultPlaceholderService.PLACEHOLDER_PREFIX}{resolved_value}" 

341 else: 

342 return f"{LazyDefaultPlaceholderService.PLACEHOLDER_PREFIX}(none)" 

343 

344 @staticmethod 

345 def _get_base_class_from_lazy(lazy_class: Type) -> Type: 

346 """ 

347 Extract the base class from a lazy dataclass using type registry. 

348 """ 

349 # First check the type registry 

350 base_type = get_base_type_for_lazy(lazy_class) 

351 if base_type: 

352 return base_type 

353 

354 # Check if the lazy class has a to_base_config method 

355 if hasattr(lazy_class, 'to_base_config'): 

356 # Create a dummy instance to inspect the to_base_config method 

357 dummy_instance = lazy_class() 

358 base_instance = dummy_instance.to_base_config() 

359 return type(base_instance) 

360 

361 # If no mapping found, raise an error - this indicates missing registration 

362 raise ValueError( 

363 f"No base type registered for lazy class {lazy_class.__name__}. " 

364 f"Use register_lazy_type_mapping() to register the mapping." 

365 ) 

366 

367 @staticmethod 

368 def _format_nested_dataclass_summary(dataclass_instance) -> str: 

369 """ 

370 Format nested dataclass with all field values for user-friendly placeholders. 

371 

372 Uses generic dataclass introspection to show all fields with their current values, 

373 providing a complete and maintainable summary without hardcoded field mappings. 

374 """ 

375 import dataclasses 

376 

377 class_name = dataclass_instance.__class__.__name__ 

378 

379 # Get all fields from the dataclass using introspection 

380 all_fields = [f.name for f in dataclasses.fields(dataclass_instance)] 

381 

382 # Extract all field values 

383 field_summaries = [] 

384 for field_name in all_fields: 

385 try: 

386 value = getattr(dataclass_instance, field_name) 

387 

388 # Skip None values to keep summary concise 

389 if value is None: 

390 continue 

391 

392 # Format different value types appropriately 

393 if hasattr(value, 'value'): # Enum 

394 formatted_value = value.value 

395 elif hasattr(value, 'name'): # Enum with name 

396 formatted_value = value.name 

397 elif isinstance(value, str) and len(value) > 20: # Long strings 

398 formatted_value = f"{value[:17]}..." 

399 elif dataclasses.is_dataclass(value): # Nested dataclass 

400 formatted_value = f"{value.__class__.__name__}(...)" 

401 else: 

402 formatted_value = str(value) 

403 

404 field_summaries.append(f"{field_name}={formatted_value}") 

405 

406 except (AttributeError, Exception): 

407 # Skip fields that can't be accessed 

408 continue 

409 

410 if field_summaries: 

411 return ", ".join(field_summaries) 

412 else: 

413 # Fallback when no non-None fields are found 

414 return f"{class_name} (default settings)" 

415 

416 

417# MaterializationPathConfig is now LazyStepMaterializationConfig from lazy_config.py 

418# Import moved to avoid circular dependency - use lazy import pattern 

419 

420 

421@dataclass(frozen=True) 

422class TilingKeybinding: 

423 """Declarative mapping between key combination and window manager method.""" 

424 key: str 

425 action: str # method name that already exists 

426 description: str 

427 

428 

429@dataclass(frozen=True) 

430class TilingKeybindings: 

431 """Declarative mapping of tiling keybindings to existing methods.""" 

432 

433 # Focus controls 

434 focus_next: TilingKeybinding = TilingKeybinding("ctrl+j", "focus_next_window", "Focus Next Window") 

435 focus_prev: TilingKeybinding = TilingKeybinding("ctrl+k", "focus_previous_window", "Focus Previous Window") 

436 

437 # Layout controls - map to wrapper methods 

438 horizontal_split: TilingKeybinding = TilingKeybinding("ctrl+shift+h", "set_horizontal_split", "Horizontal Split") 

439 vertical_split: TilingKeybinding = TilingKeybinding("ctrl+shift+v", "set_vertical_split", "Vertical Split") 

440 grid_layout: TilingKeybinding = TilingKeybinding("ctrl+shift+g", "set_grid_layout", "Grid Layout") 

441 master_detail: TilingKeybinding = TilingKeybinding("ctrl+shift+m", "set_master_detail", "Master Detail") 

442 toggle_floating: TilingKeybinding = TilingKeybinding("ctrl+shift+f", "toggle_floating", "Toggle Floating") 

443 

444 # Window movement - map to extracted window_manager methods 

445 move_window_prev: TilingKeybinding = TilingKeybinding("ctrl+shift+left", "move_focused_window_prev", "Move Window Left") 

446 move_window_next: TilingKeybinding = TilingKeybinding("ctrl+shift+right", "move_focused_window_next", "Move Window Right") 

447 rotate_left: TilingKeybinding = TilingKeybinding("ctrl+alt+left", "rotate_window_order_left", "Rotate Windows Left") 

448 rotate_right: TilingKeybinding = TilingKeybinding("ctrl+alt+right", "rotate_window_order_right", "Rotate Windows Right") 

449 

450 # Gap controls 

451 gap_increase: TilingKeybinding = TilingKeybinding("ctrl+plus", "gap_increase", "Increase Gap") 

452 gap_decrease: TilingKeybinding = TilingKeybinding("ctrl+minus", "gap_decrease", "Decrease Gap") 

453 

454 # Bulk operations 

455 minimize_all: TilingKeybinding = TilingKeybinding("ctrl+shift+d", "minimize_all_windows", "Minimize All") 

456 open_all: TilingKeybinding = TilingKeybinding("ctrl+shift+o", "open_all_windows", "Open All") 

457 

458 

459@dataclass(frozen=True) 

460class FunctionRegistryConfig: 

461 """Configuration for function registry behavior across all libraries.""" 

462 enable_scalar_functions: bool = True 

463 """ 

464 Whether to register functions that return scalars. 

465 When True: Scalar-returning functions are wrapped as (array, scalar) tuples. 

466 When False: Scalar-returning functions are filtered out entirely. 

467 Applies uniformly to all libraries (CuPy, scikit-image, pyclesperanto). 

468 """ 

469 

470 

471@dataclass(frozen=True) 

472class TUIConfig: 

473 """Configuration for OpenHCS Textual User Interface.""" 

474 default_tiling_layout: TilingLayout = TilingLayout.MASTER_DETAIL 

475 """Default tiling layout for window manager on startup.""" 

476 

477 default_window_gap: int = 1 

478 """Default gap between windows in tiling mode (in characters).""" 

479 

480 enable_startup_notification: bool = True 

481 """Whether to show notification about tiling mode on startup.""" 

482 

483 keybindings: TilingKeybindings = field(default_factory=TilingKeybindings) 

484 """Declarative mapping of all tiling keybindings.""" 

485 

486 

487@dataclass(frozen=True) 

488class GlobalPipelineConfig: 

489 """ 

490 Root configuration object for an OpenHCS pipeline session. 

491 This object is intended to be instantiated at application startup and treated as immutable. 

492 """ 

493 num_workers: int = field(default_factory=lambda: os.cpu_count() or 1) 

494 """Number of worker processes/threads for parallelizable tasks.""" 

495 

496 path_planning: PathPlanningConfig = field(default_factory=PathPlanningConfig) 

497 """Configuration for path planning (directory suffixes).""" 

498 

499 vfs: VFSConfig = field(default_factory=VFSConfig) 

500 """Configuration for Virtual File System behavior.""" 

501 

502 zarr: ZarrConfig = field(default_factory=ZarrConfig) 

503 """Configuration for Zarr storage backend.""" 

504 

505 materialization_results_path: Path = Path("results") 

506 """ 

507 Path for materialized analysis results (CSV, JSON files from special outputs). 

508 

509 This is a pipeline-wide setting that controls where all special output materialization 

510 functions save their analysis results, regardless of which step produces them. 

511 

512 Can be relative to plate folder or absolute path. 

513 Default: "results" creates a results/ folder in the plate directory. 

514 Examples: "results", "./analysis", "/data/analysis_results", "../shared_results" 

515 

516 Note: This is separate from per-step image materialization, which is controlled 

517 by the sub_dir field in each step's materialization_config. 

518 """ 

519 

520 analysis_consolidation: AnalysisConsolidationConfig = field(default_factory=AnalysisConsolidationConfig) 

521 """Configuration for automatic analysis results consolidation.""" 

522 

523 plate_metadata: PlateMetadataConfig = field(default_factory=PlateMetadataConfig) 

524 """Configuration for plate metadata in consolidated outputs.""" 

525 

526 function_registry: FunctionRegistryConfig = field(default_factory=FunctionRegistryConfig) 

527 """Configuration for function registry behavior.""" 

528 

529 materialization_defaults: StepMaterializationConfig = field(default_factory=StepMaterializationConfig) 

530 """Default configuration for per-step materialization - configurable in UI.""" 

531 

532 microscope: Microscope = Microscope.AUTO 

533 """Default microscope type for auto-detection.""" 

534 

535 use_threading: bool = field(default_factory=lambda: os.getenv('OPENHCS_USE_THREADING', 'false').lower() == 'true') 

536 """Use ThreadPoolExecutor instead of ProcessPoolExecutor for debugging. Reads from OPENHCS_USE_THREADING environment variable.""" 

537 

538 # Future extension point: 

539 # logging_config: Optional[Dict[str, Any]] = None # For configuring logging levels, handlers 

540 # plugin_settings: Dict[str, Any] = field(default_factory=dict) # For plugin-specific settings 

541 

542# --- Default Configuration Provider --- 

543 

544# Pre-instantiate default sub-configs for clarity if they have many fields or complex defaults. 

545# For simple cases, direct instantiation in get_default_global_config is also fine. 

546_DEFAULT_PATH_PLANNING_CONFIG = PathPlanningConfig() 

547_DEFAULT_VFS_CONFIG = VFSConfig( 

548 # Example: Set a default persistent_storage_root_path if desired for out-of-the-box behavior 

549 # persistent_storage_root_path="./openhcs_output_data" 

550) 

551_DEFAULT_ZARR_CONFIG = ZarrConfig() 

552_DEFAULT_ANALYSIS_CONSOLIDATION_CONFIG = AnalysisConsolidationConfig() 

553_DEFAULT_PLATE_METADATA_CONFIG = PlateMetadataConfig() 

554_DEFAULT_FUNCTION_REGISTRY_CONFIG = FunctionRegistryConfig() 

555_DEFAULT_MATERIALIZATION_DEFAULTS = StepMaterializationConfig() 

556_DEFAULT_TUI_CONFIG = TUIConfig() 

557 

558def get_default_global_config() -> GlobalPipelineConfig: 

559 """ 

560 Provides a default instance of GlobalPipelineConfig. 

561 

562 This function is called if no specific configuration is provided to the 

563 PipelineOrchestrator, ensuring the application can run with sensible defaults. 

564 """ 

565 logger.info("Initializing with default GlobalPipelineConfig.") 

566 return GlobalPipelineConfig( 

567 # num_workers is already handled by field(default_factory) in GlobalPipelineConfig 

568 path_planning=_DEFAULT_PATH_PLANNING_CONFIG, 

569 vfs=_DEFAULT_VFS_CONFIG, 

570 zarr=_DEFAULT_ZARR_CONFIG, 

571 analysis_consolidation=_DEFAULT_ANALYSIS_CONSOLIDATION_CONFIG, 

572 plate_metadata=_DEFAULT_PLATE_METADATA_CONFIG, 

573 function_registry=_DEFAULT_FUNCTION_REGISTRY_CONFIG, 

574 materialization_defaults=_DEFAULT_MATERIALIZATION_DEFAULTS 

575 ) 

576 

577 

578# Import pipeline-specific classes - circular import solved by moving import to end 

579from openhcs.core.pipeline_config import ( 

580 LazyStepMaterializationConfig as MaterializationPathConfig, 

581 PipelineConfig, 

582 set_current_pipeline_config, 

583 ensure_pipeline_config_context, 

584 create_pipeline_config_for_editing, 

585 create_editing_config_from_existing_lazy_config 

586)