Coverage for denofo/comparator/comparator_gui.py: 89%

157 statements  

« prev     ^ index     » next       coverage.py v7.6.12, created at 2025-04-09 15:27 +0200

1import sys 

2import warnings 

3from pathlib import Path 

4from denofo.utils.ncbiTaxDBcheck import check_NCBI_taxDB 

5from denofo.comparator.compare import write_comparison 

6from denofo.converter.convert import load_from_json 

7from denofo.utils.helpers import compare_two_models, add_extension 

8from PyQt6.QtWidgets import ( 

9 QApplication, 

10 QMainWindow, 

11 QWidget, 

12 QVBoxLayout, 

13 QHBoxLayout, 

14 QLabel, 

15 QLineEdit, 

16 QPushButton, 

17 QRadioButton, 

18 QTextEdit, 

19 QFileDialog, 

20 QButtonGroup, 

21 QMessageBox, 

22) 

23 

24 

25class DNGFCompareGUI(QMainWindow): 

26 """ 

27 The main window of the DeNoFo Comparator GUI. 

28 """ 

29 

30 def __init__(self): 

31 super().__init__() 

32 self.setWindowTitle("DeNoFo Compararator") 

33 self.setStyleSheet("background-color: #454746; color: white;") 

34 self.setMinimumWidth(600) 

35 

36 # Create main widget and layout 

37 main_widget = QWidget() 

38 self.setCentralWidget(main_widget) 

39 layout = QVBoxLayout(main_widget) 

40 

41 # Input file 1 

42 file1_layout = QHBoxLayout() 

43 self.input1_info_button = QPushButton("?") 

44 self.input1_info_button.setFixedSize(16, 16) 

45 self.input1_info_button.clicked.connect( 

46 lambda: self.show_help( 

47 "Input File 1", 

48 ( 

49 "Select the first DNGF file to compare to another DNGF file. " 

50 "The file has to be in DNGF format. If you have other file formats, " 

51 "you can convert them to DNGF using the DeNoFo Converter tool." 

52 ), 

53 ) 

54 ) 

55 self.file1_input = QLineEdit() 

56 self.file1_input.setPlaceholderText("Select first DNGF file...") 

57 file1_button = QPushButton("Browse") 

58 file1_button.clicked.connect(lambda: self.browse_file(self.file1_input)) 

59 file1_layout.addWidget(self.input1_info_button) 

60 file1_layout.addWidget(QLabel("Input 1:")) 

61 file1_layout.addWidget(self.file1_input) 

62 file1_layout.addWidget(file1_button) 

63 layout.addLayout(file1_layout) 

64 

65 # Name 1 

66 name1_layout = QHBoxLayout() 

67 self.name1_info_button = QPushButton("?") 

68 self.name1_info_button.setFixedSize(16, 16) 

69 self.name1_info_button.clicked.connect( 

70 lambda: self.show_help( 

71 "Name 1", 

72 ( 

73 "The name of the first DNGF file. This name will be used in " 

74 "the comparison output to clarify which annotation belongs to " 

75 "which file. For example, the name can be authors and year of " 

76 "the study: 'Grandchamp et al. (2021)'. " 

77 "If you don't provide a name, the default name will be 'dngf_1'. " 

78 "Name 1 and Name 2 have to be different and cannot be empty or the same." 

79 ), 

80 ) 

81 ) 

82 self.name1_input = QLineEdit() 

83 self.name1_input.setText("dngf_1") 

84 name1_layout.addWidget(self.name1_info_button) 

85 name1_layout.addWidget(QLabel("Name 1:")) 

86 name1_layout.addWidget(self.name1_input) 

87 layout.addLayout(name1_layout) 

88 

89 # Input file 2 

90 file2_layout = QHBoxLayout() 

91 self.file2_info_button = QPushButton("?") 

92 self.file2_info_button.setFixedSize(16, 16) 

93 self.file2_info_button.clicked.connect( 

94 lambda: self.show_help( 

95 "Input File 2", 

96 ( 

97 "Select the second DNGF file to compare to the first DNGF file. " 

98 "The file has to be in DNGF format. If you have other file formats, " 

99 "you can convert them to DNGF using the DeNoFo Converter tool." 

100 ), 

101 ) 

102 ) 

103 self.file2_input = QLineEdit() 

104 self.file2_input.setPlaceholderText("Select second DNGF file...") 

105 file2_button = QPushButton("Browse") 

