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

1 import sys

2 import warnings

3 from pathlib import Path

4 from denofo.utils.ncbiTaxDBcheck import check_NCBI_taxDB

5 from denofo.comparator.compare import write_comparison

6 from denofo.converter.convert import load_from_json

7 from denofo.utils.helpers import compare_two_models, add_extension

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

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

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

343 if __name__ == "__main__":

344 main()