Coverage for openhcs/pyqt_gui/launch.py: 0.0%
117 statements
« prev ^ index » next coverage.py v7.10.7, created at 2025-10-01 18:33 +0000
« prev ^ index » next coverage.py v7.10.7, created at 2025-10-01 18:33 +0000
1#!/usr/bin/env python3
2"""
3OpenHCS PyQt6 GUI Launcher
5Launch script for the OpenHCS PyQt6 GUI application.
6Provides command-line interface and application initialization.
7"""
9import sys
10import argparse
11import logging
12import os
13import platform
14from pathlib import Path
15from typing import Optional
17# Add OpenHCS to path if needed
18try:
19 from openhcs.core.config import GlobalPipelineConfig
20except ImportError:
21 # Add parent directory to path
22 sys.path.insert(0, str(Path(__file__).parent.parent.parent))
23 from openhcs.core.config import GlobalPipelineConfig
26from openhcs.pyqt_gui.app import OpenHCSPyQtApp
29def is_wsl() -> bool:
30 """Check if running in Windows Subsystem for Linux."""
31 try:
32 return 'microsoft' in platform.uname().release.lower()
33 except Exception:
34 return False
37def setup_qt_platform():
38 """Setup Qt platform for different environments, especially WSL2."""
39 # Check if QT_QPA_PLATFORM is already set
40 if 'QT_QPA_PLATFORM' in os.environ:
41 logging.debug(f"QT_QPA_PLATFORM already set to: {os.environ['QT_QPA_PLATFORM']}")
42 return
44 # For WSL2, we need to explicitly set the platform to xcb
45 if is_wsl():
46 os.environ['QT_QPA_PLATFORM'] = 'xcb'
47 logging.info("WSL2 detected - setting QT_QPA_PLATFORM=xcb")
48 else:
49 logging.debug("Not running in WSL2 - using default Qt platform")
52def setup_logging(log_level: str = "INFO", log_file: Optional[Path] = None):
53 """Setup unified logging configuration for entire OpenHCS system - matches TUI exactly."""
54 log_level_obj = getattr(logging, log_level.upper())
56 # Create logs directory
57 log_dir = Path.home() / ".local" / "share" / "openhcs" / "logs"
58 log_dir.mkdir(parents=True, exist_ok=True)
60 # Create timestamped log file if not specified
61 if log_file is None:
62 import time
63 log_file = log_dir / f"openhcs_unified_{time.strftime('%Y%m%d_%H%M%S')}.log"
65 # Setup unified logging for entire OpenHCS system (EXACTLY like TUI)
66 root_logger = logging.getLogger()
68 # Clear any existing handlers to ensure clean state
69 root_logger.handlers.clear()
71 # Setup console + file logging (TUI only has file, GUI has both)
72 console_handler = logging.StreamHandler(sys.stdout)
73 console_handler.setFormatter(logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s'))
75 file_handler = logging.FileHandler(log_file)
76 file_handler.setFormatter(logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s'))
78 root_logger.addHandler(console_handler)
79 root_logger.addHandler(file_handler)
80 root_logger.setLevel(log_level_obj)
82 # Prevent other modules from adding console handlers
83 logging.basicConfig = lambda *args, **kwargs: None
85 # Set OpenHCS logger level for all components
86 logging.getLogger("openhcs").setLevel(log_level_obj)
87 logger = logging.getLogger("openhcs.pyqt_gui")
88 logger.info(f"OpenHCS PyQt6 GUI logging started - Level: {logging.getLevelName(log_level_obj)}")
89 logger.info(f"Log file: {log_file}")
91 # Reduce noise from some libraries
92 logging.getLogger('PIL').setLevel(logging.WARNING)
95def parse_arguments():
96 """
97 Parse command line arguments.
99 Returns:
100 Parsed arguments
101 """
102 parser = argparse.ArgumentParser(
103 description="OpenHCS PyQt6 GUI - High-Content Screening Platform",
104 formatter_class=argparse.RawDescriptionHelpFormatter,
105 epilog="""
106Examples:
107 %(prog)s # Launch with default settings
108 %(prog)s --log-level DEBUG # Launch with debug logging
109 %(prog)s --config config.json # Launch with custom config
110 %(prog)s --log-file app.log # Launch with log file
111 """
112 )
114 parser.add_argument(
115 '--log-level',
116 choices=['DEBUG', 'INFO', 'WARNING', 'ERROR'],
117 default='INFO',
118 help='Set logging level (default: INFO)'
119 )
121 parser.add_argument(
122 '--log-file',
123 type=Path,
124 help='Log file path (default: console only)'
125 )
127 parser.add_argument(
128 '--config',
129 type=Path,
130 help='Custom configuration file path'
131 )
133 parser.add_argument(
134 '--no-gpu',
135 action='store_true',
136 help='Disable GPU acceleration'
137 )
139 parser.add_argument(
140 '--version',
141 action='version',
142 version='OpenHCS PyQt6 GUI 1.0.0'
143 )
145 return parser.parse_args()
148def load_configuration(config_path: Optional[Path] = None):
149 """
150 Load application configuration with cache support (matches TUI pattern).
152 Args:
153 config_path: Optional custom configuration file path
155 Returns:
156 Global configuration object
157 """
158 try:
159 if config_path and config_path.exists():
160 # Load custom configuration
161 # This would need to be implemented based on config format
162 logging.info(f"Loading custom configuration from: {config_path}")
163 # For now, use default config
164 config = GlobalPipelineConfig()
165 else:
166 # Load cached configuration (matches TUI pattern)
167 from openhcs.pyqt_gui.services.config_cache_adapter import load_cached_global_config_sync
168 config = load_cached_global_config_sync()
170 return config
172 except Exception as e:
173 logging.error(f"Failed to load configuration: {e}")
174 logging.info("Falling back to default configuration")
175 return GlobalPipelineConfig()
178def check_dependencies():
179 """
180 Check for required dependencies.
182 Returns:
183 True if all dependencies are available, False otherwise
184 """
185 missing_deps = []
187 # Check PyQt6
188 try:
189 import PyQt6
190 logging.debug(f"PyQt6 version: {PyQt6.QtCore.PYQT_VERSION_STR}")
191 except ImportError:
192 missing_deps.append("PyQt6")
194 # Check PyQtGraph (optional)
195 try:
196 import pyqtgraph
197 logging.debug(f"PyQtGraph version: {pyqtgraph.__version__}")
198 except ImportError:
199 logging.warning("PyQtGraph not available - system monitor will use fallback display")
201 # Check other optional dependencies
202 optional_deps = {
203 'cupy': 'GPU acceleration',
204 'dill': 'Pipeline serialization',
205 'psutil': 'System monitoring'
206 }
208 for dep, description in optional_deps.items():
209 try:
210 __import__(dep)
211 logging.debug(f"{dep} available for {description}")
212 except ImportError:
213 logging.warning(f"{dep} not available - {description} may be limited")
215 if missing_deps:
216 logging.error(f"Missing required dependencies: {', '.join(missing_deps)}")
217 return False
219 return True
222def main():
223 """
224 Main entry point for the OpenHCS PyQt6 GUI launcher.
226 Returns:
227 Exit code
228 """
229 # Parse command line arguments
230 args = parse_arguments()
232 # Setup logging
233 setup_logging(args.log_level, args.log_file)
235 logging.info("Starting OpenHCS PyQt6 GUI...")
236 logging.info(f"Python version: {sys.version}")
237 logging.info(f"Platform: {sys.platform}")
239 # Setup Qt platform (must be done before creating QApplication)
240 setup_qt_platform()
242 try:
243 # Check dependencies
244 if not check_dependencies():
245 logging.error("Dependency check failed")
246 return 1
248 # Load configuration
249 config = load_configuration(args.config)
251 # Apply command line overrides
252 if args.no_gpu:
253 logging.info("GPU acceleration disabled by command line")
254 # This would need to be implemented in the config
255 # config.disable_gpu = True
257 # Create and run application
258 logging.info("Initializing PyQt6 application...")
259 app = OpenHCSPyQtApp(sys.argv, config)
261 logging.info("Starting application event loop...")
262 exit_code = app.run()
264 logging.info(f"Application exited with code: {exit_code}")
265 return exit_code
267 except KeyboardInterrupt:
268 logging.info("Application interrupted by user")
269 return 130 # Standard exit code for Ctrl+C
271 except Exception as e:
272 logging.critical(f"Unexpected error: {e}", exc_info=True)
273 return 1
276if __name__ == "__main__":
277 sys.exit(main())