106 file2_button.clicked.connect(lambda: self.browse_file(self.file2_input)) 

107 file2_layout.addWidget(self.file2_info_button) 

108 file2_layout.addWidget(QLabel("Input 2:")) 

109 file2_layout.addWidget(self.file2_input) 

110 file2_layout.addWidget(file2_button) 

111 layout.addLayout(file2_layout) 

112 

113 # Name 2 

114 name2_layout = QHBoxLayout() 

115 self.name2_info_button = QPushButton("?") 

116 self.name2_info_button.setFixedSize(16, 16) 

117 self.name2_info_button.clicked.connect( 

118 lambda: self.show_help( 

119 "Name 2", 

120 ( 

121 "The name of the second DNGF file. This name will be used in " 

122 "the comparison output to clarify which annotation belongs to " 

123 "which file. For example, the name can be authors and year of " 

124 "the study: 'Grandchamp et al. (2021)'. " 

125 "If you don't provide a name, the default name will be 'dngf_2'. " 

126 "Name 1 and Name 2 have to be different and cannot be empty or the same." 

127 ), 

128 ) 

129 ) 

130 self.name2_input = QLineEdit() 

131 self.name2_input.setText("dngf_2") 

132 name2_layout.addWidget(self.name2_info_button) 

133 name2_layout.addWidget(QLabel("Name 2:")) 

134 name2_layout.addWidget(self.name2_input) 

135 layout.addLayout(name2_layout) 

136 

137 # Mode selection 

138 mode_layout = QHBoxLayout() 

139 self.mode_info_button = QPushButton("?") 

140 self.mode_info_button.setFixedSize(16, 16) 

141 self.mode_info_button.clicked.connect( 

142 lambda: self.show_help( 

143 "Mode", 

144 ( 

145 "Select the mode of comparison: 'Differences' or 'Similarities'. " 

146 "Differences will show all differences between the two annotations, " 

147 "Similarities will show all similarities between the two annotations." 

148 ), 

149 ) 

150 ) 

151 mode_layout.addWidget(self.mode_info_button) 

152 mode_layout.addWidget(QLabel("Mode: ")) 

153 self.mode_group = QButtonGroup() 

154 diff_radio = QRadioButton("Differences") 

155 diff_radio.setStyleSheet( 

156 """ 

157 QRadioButton { 

158 color: white; 

159 background-color: #73403F; 

160 } 

161 QRadioButton:unchecked { 

162 color: grey; 

163 background-color: #454746; 

164 } 

165 QRadioButton:checked { 

166 color: white; 

167 background-color: #73403F; 

168 } 

169 """ 

170 ) 

171 diff_radio.setChecked(True) 

172 

173 sim_radio = QRadioButton("Similarities") 

174 sim_radio.setStyleSheet( 

175 """ 

176 QRadioButton { 

177 color: white; 

178 background-color: #4F6B90; 

179 } 

180 QRadioButton:unchecked { 

181 color: grey; 

182 background-color: #454746; 

183 } 

184 QRadioButton:checked { 

185 color: white; 

186 background-color: #4F6B90; 

187 } 

188 """ 

189 ) 

190 self.mode_group.addButton(diff_radio, 0) 

191 self.mode_group.addButton(sim_radio, 1) 

192 mode_layout.addWidget(diff_radio, stretch=1) 

193 mode_layout.addWidget(sim_radio, stretch=1) 

194 layout.addLayout(mode_layout) 

195 

196 # Output file 

197 output_layout = QHBoxLayout() 

198 self.output_info_button = QPushButton("?") 

199 self.output_info_button.setFixedSize(16, 16) 

200 self.output_info_button.clicked.connect( 

201 lambda: self.show_help( 

202 "Output File", 

203 ( 

204 "Select the output file to save the comparison results. " 

205 "If no output file is selected, the results will be only " 

206 "displayed in the GUI and not saved in a file." 

207 ), 

208 ) 

209 ) 

210 self.output_input = QLineEdit() 

211 self.output_input.setPlaceholderText("Select output file (optional)...") 

212 output_button = QPushButton("Browse") 

213 output_button.clicked.connect(lambda: self.browse_save_file(self.output_input)) 

214 output_layout.addWidget(self.output_info_button) 

215 output_layout.addWidget(QLabel("Output:")) 

216 output_layout.addWidget(self.output_input) 

217 output_layout.addWidget(output_button) 

