MIDRC-MELODY 0.3.3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. MIDRC_MELODY/__init__.py +0 -0
  2. MIDRC_MELODY/__main__.py +4 -0
  3. MIDRC_MELODY/common/__init__.py +0 -0
  4. MIDRC_MELODY/common/data_loading.py +199 -0
  5. MIDRC_MELODY/common/data_preprocessing.py +134 -0
  6. MIDRC_MELODY/common/edit_config.py +156 -0
  7. MIDRC_MELODY/common/eod_aaod_metrics.py +292 -0
  8. MIDRC_MELODY/common/generate_eod_aaod_spiders.py +69 -0
  9. MIDRC_MELODY/common/generate_qwk_spiders.py +56 -0
  10. MIDRC_MELODY/common/matplotlib_spider.py +425 -0
  11. MIDRC_MELODY/common/plot_tools.py +132 -0
  12. MIDRC_MELODY/common/plotly_spider.py +217 -0
  13. MIDRC_MELODY/common/qwk_metrics.py +244 -0
  14. MIDRC_MELODY/common/table_tools.py +230 -0
  15. MIDRC_MELODY/gui/__init__.py +0 -0
  16. MIDRC_MELODY/gui/config_editor.py +200 -0
  17. MIDRC_MELODY/gui/data_loading.py +157 -0
  18. MIDRC_MELODY/gui/main_controller.py +154 -0
  19. MIDRC_MELODY/gui/main_window.py +545 -0
  20. MIDRC_MELODY/gui/matplotlib_spider_widget.py +204 -0
  21. MIDRC_MELODY/gui/metrics_model.py +62 -0
  22. MIDRC_MELODY/gui/plotly_spider_widget.py +56 -0
  23. MIDRC_MELODY/gui/qchart_spider_widget.py +272 -0
  24. MIDRC_MELODY/gui/shared/__init__.py +0 -0
  25. MIDRC_MELODY/gui/shared/react/__init__.py +0 -0
  26. MIDRC_MELODY/gui/shared/react/copyabletableview.py +100 -0
  27. MIDRC_MELODY/gui/shared/react/grabbablewidget.py +406 -0
  28. MIDRC_MELODY/gui/tqdm_handler.py +210 -0
  29. MIDRC_MELODY/melody.py +102 -0
  30. MIDRC_MELODY/melody_gui.py +111 -0
  31. MIDRC_MELODY/resources/MIDRC.ico +0 -0
  32. midrc_melody-0.3.3.dist-info/METADATA +151 -0
  33. midrc_melody-0.3.3.dist-info/RECORD +37 -0
  34. midrc_melody-0.3.3.dist-info/WHEEL +5 -0
  35. midrc_melody-0.3.3.dist-info/entry_points.txt +4 -0
  36. midrc_melody-0.3.3.dist-info/licenses/LICENSE +201 -0
  37. midrc_melody-0.3.3.dist-info/top_level.txt +1 -0
