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

1#!/usr/bin/env python3 

2""" 

3OpenHCS PyQt6 GUI Launcher 

4 

5Launch script for the OpenHCS PyQt6 GUI application. 

6Provides command-line interface and application initialization. 

7""" 

8 

9import sys 

10import argparse 

11import logging 

12import os 

13import platform 

14from pathlib import Path 

15from typing import Optional 

16 

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 

24 

25 

26from openhcs.pyqt_gui.app import OpenHCSPyQtApp 

27 

28 

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 

35 

36 

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 

43 

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") 

50 

51 

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()) 

55 

56 # Create logs directory 

57 log_dir = Path.home() / ".local" / "share" / "openhcs" / "logs" 

58 log_dir.mkdir(parents=True, exist_ok=True) 

59 

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" 

64 

65 # Setup unified logging for entire OpenHCS system (EXACTLY like TUI) 

66 root_logger = logging.getLogger() 

67 

68 # Clear any existing handlers to ensure clean state 

69 root_logger.handlers.clear() 

70 

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')) 

74 

75 file_handler = logging.FileHandler(log_file) 

76 file_handler.setFormatter(logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')) 

77 

78 root_logger.addHandler(console_handler) 

79 root_logger.addHandler(file_handler) 

80 root_logger.setLevel(log_level_obj) 

81 

82 # Prevent other modules from adding console handlers 

83 logging.basicConfig = lambda *args, **kwargs: None 

84 

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}") 

90 

91 # Reduce noise from some libraries 

92 logging.getLogger('PIL').setLevel(logging.WARNING) 

93 

94 

95def parse_arguments(): 

96 """ 

97 Parse command line arguments. 

98  

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 ) 

113 

114 parser.add_argument( 

115 '--log-level', 

116 choices=['DEBUG', 'INFO', 'WARNING', 'ERROR'], 

117 default='INFO', 

118 help='Set logging level (default: INFO)' 

119 ) 

120 

121 parser.add_argument( 

122 '--log-file', 

123 type=Path, 

124 help='Log file path (default: console only)' 

125 ) 

126 

127 parser.add_argument( 

128 '--config', 

129 type=Path, 

130 help='Custom configuration file path' 

131 ) 

132 

133 parser.add_argument( 

134 '--no-gpu', 

135 action='store_true', 

136 help='Disable GPU acceleration' 

137 ) 

138 

139 parser.add_argument( 

140 '--version', 

141 action='version', 

142 version='OpenHCS PyQt6 GUI 1.0.0' 

143 ) 

144 

145 return parser.parse_args() 

146 

147 

148def load_configuration(config_path: Optional[Path] = None): 

149 """ 

150 Load application configuration with cache support (matches TUI pattern). 

151 

152 Args: 

153 config_path: Optional custom configuration file path 

154 

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() 

169 

170 return config 

171 

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() 

176 

177 

178def check_dependencies(): 

179 """ 

180 Check for required dependencies. 

181  

182 Returns: 

183 True if all dependencies are available, False otherwise 

184 """ 

185 missing_deps = [] 

186 

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") 

193 

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") 

200 

201 # Check other optional dependencies 

202 optional_deps = { 

203 'cupy': 'GPU acceleration', 

204 'dill': 'Pipeline serialization', 

205 'psutil': 'System monitoring' 

206 } 

207 

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") 

214 

215 if missing_deps: 

216 logging.error(f"Missing required dependencies: {', '.join(missing_deps)}") 

217 return False 

218 

219 return True 

220 

221 

222def main(): 

223 """ 

224 Main entry point for the OpenHCS PyQt6 GUI launcher. 

225  

226 Returns: 

227 Exit code 

228 """ 

229 # Parse command line arguments 

230 args = parse_arguments() 

231 

232 # Setup logging 

233 setup_logging(args.log_level, args.log_file) 

234 

235 logging.info("Starting OpenHCS PyQt6 GUI...") 

236 logging.info(f"Python version: {sys.version}") 

237 logging.info(f"Platform: {sys.platform}") 

238 

239 # Setup Qt platform (must be done before creating QApplication) 

240 setup_qt_platform() 

241 

242 try: 

243 # Check dependencies 

244 if not check_dependencies(): 

245 logging.error("Dependency check failed") 

246 return 1 

247 

248 # Load configuration 

249 config = load_configuration(args.config) 

250 

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 

256 

257 # Create and run application 

258 logging.info("Initializing PyQt6 application...") 

259 app = OpenHCSPyQtApp(sys.argv, config) 

260 

261 logging.info("Starting application event loop...") 

262 exit_code = app.run() 

263 

264 logging.info(f"Application exited with code: {exit_code}") 

265 return exit_code 

266 

267 except KeyboardInterrupt: 

268 logging.info("Application interrupted by user") 

269 return 130 # Standard exit code for Ctrl+C 

270 

271 except Exception as e: 

272 logging.critical(f"Unexpected error: {e}", exc_info=True) 

273 return 1 

274 

275 

276if __name__ == "__main__": 

277 sys.exit(main())