218 layout.addLayout(output_layout) 

219 

220 # Compare button 

221 self.compare_button = QPushButton("Compare") 

222 self.compare_button.setStyleSheet( 

223 """ 

224 QPushButton { 

225 background-color: #545A61; 

226 color: white; 

227 } 

228 QPushButton:disabled { 

229 background-color: #454746; 

230 color: grey; 

231 } 

232 QPushButton:enabled { 

233 background-color: #545A61; 

234 color: white; 

235 } 

236 """ 

237 ) 

238 self.compare_button.setEnabled(False) 

239 self.compare_button.clicked.connect(self.compare_files) 

240 layout.addWidget(self.compare_button) 

241 

242 # Connect input fields to update function 

243 self.file1_input.textChanged.connect(self.update_compare_button) 

244 self.file2_input.textChanged.connect(self.update_compare_button) 

245 self.name1_input.textChanged.connect(self.update_compare_button) 

246 self.name2_input.textChanged.connect(self.update_compare_button) 

247 

248 # Results display 

249 self.results_display = QTextEdit() 

250 self.results_display.setReadOnly(True) 

251 layout.addWidget(self.results_display) 

252 

253 def show_help(self, title, message): 

254 QMessageBox.information(self, title, message) 

255 

256 # Function to update Compare button state 

257 def update_compare_button(self): 

258 enabled = all( 

259 [ 

260 self.file1_input.text().strip(), 

261 self.file2_input.text().strip(), 

262 self.name1_input.text().strip(), 

263 self.name2_input.text().strip(), 

264 self.name1_input.text() != self.name2_input.text(), 

265 ] 

266 ) 

267 self.compare_button.setEnabled(enabled) 

268 

269 def browse_file(self, line_edit): 

270 filename, _ = QFileDialog.getOpenFileName( 

271 self, "Select DNGF File", "", "DNGF Files (*.dngf);;All Files (*)" 

272 ) 

273 if filename: 

274 line_edit.setText(filename) 

275 

276 def browse_save_file(self, line_edit): 

277 filename, _ = QFileDialog.getSaveFileName( 

278 self, "Save Comparison As", "", "Text Files (*.txt);;All Files (*)" 

279 ) 

280 if filename: 

281 line_edit.setText(str(add_extension(Path(filename)))) 

282 

283 def compare_files(self): 

284 try: 

285 # Get input parameters 

286 input1 = Path(self.file1_input.text()) 

287 input2 = Path(self.file2_input.text()) 

288 name1 = self.name1_input.text() 

289 name2 = self.name2_input.text() 

290 mode = "differences" if self.mode_group.checkedId() == 0 else "similarities" 

291 output = self.output_input.text() 

292 output_path = Path(output) if output else None 

293 

294 # Load and compare files 

295 dngf1 = load_from_json(input1) 

296 dngf2 = load_from_json(input2) 

297 comparison = compare_two_models(dngf1, dngf2, mode) 

298 outstr = write_comparison(comparison, mode, None, name1, name2) 

299 if output_path: 

300 with open(output_path, "w") as f: 

301 f.write(outstr) 

302 # Display File saved message 

303 msg = QMessageBox() 

304 msg.setIcon(QMessageBox.Icon.Information) 

305 msg.setText(f"File was saved to {output_path}") 

306 msg.setWindowTitle("Success") 

307 msg.setStandardButtons(QMessageBox.StandardButton.Ok) 

308 msg.exec() 

309 

310 # Display results 

311 self.results_display.setText(outstr) 

312 

313 except Exception as e: 

314 self.results_display.setText(f"Error: {str(e)}") 

315 

316 def closeEvent(self, event): 

317 reply = QMessageBox.question( 

318 self, 

319 "Quit", 

320 "Are you sure you want to quit?", 

321 QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No, 

322 QMessageBox.StandardButton.No, 

323 ) 

324 

325 if reply == QMessageBox.StandardButton.Yes: 

326 event.accept() 

327 else: 

328 event.ignore() 

329 

330 

331def main(): 

332 """Main function to run the GUI. Entry point for the application.""" 

333 # Check the NCBI Taxonomy Database 

334 check_NCBI_taxDB() 

335 

336 warnings.filterwarnings("ignore") 

337 app = QApplication([]) 

338 window = DNGFCompareGUI() 

339 window.show() 

340 sys.exit(app.exec()) 

341 

342 

343if __name__ == "__main__": 

344 main()