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

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

24 

25from openhcs.pyqt_gui.app import OpenHCSPyQtApp 

26 

27 

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 

34 

35 

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 

42 

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

49 

50 

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

54 

55 # Create logs directory 

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

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

58 

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" 

63 

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

65 root_logger = logging.getLogger() 

66 

67 # Clear any existing handlers to ensure clean state 

68 root_logger.handlers.clear() 

69 

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

73 

74 file_handler = logging.FileHandler(log_file) 

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

76 

77 root_logger.addHandler(console_handler) 

78 root_logger.addHandler(file_handler) 

79 root_logger.setLevel(log_level_obj) 

80 

81 # Prevent other modules from adding console handlers 

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

83 

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

89 

90 # Reduce noise from some libraries 

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

92 

93 

94def parse_arguments(): 

95 """ 

96 Parse command line arguments. 

97  

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 ) 

112 

113 parser.add_argument( 

114 '--log-level', 

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

116 default='INFO', 

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

118 ) 

119 

120 parser.add_argument( 

121 '--log-file', 

122 type=Path, 

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

124 ) 

125 

126 parser.add_argument( 

127 '--config', 

128 type=Path, 

129 help='Custom configuration file path' 

130 ) 

131 

132 parser.add_argument( 

133 '--no-gpu', 

134 action='store_true', 

135 help='Disable GPU acceleration' 

136 ) 

137 

138 parser.add_argument( 

139 '--version', 

140 action='version', 

141 version='OpenHCS PyQt6 GUI 1.0.0' 

142 ) 

143 

144 return parser.parse_args() 

145 

146 

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

148 """ 

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

150 

151 Args: 

152 config_path: Optional custom configuration file path 

153 

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

168 

169 return config 

170 

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

175 

176 

177def check_dependencies(): 

178 """ 

179 Check for required dependencies. 

180  

181 Returns: 

182 True if all dependencies are available, False otherwise 

183 """ 

184 missing_deps = [] 

185 

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

192 

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

199 

200 # Check other optional dependencies 

201 optional_deps = { 

202 'cupy': 'GPU acceleration', 

203 'dill': 'Pipeline serialization', 

204 'psutil': 'System monitoring' 

205 } 

206 

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

213 

214 if missing_deps: 

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

216 return False 

217 

218 return True 

219 

220 

221def main(): 

222 """ 

223 Main entry point for the OpenHCS PyQt6 GUI launcher. 

224  

225 Returns: 

226 Exit code 

227 """ 

228 # Parse command line arguments 

229 args = parse_arguments() 

230 

231 # Setup logging 

232 setup_logging(args.log_level, args.log_file) 

233 

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

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

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

237 

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

239 setup_qt_platform() 

240 

241 try: 

242 # Check dependencies 

243 if not check_dependencies(): 

244 logging.error("Dependency check failed") 

245 return 1 

246 

247 # Load configuration 

248 config = load_configuration(args.config) 

249 

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 

255 

256 # Create and run application 

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

258 app = OpenHCSPyQtApp(sys.argv, config) 

259 

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

261 exit_code = app.run() 

262 

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

264 return exit_code 

265 

266 except KeyboardInterrupt: 

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

268 return 130 # Standard exit code for Ctrl+C 

269 

270 except Exception as e: 

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

272 return 1 

273 

274 

275if __name__ == "__main__": 

276 sys.exit(main())