Coverage for openhcs/pyqt_gui/launch.py: 0.0%
141 statements
« prev ^ index » next coverage.py v7.11.0, created at 2025-11-04 02:09 +0000
« prev ^ index » next coverage.py v7.11.0, created at 2025-11-04 02:09 +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
27from openhcs.pyqt_gui.utils.window_utils import install_global_window_bounds_filter
30def is_wsl() -> bool:
31 """Check if running in Windows Subsystem for Linux."""
32 try:
33 return 'microsoft' in platform.uname().release.lower()
34 except Exception:
35 return False
38def setup_qt_platform():
39 """Setup Qt platform for different environments (macOS, Linux, WSL2, Windows)."""
40 import platform
41 from pathlib import Path
43 # Check if QT_QPA_PLATFORM is already set
44 if 'QT_QPA_PLATFORM' in os.environ:
45 logging.debug(f"QT_QPA_PLATFORM already set to: {os.environ['QT_QPA_PLATFORM']}")
46 return
48 # Set appropriate Qt platform based on OS
49 if platform.system() == 'Darwin': # macOS
50 os.environ['QT_QPA_PLATFORM'] = 'cocoa'
51 logging.info("macOS detected - setting QT_QPA_PLATFORM=cocoa")
53 # Set plugin path to help Qt find the cocoa plugin
54 # Try to find PyQt6 installation directory
55 if 'QT_QPA_PLATFORM_PLUGIN_PATH' not in os.environ:
56 try:
57 import PyQt6
58 pyqt6_path = Path(PyQt6.__file__).parent
59 plugin_path = pyqt6_path / 'Qt6' / 'plugins' / 'platforms'
60 if plugin_path.exists():
61 os.environ['QT_QPA_PLATFORM_PLUGIN_PATH'] = str(plugin_path.parent)
62 logging.info(f"Set QT_QPA_PLATFORM_PLUGIN_PATH to: {plugin_path.parent}")
63 else:
64 logging.warning(f"PyQt6 plugins directory not found at: {plugin_path}")
65 except Exception as e:
66 logging.warning(f"Could not set QT_QPA_PLATFORM_PLUGIN_PATH: {e}")
68 elif platform.system() == 'Linux':
69 os.environ['QT_QPA_PLATFORM'] = 'xcb'
70 if is_wsl():
71 logging.info("WSL2 detected - setting QT_QPA_PLATFORM=xcb")
72 else:
73 logging.info("Linux detected - setting QT_QPA_PLATFORM=xcb")
74 # Disable shared memory for X11 (helps with display issues)
75 os.environ['QT_X11_NO_MITSHM'] = '1'
76 # Windows doesn't need QT_QPA_PLATFORM set
77 else:
78 logging.debug(f"Platform {platform.system()} - using default Qt platform")
81def setup_logging(log_level: str = "INFO", log_file: Optional[Path] = None):
82 """Setup unified logging configuration for entire OpenHCS system - matches TUI exactly."""
83 log_level_obj = getattr(logging, log_level.upper())
85 # Create logs directory
86 log_dir = Path.home() / ".local" / "share" / "openhcs" / "logs"
87 log_dir.mkdir(parents=True, exist_ok=True)
89 # Create timestamped log file if not specified
90 if log_file is None:
91 import time
92 log_file = log_dir / f"openhcs_unified_{time.strftime('%Y%m%d_%H%M%S')}.log"
94 # Setup unified logging for entire OpenHCS system (EXACTLY like TUI)
95 root_logger = logging.getLogger()
97 # Clear any existing handlers to ensure clean state
98 root_logger.handlers.clear()
100 # Setup console + file logging (TUI only has file, GUI has both)
101 console_handler = logging.StreamHandler(sys.stdout)
102 console_handler.setFormatter(logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s'))
104 file_handler = logging.FileHandler(log_file)
105 file_handler.setFormatter(logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s'))
107 root_logger.addHandler(console_handler)
108 root_logger.addHandler(file_handler)
109 root_logger.setLevel(log_level_obj)
111 # Prevent other modules from adding console handlers
112 logging.basicConfig = lambda *args, **kwargs: None
114 # Set OpenHCS logger level for all components
115 logging.getLogger("openhcs").setLevel(log_level_obj)
116 logger = logging.getLogger("openhcs.pyqt_gui")
117 logger.info(f"OpenHCS PyQt6 GUI logging started - Level: {logging.getLevelName(log_level_obj)}")
118 logger.info(f"Log file: {log_file}")
120 # Reduce noise from some libraries
121 logging.getLogger('PIL').setLevel(logging.WARNING)
124def parse_arguments():
125 """
126 Parse command line arguments.
128 Returns:
129 Parsed arguments
130 """
131 parser = argparse.ArgumentParser(
132 description="OpenHCS PyQt6 GUI - High-Content Screening Platform",
133 formatter_class=argparse.RawDescriptionHelpFormatter,
134 epilog="""
135Examples:
136 %(prog)s # Launch with default settings
137 %(prog)s --log-level DEBUG # Launch with debug logging
138 %(prog)s --config config.json # Launch with custom config
139 %(prog)s --log-file app.log # Launch with log file
140 """
141 )
143 parser.add_argument(
144 '--log-level',
145 choices=['DEBUG', 'INFO', 'WARNING', 'ERROR'],
146 default='INFO',
147 help='Set logging level (default: INFO)'
148 )
150 parser.add_argument(
151 '--log-file',
152 type=Path,
153 help='Log file path (default: console only)'
154 )
156 parser.add_argument(
157 '--config',
158 type=Path,
159 help='Custom configuration file path'
160 )
162 parser.add_argument(
163 '--no-gpu',
164 action='store_true',
165 help='Disable GPU acceleration'
166 )
168 parser.add_argument(
169 '--version',
170 action='version',
171 version='OpenHCS PyQt6 GUI 1.0.0'
172 )
174 return parser.parse_args()
177def load_configuration(config_path: Optional[Path] = None):
178 """
179 Load application configuration with cache support (matches TUI pattern).
181 Args:
182 config_path: Optional custom configuration file path
184 Returns:
185 Global configuration object
186 """
187 try:
188 if config_path and config_path.exists():
189 # Load custom configuration
190 # This would need to be implemented based on config format
191 logging.info(f"Loading custom configuration from: {config_path}")
192 # For now, use default config
193 config = GlobalPipelineConfig()
194 else:
195 # Load cached configuration (matches TUI pattern)
196 from openhcs.pyqt_gui.services.config_cache_adapter import load_cached_global_config_sync
197 config = load_cached_global_config_sync()
199 return config
201 except Exception as e:
202 logging.error(f"Failed to load configuration: {e}")
203 logging.info("Falling back to default configuration")
204 return GlobalPipelineConfig()
207def check_dependencies():
208 """
209 Check for required dependencies.
211 Returns:
212 True if all dependencies are available, False otherwise
213 """
214 missing_deps = []
216 # Check PyQt6
217 try:
218 import PyQt6
219 logging.debug(f"PyQt6 version: {PyQt6.QtCore.PYQT_VERSION_STR}")
220 except ImportError:
221 missing_deps.append("PyQt6")
223 # Check PyQtGraph (optional)
224 try:
225 import pyqtgraph
226 logging.debug(f"PyQtGraph version: {pyqtgraph.__version__}")
227 except ImportError:
228 logging.warning("PyQtGraph not available - system monitor will use fallback display")
230 # Check other optional dependencies
231 optional_deps = {
232 'cupy': 'GPU acceleration',
233 'dill': 'Pipeline serialization',
234 'psutil': 'System monitoring'
235 }
237 for dep, description in optional_deps.items():
238 try:
239 __import__(dep)
240 logging.debug(f"{dep} available for {description}")
241 except ImportError:
242 logging.warning(f"{dep} not available - {description} may be limited")
244 if missing_deps:
245 logging.error(f"Missing required dependencies: {', '.join(missing_deps)}")
246 return False
248 return True
251def main():
252 """
253 Main entry point for the OpenHCS PyQt6 GUI launcher.
255 Returns:
256 Exit code
257 """
258 # Parse command line arguments
259 args = parse_arguments()
261 # Setup logging
262 setup_logging(args.log_level, args.log_file)
264 logging.info("Starting OpenHCS PyQt6 GUI...")
265 logging.info(f"Python version: {sys.version}")
266 logging.info(f"Platform: {sys.platform}")
268 # Setup Qt platform (must be done before creating QApplication)
269 setup_qt_platform()
271 try:
272 # Check dependencies
273 if not check_dependencies():
274 logging.error("Dependency check failed")
275 return 1
277 # Load configuration
278 config = load_configuration(args.config)
280 # Apply command line overrides
281 if args.no_gpu:
282 logging.info("GPU acceleration disabled by command line")
283 # This would need to be implemented in the config
284 # config.disable_gpu = True
286 # Setup GPU registry (must be done before creating app)
287 from openhcs.core.orchestrator.gpu_scheduler import setup_global_gpu_registry
288 setup_global_gpu_registry(global_config=config)
289 logging.info("GPU registry setup completed")
291 # Create and run application
292 logging.info("Initializing PyQt6 application...")
293 app = OpenHCSPyQtApp(sys.argv, config)
294 install_global_window_bounds_filter(app) # install once, early
296 logging.info("Starting application event loop...")
297 exit_code = app.run()
299 logging.info(f"Application exited with code: {exit_code}")
300 return exit_code
302 except KeyboardInterrupt:
303 logging.info("Application interrupted by user")
304 return 130 # Standard exit code for Ctrl+C
306 except Exception as e:
307 logging.critical(f"Unexpected error: {e}", exc_info=True)
308 return 1
311if __name__ == "__main__":
312 sys.exit(main())