Coverage for openhcs/pyqt_gui/launch.py: 0.0%
117 statements
« prev ^ index » next coverage.py v7.10.3, created at 2025-08-14 05:57 +0000
« prev ^ index » next coverage.py v7.10.3, created at 2025-08-14 05:57 +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 get_default_global_config
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 get_default_global_config
25from openhcs.pyqt_gui.app import OpenHCSPyQtApp
28def is_wsl() -> bool:
29 """Check if running in Windows Subsystem for Linux."""
30 try:
31 return 'microsoft' in platform.uname().release.lower()
32 except Exception:
33 return False
36def setup_qt_platform():
37 """Setup Qt platform for different environments, especially WSL2."""
38 # Check if QT_QPA_PLATFORM is already set
39 if 'QT_QPA_PLATFORM' in os.environ:
40 logging.debug(f"QT_QPA_PLATFORM already set to: {os.environ['QT_QPA_PLATFORM']}")
41 return
43 # For WSL2, we need to explicitly set the platform to xcb
44 if is_wsl():
45 os.environ['QT_QPA_PLATFORM'] = 'xcb'
46 logging.info("WSL2 detected - setting QT_QPA_PLATFORM=xcb")
47 else:
48 logging.debug("Not running in WSL2 - using default Qt platform")
51def setup_logging(log_level: str = "INFO", log_file: Optional[Path] = None):
52 """Setup unified logging configuration for entire OpenHCS system - matches TUI exactly."""
53 log_level_obj = getattr(logging, log_level.upper())
55 # Create logs directory
56 log_dir = Path.home() / ".local" / "share" / "openhcs" / "logs"
57 log_dir.mkdir(parents=True, exist_ok=True)
59 # Create timestamped log file if not specified
60 if log_file is None:
61 import time
62 log_file = log_dir / f"openhcs_unified_{time.strftime('%Y%m%d_%H%M%S')}.log"
64 # Setup unified logging for entire OpenHCS system (EXACTLY like TUI)
65 root_logger = logging.getLogger()
67 # Clear any existing handlers to ensure clean state
68 root_logger.handlers.clear()
70 # Setup console + file logging (TUI only has file, GUI has both)
71 console_handler = logging.StreamHandler(sys.stdout)
72 console_handler.setFormatter(logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s'))
74 file_handler = logging.FileHandler(log_file)
75 file_handler.setFormatter(logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s'))
77 root_logger.addHandler(console_handler)
78 root_logger.addHandler(file_handler)
79 root_logger.setLevel(log_level_obj)
81 # Prevent other modules from adding console handlers
82 logging.basicConfig = lambda *args, **kwargs: None
84 # Set OpenHCS logger level for all components
85 logging.getLogger("openhcs").setLevel(log_level_obj)
86 logger = logging.getLogger("openhcs.pyqt_gui")
87 logger.info(f"OpenHCS PyQt6 GUI logging started - Level: {logging.getLevelName(log_level_obj)}")
88 logger.info(f"Log file: {log_file}")
90 # Reduce noise from some libraries
91 logging.getLogger('PIL').setLevel(logging.WARNING)
94def parse_arguments():
95 """
96 Parse command line arguments.
98 Returns:
99 Parsed arguments
100 """
101 parser = argparse.ArgumentParser(
102 description="OpenHCS PyQt6 GUI - High-Content Screening Platform",
103 formatter_class=argparse.RawDescriptionHelpFormatter,
104 epilog="""
105Examples:
106 %(prog)s # Launch with default settings
107 %(prog)s --log-level DEBUG # Launch with debug logging
108 %(prog)s --config config.json # Launch with custom config
109 %(prog)s --log-file app.log # Launch with log file
110 """
111 )
113 parser.add_argument(
114 '--log-level',
115 choices=['DEBUG', 'INFO', 'WARNING', 'ERROR'],
116 default='INFO',
117 help='Set logging level (default: INFO)'
118 )
120 parser.add_argument(
121 '--log-file',
122 type=Path,
123 help='Log file path (default: console only)'
124 )
126 parser.add_argument(
127 '--config',
128 type=Path,
129 help='Custom configuration file path'
130 )
132 parser.add_argument(
133 '--no-gpu',
134 action='store_true',
135 help='Disable GPU acceleration'
136 )
138 parser.add_argument(
139 '--version',
140 action='version',
141 version='OpenHCS PyQt6 GUI 1.0.0'
142 )
144 return parser.parse_args()
147def load_configuration(config_path: Optional[Path] = None):
148 """
149 Load application configuration with cache support (matches TUI pattern).
151 Args:
152 config_path: Optional custom configuration file path
154 Returns:
155 Global configuration object
156 """
157 try:
158 if config_path and config_path.exists():
159 # Load custom configuration
160 # This would need to be implemented based on config format
161 logging.info(f"Loading custom configuration from: {config_path}")
162 # For now, use default config
163 config = get_default_global_config()
164 else:
165 # Load cached configuration (matches TUI pattern)
166 from openhcs.pyqt_gui.services.config_cache_adapter import load_cached_global_config_sync
167 config = load_cached_global_config_sync()
169 return config
171 except Exception as e:
172 logging.error(f"Failed to load configuration: {e}")
173 logging.info("Falling back to default configuration")
174 return get_default_global_config()
177def check_dependencies():
178 """
179 Check for required dependencies.
181 Returns:
182 True if all dependencies are available, False otherwise
183 """
184 missing_deps = []
186 # Check PyQt6
187 try:
188 import PyQt6
189 logging.debug(f"PyQt6 version: {PyQt6.QtCore.PYQT_VERSION_STR}")
190 except ImportError:
191 missing_deps.append("PyQt6")
193 # Check PyQtGraph (optional)
194 try:
195 import pyqtgraph
196 logging.debug(f"PyQtGraph version: {pyqtgraph.__version__}")
197 except ImportError:
198 logging.warning("PyQtGraph not available - system monitor will use fallback display")
200 # Check other optional dependencies
201 optional_deps = {
202 'cupy': 'GPU acceleration',
203 'dill': 'Pipeline serialization',
204 'psutil': 'System monitoring'
205 }
207 for dep, description in optional_deps.items():
208 try:
209 __import__(dep)
210 logging.debug(f"{dep} available for {description}")
211 except ImportError:
212 logging.warning(f"{dep} not available - {description} may be limited")
214 if missing_deps:
215 logging.error(f"Missing required dependencies: {', '.join(missing_deps)}")
216 return False
218 return True
221def main():
222 """
223 Main entry point for the OpenHCS PyQt6 GUI launcher.
225 Returns:
226 Exit code
227 """
228 # Parse command line arguments
229 args = parse_arguments()
231 # Setup logging
232 setup_logging(args.log_level, args.log_file)
234 logging.info("Starting OpenHCS PyQt6 GUI...")
235 logging.info(f"Python version: {sys.version}")
236 logging.info(f"Platform: {sys.platform}")
238 # Setup Qt platform (must be done before creating QApplication)
239 setup_qt_platform()
241 try:
242 # Check dependencies
243 if not check_dependencies():
244 logging.error("Dependency check failed")
245 return 1
247 # Load configuration
248 config = load_configuration(args.config)
250 # Apply command line overrides
251 if args.no_gpu:
252 logging.info("GPU acceleration disabled by command line")
253 # This would need to be implemented in the config
254 # config.disable_gpu = True
256 # Create and run application
257 logging.info("Initializing PyQt6 application...")
258 app = OpenHCSPyQtApp(sys.argv, config)
260 logging.info("Starting application event loop...")
261 exit_code = app.run()
263 logging.info(f"Application exited with code: {exit_code}")
264 return exit_code
266 except KeyboardInterrupt:
267 logging.info("Application interrupted by user")
268 return 130 # Standard exit code for Ctrl+C
270 except Exception as e:
271 logging.critical(f"Unexpected error: {e}", exc_info=True)
272 return 1
275if __name__ == "__main__":
276 sys.exit(main())