@@ -0,0 +1,545 @@
1
+ # Copyright (c) 2025 Medical Imaging and Data Resource Center (MIDRC).
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ """
16
+ main_window.py
17
+
18
+ Main application window for MIDRC Melody GUI. Handles menus, toolbar actions,
19
+ and central tab widget, including spider-chart tabs, data tables, and progress output.
20
+ """
21
+
22
+ from typing import Any, Dict, List, Optional, Tuple
23
+
24
+ from PySide6.QtCore import Qt, QThreadPool, Slot
25
+ from PySide6.QtGui import QAction, QBrush, QFontDatabase, QIcon, QColor
26
+ from PySide6.QtWidgets import (
27
+ QMainWindow,
28
+ QPlainTextEdit,
29
+ QSizePolicy,
30
+ QTabWidget,
31
+ QWidget,
32
+ QTableWidgetItem,
33
+ )
34
+
35
+ from MIDRC_MELODY.common.eod_aaod_metrics import (
36
+ create_spider_plot_data_eod_aaod,
37
+ generate_plot_data_eod_aaod,
38
+ )
39
+ from MIDRC_MELODY.common.plot_tools import SpiderPlotData
40
+ from MIDRC_MELODY.common.qwk_metrics import create_spider_plot_data_qwk
41
+
42
+ from MIDRC_MELODY.gui.shared.react.copyabletableview import CopyableTableWidget
43
+ from MIDRC_MELODY.gui.matplotlib_spider_widget import (
44
+ MatplotlibSpiderWidget,
45
+ display_spider_charts_in_tabs_matplotlib as display_spider_charts_in_tabs,
46
+ )
47
+ from MIDRC_MELODY.gui.tqdm_handler import ANSIProcessor
48
+ from MIDRC_MELODY.gui.data_loading import load_config_file, edit_config_file
49
+ from MIDRC_MELODY.gui.main_controller import MainController
50
+
51
+
52
+ class NumericSortTableWidgetItem(QTableWidgetItem):
53
+ """
54
+ A QTableWidgetItem that sorts numerically when its text can be parsed as float.
55
+
56
+ Falls back to default string comparison on ValueError.
57
+ """
58
+
59
+ def __lt__(self, other: QTableWidgetItem) -> bool:
60
+ """
61
+ Compare two items as floats when possible.
62
+
63
+ Parameters
64
+ ----------
65
+ other : QTableWidgetItem
66
+ The other table widget item to compare.
67
+
68
+ Returns
69
+ -------
70
+ bool
71
+ True if self < other numerically, otherwise lexical comparison.
72
+ """
73
+ try:
74
+ return float(self.text()) < float(other.text())
75
+ except ValueError:
76
+ return super().__lt__(other)
77
+
78
+
79
+ def _add_ref_group(
80
+ rows: List[Tuple[List[str], QColor]], ref_groups: Dict[str, str]
81
+ ) -> List[Tuple[List[str], QColor]]:
82
+ """
83
+ Add a reference group (from ref_groups) to each row based on the category.
84
+
85
+ If the category (row_data[1]) is not found in ref_groups, use "N/A".
86
+
87
+ Parameters
88
+ ----------
89
+ rows : list of tuple(list of str, QColor)
90
+ Each tuple contains a row's data and an optional color.
91
+ ref_groups : dict of str to str
92
+ Mapping from category to reference group string.
93
+
94
+ Returns
95
+ -------
96
+ list of tuple(list of str, QColor)
97
+ New list of rows with reference group inserted at index 2.
98
+ """
99
+ new_rows: List[Tuple[List[str], QColor]] = []
100
+ for row_data, color in rows:
101
+ row_copy = list(row_data)
102
+ category = row_copy[1]
103
+ ref = ref_groups.get(category, "N/A")
104
+ row_copy.insert(2, ref)
105
+ new_rows.append((row_copy, color))
106
+ return new_rows
107
+
108
+
109
+ class MainWindow(QMainWindow):
110
+ """
111
+ Main application window for the Melody GUI.
112
+
113
+ Manages the toolbar, menus, and the central QTabWidget which holds
114
+ progress output, data tables, and spider-chart tabs. Provides actions
115
+ for loading/editing config and toggling Matplotlib spider-chart toolbars.
116
+ """
117
+
118
+ def __init__(self, parent: Optional[QWidget] = None) -> None:
119
+ """
120
+ Initialize the MainWindow.
121
+
122
+ Parameters
123
+ ----------
124
+ parent : QWidget, optional
125
+ Parent widget in the Qt hierarchy. Defaults to None.
126
+ """
127
+ super().__init__(parent)
128
+ self.setWindowTitle("Melody GUI")
129
+ self.resize(1200, 600)
130
+
131
+ # Thread pool for background tasks
132
+ self.threadpool = QThreadPool()
133
+
134
+ # Whether to show Matplotlib toolbar in spider-chart tabs
135
+ self._show_mpl_toolbar: bool = False
136
+
137
+ # Instantiate controller and hand it this window
138
+ self.controller = MainController(self)
139
+
140
+ # Build the menu bar, toolbar, and central widget
141
+ self._create_menu_bar()
142
+ self._create_tool_bar()
143
+ self._create_central_widget()
144
+
145
+ self.chart_tabs: Dict[str, QTabWidget] = {} # Store chart tab widgetss by name
146
+
147
+ # Prepare the progress view (console) as a QPlainTextEdit (hidden by default)
148
+ self.progress_view: QPlainTextEdit = QPlainTextEdit()
149
+ self._ansi_processor: Optional[ANSIProcessor] = None # Initialized on first use
150
+
151
+ def _create_menu_bar(self) -> None:
152
+ """
153
+ Build the application's menu bar with File and Configuration menus.
154
+ """
155
+ menu_bar = self.menuBar()
156
+
157
+ # File menu: Load Config File
158
+ file_menu = menu_bar.addMenu("File")
159
+ load_config_act = QAction("Load Config File", self)
160
+ load_config_act.triggered.connect(self.load_config_file)
161
+ file_menu.addAction(load_config_act)
162
+
163
+ # Configuration menu: Edit Config File
164
+ config_menu = menu_bar.addMenu("Configuration")
165
+ edit_config_act = QAction("Edit Config File", self)
166
+ edit_config_act.triggered.connect(self.edit_config)
167
+ config_menu.addAction(edit_config_act)
168
+
169
+ def _create_tool_bar(self) -> None:
170
+ """
171
+ Build the main toolbar with actions for metrics, config, and toggling spider-chart toolbar.
172
+ """
173
+ toolbar = self.addToolBar("MainToolbar")
174
+ toolbar.setToolButtonStyle(Qt.ToolButtonTextUnderIcon)
175
+
176
+ # QWK Metrics action
177
+ qwk_icon = QIcon.fromTheme("accessories-calculator")
178
+ qwk_act = QAction(qwk_icon, "QWK Metrics", self) # Use K instead of κ for toolbar in case of font issues
179
+ qwk_act.setToolTip("Calculate QWκ Metrics")
180
+ qwk_act.triggered.connect(self.controller.calculate_qwk)
181
+ toolbar.addAction(qwk_act)
182
+
183
+ # EOD/AAOD Metrics action
184
+ eod_icon = QIcon.fromTheme(QIcon.ThemeIcon.Computer)
185
+ eod_act = QAction(eod_icon, "EOD/AAOD Metrics", self)
186
+ eod_act.setToolTip("Calculate EOD/AAOD Metrics")
187
+ eod_act.triggered.connect(self.controller.calculate_eod_aaod)
188
+ toolbar.addAction(eod_act)
189
+
190
+ # Spacer to push next actions to the right
191
+ spacer = QWidget()
192
+ spacer.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred)
193
+ toolbar.addWidget(spacer)
194
+
195
+ # Checkable action: Show/Hide Matplotlib spider-chart toolbar
196
+ self._toggle_mpl_act = QAction(
197
+ f"{'Hide' if self._show_mpl_toolbar else 'Show'} Plot Toolbar", self
198
+ )
199
+ self._toggle_mpl_act.setCheckable(True)
200
+ self._toggle_mpl_act.setChecked(self._show_mpl_toolbar)
201
+ self._toggle_mpl_act.setToolTip(
202
+ "Toggle the Matplotlib navigation toolbar in spider-chart tabs"
203
+ )
204
+ self._toggle_mpl_act.toggled.connect(self._on_toggle_mpl_toolbar)
205
+ toolbar.addAction(self._toggle_mpl_act)
206
+
207
+ # Config action
208
+ config_icon = QIcon.fromTheme(QIcon.ThemeIcon.DocumentProperties)
209
+ config_act = QAction(config_icon, "Config", self)
210
+ config_act.setToolTip("Edit Configuration")
211
+ config_act.triggered.connect(self.edit_config)
212
+ toolbar.addAction(config_act)
213
+
214
+ def _create_central_widget(self) -> None:
215
+ """
216
+ Create and set the central widget as a QTabWidget.
217
+
218
+ Tab index 0 will hold the progress view; subsequent tabs are for data and charts.
219
+ """
220
+ tab_widget = QTabWidget()
221
+ tab_widget.setMovable(True)
222
+ self.setCentralWidget(tab_widget)
223
+
224
+ @Slot()
225
+ def load_config_file(self) -> None:
226
+ """
227
+ Slot to load configuration file via the data_loading module.
228
+ """
229
+ load_config_file(self)
230
+
231
+ @Slot()
232
+ def edit_config(self) -> None:
233
+ """
234
+ Slot to edit the configuration file via the data_loading module.
235
+ """
236
+ edit_config_file(self)
237
+
238
+ @Slot(bool)
239
+ def _on_toggle_mpl_toolbar(self, checked: bool) -> None:
240
+ """
241
+ Show or hide the Matplotlib spider-chart toolbar across all existing tabs
242
+ and update the flag for future tabs.
243
+
244
+ Parameters
245
+ ----------
246
+ checked : bool
247
+ True to show the toolbar, False to hide it.
248
+ """
249
+ self._show_mpl_toolbar = checked
250
+ self._toggle_mpl_act.setText(f"{'Hide' if checked else 'Show'} Plot Toolbar")
251
+
252
+ tabs: QTabWidget = self.centralWidget() # type: ignore
253
+ for idx in range(tabs.count()):
254
+ page = tabs.widget(idx)
255
+ spiders = page.findChildren(MatplotlibSpiderWidget)
256
+ for spider in spiders:
257
+ spider.set_toolbar_visible(checked)
258
+
259
+ def show_progress_view(self) -> None:
260
+ """
261
+ Insert or reinsert the read-only console tab (QPlainTextEdit) at index 0
262
+ so that redirected output appears there.
263
+ """
264
+ fixed_font = QFontDatabase.systemFont(QFontDatabase.FixedFont)
265
+ fixed_font.setPointSize(10)
266
+ self.progress_view.setFont(fixed_font)
267
+ self.progress_view.setLineWrapMode(QPlainTextEdit.NoWrap)
268
+ self.progress_view.setReadOnly(True)
269
+
270
+ tabs: QTabWidget = self.centralWidget() # type: ignore
271
+
272
+ # Remove any existing "Progress Output" tab
273
+ for i in range(tabs.count()):
274
+ if tabs.tabText(i) == "Progress Output":
275
+ tabs.removeTab(i)
276
+ break
277
+
278
+ # Insert new progress tab at index 0 and make it current
279
+ tabs.insertTab(0, self.progress_view, "Progress Output")
280
+ tabs.setCurrentIndex(0)
281
+
282
+ def append_progress(self, text: str) -> None:
283
+ """
284
+ Feed each chunk of emitted text through ANSIProcessor so colors/formatting appear.
285
+
286
+ Parameters
287
+ ----------
288
+ text : str
289
+ The text chunk containing possible ANSI escape sequences.
290
+ """
291
+ if not self._ansi_processor:
292
+ self._ansi_processor = ANSIProcessor()
293
+ self._ansi_processor.process(self.progress_view, text)
294
+
295
+ def update_tabs(
296
+ self, tab_dict: Dict[QWidget, str], set_current: bool = True
297
+ ) -> None:
298
+ """
299
+ Given a mapping {widget: tab_title}, remove existing tabs with those titles,
300
+ then insert each new tab starting at index 1 (index 0 is reserved for progress).
301
+
302
+ Parameters
303
+ ----------
304
+ tab_dict : dict
305
+ Mapping of QWidget instances to their desired tab titles.
306
+ set_current : bool, optional
307
+ If True, switch to the first new tab after insertion. Defaults to True.
308
+ """
309
+ tabs: QTabWidget = self.centralWidget() # type: ignore
310
+
311
+ # Remove tabs whose title matches any in tab_dict
312
+ for i in reversed(range(tabs.count())):
313
+ if tabs.tabText(i) in tab_dict.values():
314
+ tabs.removeTab(i)
315
+
316
+ # Insert new tabs at indices starting from 1
317
+ for idx, (widget, title) in enumerate(tab_dict.items(), start=1):
318
+ tabs.insertTab(idx, widget, title)
319
+ if widget.findChild(MatplotlibSpiderWidget):
320
+ self.chart_tabs[title] = widget # Store for later reference
321
+ widget.currentChanged.connect(self.adjust_chart_tab_indices)
322
+
323
+ if set_current:
324
+ tabs.setCurrentIndex(1)
325
+
326
+ def adjust_chart_tab_indices(self, int):
327
+ """
328
+ Adjust the indices of chart tabs when one is changed.
329
+ """
330
+ for chart_tab in self.chart_tabs.values():
331
+ if isinstance(chart_tab, QTabWidget):
332
+ chart_tab.setCurrentIndex(int)
333
+
334
+ @staticmethod
335
+ def create_table_widget(
336
+ headers: List[str], rows: List[Tuple[List[str], Optional[QColor]]]
337
+ ) -> CopyableTableWidget:
338
+ """
339
+ Build a CopyableTableWidget with the given headers and rows.
340
+
341
+ Parameters
342
+ ----------
343
+ headers : list of str
344
+ Column header names.
345
+ rows : list of (row_data, row_color)
346
+ - row_data: list of str for each cell.
347
+ - row_color: QColor or None; if provided, apply to the last three columns.
348
+
349
+ Returns
350
+ -------
351
+ CopyableTableWidget
352
+ The configured and populated table widget.
353
+ """
354
+ table = CopyableTableWidget()
355
+ table.setSortingEnabled(True)
356
+ table.setColumnCount(len(headers))
357
+ table.setHorizontalHeaderLabels(headers)
358
+ table.setRowCount(len(rows))
359
+
360
+ for r, (row_data, row_color) in enumerate(rows):
361
+ for c, cell_text in enumerate(row_data):
362
+ try:
363
+ float(cell_text)
364
+ item: QTableWidgetItem = NumericSortTableWidgetItem(cell_text)
365
+ except ValueError:
366
+ item = QTableWidgetItem(cell_text)
367
+
368
+ if row_color is not None and c >= len(row_data) - 3:
369
+ item.setForeground(QBrush(row_color))
370
+ font = item.font()
371
+ font.setBold(True)
372
+ item.setFont(font)
373
+
374
+ table.setItem(r, c, item)
375
+
376
+ table.resizeColumnsToContents()
377
+ return table
378
+
379
+ def create_spider_plot_from_qwk(
380
+ self, delta_kappas: Any, test_cols: List[str], plot_config: Optional[Dict] = None
381
+ ) -> QTabWidget:
382
+ """
383
+ Build spider-chart tabs for QWK and return a QTabWidget containing them.
384
+
385
+ Parameters
386
+ ----------
387
+ delta_kappas : Any
388
+ The computed delta kappa values from the controller.
389
+ test_cols : list of str
390
+ Names of test columns to include in the plot.
391
+ plot_config : dict, optional
392
+ Configuration dictionary for spider-plot styling.
393
+
394
+ Returns
395
+ -------
396
+ QTabWidget
397
+ A tab widget containing one spider-chart per model.
398
+ """
399
+ plot_data_list = create_spider_plot_data_qwk(
400
+ delta_kappas, test_cols, plot_config=plot_config
401
+ )
402
+ return display_spider_charts_in_tabs(
403
+ plot_data_list, show_toolbar=self._show_mpl_toolbar
404
+ )
405
+
406
+ def create_spider_plot_from_eod_aaod(
407
+ self,
408
+ eod_aaod: Any,
409
+ test_cols: List[str],
410
+ plot_config: Optional[Dict] = None,
411
+ *,
412
+ metrics: Tuple[str, str] = ("eod", "aaod"),
413
+ ) -> List[QTabWidget]:
414
+ """
415
+ Build one or more spider-chart tabs for EOD/AAOD metrics.
416
+
417
+ Parameters
418
+ ----------
419
+ eod_aaod : Any
420
+ The computed EOD/AAOD values from the controller.
421
+ test_cols : list of str
422
+ Names of test columns to include in the plot.
423
+ plot_config : dict, optional
424
+ Configuration dictionary for spider-plot styling.
425
+ metrics : tuple of str, optional
426
+ Two metric names to plot (default ("eod", "aaod")).
427
+
428
+ Returns
429
+ -------
430
+ list of QTabWidget
431
+ A list of tab widgets, one for each metric group.
432
+ """
433
+ plot_data_dict, global_min, global_max = generate_plot_data_eod_aaod(
434
+ eod_aaod, test_cols, metrics=metrics
435
+ )
436
+ base_data = SpiderPlotData(
437
+ ylim_max=global_max, ylim_min=global_min, plot_config=plot_config
438
+ )
439
+ plot_data_list = create_spider_plot_data_eod_aaod(
440
+ plot_data_dict, test_cols, metrics, base_data
441
+ )
442
+
443
+ chart_tabs: List[QTabWidget] = []
444
+ grouped: Dict[str, List[SpiderPlotData]] = {}
445
+ for pdata in plot_data_list:
446
+ grouped.setdefault(pdata.metric, []).append(pdata)
447
+
448
+ for metric_name, data_list in grouped.items():
449
+ tab_widget = display_spider_charts_in_tabs(
450
+ data_list, show_toolbar=self._show_mpl_toolbar
451
+ )
452
+ tab_widget.setObjectName(f"{metric_name.upper()}_Spider_Charts")
453
+ chart_tabs.append(tab_widget)
454
+
455
+ return chart_tabs
456
+
457
+ def update_qwk_tables(self, result: Tuple[Any, Dict[str, str]]) -> None:
458
+ """
459
+ Build QWK result tables and spider-chart tab when the worker finishes.
460
+
461
+ Parameters
462
+ ----------
463
+ result : tuple
464
+ ( (all_rows, filtered_rows, kappas_rows, plot_args), reference_groups ).
465
+ """
466
+ (all_rows, filtered_rows, kappas_rows, plot_args), reference_groups = result
467
+
468
+ # Table of all delta-kappa values
469
+ headers_delta = [
470
+ "Model",
471
+ "Category",
472
+ "Reference",
473
+ "Group",
474
+ "Δκ",
475
+ "Lower CI",
476
+ "Upper CI",
477
+ ]
478
+ delta_table_data = _add_ref_group(all_rows, reference_groups)
479
+ table_all = self.create_table_widget(headers_delta, delta_table_data)
480
+
481
+ # Table of filtered delta-kappa values
482
+ filtered_table_data = _add_ref_group(filtered_rows, reference_groups)
483
+ table_filtered = self.create_table_widget(headers_delta, filtered_table_data)
484
+
485
+ # Table of overall kappa metrics
486
+ headers_kappas = ["Model", "Kappa (κ)", "Lower CI", "Upper CI"]
487
+ table_kappas = self.create_table_widget(headers_kappas, kappas_rows)
488
+
489
+ # Spider-chart tab for QWK
490
+ charts_tab = self.create_spider_plot_from_qwk(*plot_args)
491
+
492
+ tabs_dict: Dict[QWidget, str] = {
493
+ table_kappas: "QWκ (95% CI)",
494
+ table_all: "ΔQWκ (95% CI)",
495
+ table_filtered: "Filtered ΔQWκ (95% CI Excludes Zero)",
496
+ charts_tab: "ΔQWκ Spider Charts",
497
+ }
498
+ self.update_tabs(tabs_dict)
499
+
500
+ def update_eod_aaod_tables(self, result: Tuple[Any, Dict[str, str]]) -> None:
501
+ """
502
+ Build EOD/AAOD result tables and spider-chart tabs when the worker finishes.
503
+
504
+ Parameters
505
+ ----------
506
+ result : tuple
507
+ ( (all_eod_rows, all_aaod_rows, filtered_rows, plot_args), reference_groups ).
508
+ """
509
+ (all_eod_rows, all_aaod_rows, filtered_rows, plot_args), reference_groups = result
510
+
511
+ # Table of all EOD values
512
+ headers = [
513
+ "Model",
514
+ "Category",
515
+ "Reference",
516
+ "Group",
517
+ "Median",
518
+ "Lower CI",
519
+ "Upper CI",
520
+ ]
521
+ eod_table_data = _add_ref_group(all_eod_rows, reference_groups)
522
+ table_all_eod = self.create_table_widget(headers, eod_table_data)
523
+
524
+ # Table of all AAOD values
525
+ aaod_table_data = _add_ref_group(all_aaod_rows, reference_groups)
526
+ table_all_aaod = self.create_table_widget(headers, aaod_table_data)
527
+
528
+ # Filtered table with extra "Metric" column at index 4
529
+ filt_headers = headers.copy()
530
+ filt_headers.insert(4, "Metric")
531
+ filtered_table_data = _add_ref_group(filtered_rows, reference_groups)
532
+ table_filtered = self.create_table_widget(filt_headers, filtered_table_data)
533
+
534
+ # Spider-chart tabs for EOD/AAOD
535
+ chart_tabs = self.create_spider_plot_from_eod_aaod(*plot_args)
536
+
537
+ tabs_dict: Dict[QWidget, str] = {
538
+ table_all_eod: "All EOD Values",
539
+ table_all_aaod: "All AAOD Values",
540
+ table_filtered: r"EOD/AAOD Filtered (values outside [-0.1, 0.1])",
541
+ }
542
+ for ct in chart_tabs:
543
+ tabs_dict[ct] = ct.objectName()
544
+
545
+ self.update_tabs(tabs_dict)