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.
- MIDRC_MELODY/__init__.py +0 -0
- MIDRC_MELODY/__main__.py +4 -0
- MIDRC_MELODY/common/__init__.py +0 -0
- MIDRC_MELODY/common/data_loading.py +199 -0
- MIDRC_MELODY/common/data_preprocessing.py +134 -0
- MIDRC_MELODY/common/edit_config.py +156 -0
- MIDRC_MELODY/common/eod_aaod_metrics.py +292 -0
- MIDRC_MELODY/common/generate_eod_aaod_spiders.py +69 -0
- MIDRC_MELODY/common/generate_qwk_spiders.py +56 -0
- MIDRC_MELODY/common/matplotlib_spider.py +425 -0
- MIDRC_MELODY/common/plot_tools.py +132 -0
- MIDRC_MELODY/common/plotly_spider.py +217 -0
- MIDRC_MELODY/common/qwk_metrics.py +244 -0
- MIDRC_MELODY/common/table_tools.py +230 -0
- MIDRC_MELODY/gui/__init__.py +0 -0
- MIDRC_MELODY/gui/config_editor.py +200 -0
- MIDRC_MELODY/gui/data_loading.py +157 -0
- MIDRC_MELODY/gui/main_controller.py +154 -0
- MIDRC_MELODY/gui/main_window.py +545 -0
- MIDRC_MELODY/gui/matplotlib_spider_widget.py +204 -0
- MIDRC_MELODY/gui/metrics_model.py +62 -0
- MIDRC_MELODY/gui/plotly_spider_widget.py +56 -0
- MIDRC_MELODY/gui/qchart_spider_widget.py +272 -0
- MIDRC_MELODY/gui/shared/__init__.py +0 -0
- MIDRC_MELODY/gui/shared/react/__init__.py +0 -0
- MIDRC_MELODY/gui/shared/react/copyabletableview.py +100 -0
- MIDRC_MELODY/gui/shared/react/grabbablewidget.py +406 -0
- MIDRC_MELODY/gui/tqdm_handler.py +210 -0
- MIDRC_MELODY/melody.py +102 -0
- MIDRC_MELODY/melody_gui.py +111 -0
- MIDRC_MELODY/resources/MIDRC.ico +0 -0
- midrc_melody-0.3.3.dist-info/METADATA +151 -0
- midrc_melody-0.3.3.dist-info/RECORD +37 -0
- midrc_melody-0.3.3.dist-info/WHEEL +5 -0
- midrc_melody-0.3.3.dist-info/entry_points.txt +4 -0
- midrc_melody-0.3.3.dist-info/licenses/LICENSE +201 -0
- 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)
|