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
« 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)
25class DNGFCompareGUI(QMainWindow):
26 """
27 The main window of the DeNoFo Comparator GUI.
28 """
30 def __init__(self):
31 super().__init__()
32 self.setWindowTitle("DeNoFo Compararator")
33 self.setStyleSheet("background-color: #454746; color: white;")
34 self.setMinimumWidth(600)
36 # Create main widget and layout
37 main_widget = QWidget()
38 self.setCentralWidget(main_widget)
39 layout = QVBoxLayout(main_widget)
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)
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)
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)
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)
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)
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)
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)
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)
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)
248 # Results display
249 self.results_display = QTextEdit()
250 self.results_display.setReadOnly(True)
251 layout.addWidget(self.results_display)
253 def show_help(self, title, message):
254 QMessageBox.information(self, title, message)
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)
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)
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))))
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
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()
310 # Display results
311 self.results_display.setText(outstr)
313 except Exception as e:
314 self.results_display.setText(f"Error: {str(e)}")
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 )
325 if reply == QMessageBox.StandardButton.Yes:
326 event.accept()
327 else:
328 event.ignore()
331def main():
332 """Main function to run the GUI. Entry point for the application."""
333 # Check the NCBI Taxonomy Database
334 check_NCBI_taxDB()
336 warnings.filterwarnings("ignore")
337 app = QApplication([])
338 window = DNGFCompareGUI()
339 window.show()
340 sys.exit(app.exec())
343if __name__ == "__main__":
344 main()