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,292 @@
|
|
|
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
|
+
""" EOD and AAOD metric calculation and plotting functions. """
|
|
17
|
+
|
|
18
|
+
from typing import Any, Dict, List, Optional, Tuple, Union
|
|
19
|
+
|
|
20
|
+
from joblib import delayed, Parallel
|
|
21
|
+
import matplotlib.pyplot as plt
|
|
22
|
+
import numpy as np
|
|
23
|
+
import pandas as pd
|
|
24
|
+
from sklearn.utils import resample
|
|
25
|
+
from tqdm import tqdm
|
|
26
|
+
from tqdm_joblib import tqdm_joblib
|
|
27
|
+
|
|
28
|
+
from MIDRC_MELODY.common.data_loading import check_required_columns, TestAndDemographicData
|
|
29
|
+
from MIDRC_MELODY.common.plot_tools import SpiderPlotData
|
|
30
|
+
from MIDRC_MELODY.common.matplotlib_spider import plot_spider_chart, display_figures_grid
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def binarize_scores(df: pd.DataFrame, truth_col: str, ai_cols: Union[List[str], str], threshold: int = 4
|
|
34
|
+
) -> pd.DataFrame:
|
|
35
|
+
"""
|
|
36
|
+
Binarize scores based on a threshold for truth and AI columns.
|
|
37
|
+
Converts values greater than or equal to threshold to 1, else 0.
|
|
38
|
+
|
|
39
|
+
:arg df: DataFrame containing truth and test columns.
|
|
40
|
+
:arg truth_col: Name of the truth column.
|
|
41
|
+
:arg ai_cols: Name of the test column or a list of test columns.
|
|
42
|
+
:arg threshold: Threshold value for binarization.
|
|
43
|
+
|
|
44
|
+
:returns: DataFrame with binarized columns.
|
|
45
|
+
"""
|
|
46
|
+
if not isinstance(ai_cols, list):
|
|
47
|
+
ai_cols = [ai_cols]
|
|
48
|
+
cols = [truth_col] + ai_cols
|
|
49
|
+
check_required_columns(df, cols)
|
|
50
|
+
df[cols] = (df[cols] >= threshold).astype(int)
|
|
51
|
+
return df
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def resample_by_column(df: pd.DataFrame, col: Union[str, List[str]], seed: int) -> pd.DataFrame:
|
|
55
|
+
"""
|
|
56
|
+
Resample each group in a DataFrame by the specified column
|
|
57
|
+
using the same seed across groups.
|
|
58
|
+
|
|
59
|
+
:arg df: DataFrame to resample.
|
|
60
|
+
:arg col: Column to group by.
|
|
61
|
+
:arg seed: Seed for reproducibility across groups.
|
|
62
|
+
|
|
63
|
+
:returns: Resampled DataFrame.
|
|
64
|
+
"""
|
|
65
|
+
sampled_groups = [
|
|
66
|
+
resample(group_df, replace=True, n_samples=len(group_df), random_state=seed)
|
|
67
|
+
for _, group_df in df.groupby(col)
|
|
68
|
+
]
|
|
69
|
+
return pd.concat(sampled_groups)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def compute_bootstrap_eod_aaod(
|
|
73
|
+
df: pd.DataFrame,
|
|
74
|
+
category: str,
|
|
75
|
+
ref_group: Any,
|
|
76
|
+
group_value: Any,
|
|
77
|
+
truth_col: str,
|
|
78
|
+
ai_columns: List[str],
|
|
79
|
+
seed: int
|
|
80
|
+
) -> Dict[str, Tuple[float, float]]:
|
|
81
|
+
"""
|
|
82
|
+
Compute bootstrap estimates for EOD and AAOD metrics.
|
|
83
|
+
|
|
84
|
+
:arg df: DataFrame containing truth and test columns.
|
|
85
|
+
:arg category: Column to group by.
|
|
86
|
+
:arg ref_group: Reference group value.
|
|
87
|
+
:arg group_value: Group value to compare against reference.
|
|
88
|
+
:arg truth_col: Name of the truth column.
|
|
89
|
+
:arg ai_columns: List of test columns.
|
|
90
|
+
:arg seed: Seed for reproducibility.
|
|
91
|
+
|
|
92
|
+
:returns: Dictionary of EOD and AAOD values for each model.
|
|
93
|
+
"""
|
|
94
|
+
sample_df = resample_by_column(df, [category, truth_col], seed)
|
|
95
|
+
ref_df = sample_df[sample_df[category] == ref_group]
|
|
96
|
+
group_df = sample_df[sample_df[category] == group_value]
|
|
97
|
+
|
|
98
|
+
# Precompute truth masks for both reference and group DataFrames
|
|
99
|
+
ref_truth_pos = (ref_df[truth_col] == 1)
|
|
100
|
+
ref_truth_neg = ~ref_truth_pos
|
|
101
|
+
group_truth_pos = (group_df[truth_col] == 1)
|
|
102
|
+
group_truth_neg = ~group_truth_pos
|
|
103
|
+
|
|
104
|
+
results: Dict[str, Tuple[float, float]] = {}
|
|
105
|
+
for model in ai_columns:
|
|
106
|
+
ref_pred = (ref_df[model] == 1)
|
|
107
|
+
group_pred = (group_df[model] == 1)
|
|
108
|
+
|
|
109
|
+
tpr_ref = ref_pred[ref_truth_pos].sum() / ref_truth_pos.sum() if ref_truth_pos.sum() else np.nan
|
|
110
|
+
fpr_ref = ref_pred[ref_truth_neg].sum() / ref_truth_neg.sum() if ref_truth_neg.sum() else np.nan
|
|
111
|
+
tpr_group = group_pred[group_truth_pos].sum() / group_truth_pos.sum() if group_truth_pos.sum() else np.nan
|
|
112
|
+
fpr_group = group_pred[group_truth_neg].sum() / group_truth_neg.sum() if group_truth_neg.sum() else np.nan
|
|
113
|
+
|
|
114
|
+
eod = tpr_group - tpr_ref
|
|
115
|
+
aaod = 0.5 * (abs(fpr_group - fpr_ref) + abs(tpr_group - tpr_ref))
|
|
116
|
+
results[model] = (eod, aaod)
|
|
117
|
+
|
|
118
|
+
return results
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def calculate_eod_aaod(
|
|
122
|
+
test_data: TestAndDemographicData
|
|
123
|
+
) -> Dict[str, Dict[str, Dict[Any, Dict[str, Any]]]]:
|
|
124
|
+
"""
|
|
125
|
+
Calculate EOD and AAOD metrics with bootstrap iterations for multiple categories.
|
|
126
|
+
|
|
127
|
+
:arg test_data: Test and demographic data.
|
|
128
|
+
|
|
129
|
+
:returns: Dictionary of EOD and AAOD values for each model.
|
|
130
|
+
"""
|
|
131
|
+
ai_columns = test_data.test_cols
|
|
132
|
+
eod_aaod: Dict[str, Dict[str, Dict[Any, Dict[str, Any]]]] = {
|
|
133
|
+
category: {model: {} for model in ai_columns} for category in test_data.categories
|
|
134
|
+
}
|
|
135
|
+
rng = np.random.default_rng(test_data.base_seed)
|
|
136
|
+
|
|
137
|
+
for category in tqdm(test_data.categories, desc='Categories', position=0):
|
|
138
|
+
if category not in test_data.valid_groups:
|
|
139
|
+
continue
|
|
140
|
+
|
|
141
|
+
ref_group = test_data.reference_groups[category]
|
|
142
|
+
unique_values = test_data.matched_df[category].unique()
|
|
143
|
+
|
|
144
|
+
for group_value in tqdm(unique_values, desc=f"Category \'{category}\' Groups", leave=False, position=1):
|
|
145
|
+
if group_value == ref_group or group_value not in test_data.valid_groups[category]:
|
|
146
|
+
continue
|
|
147
|
+
|
|
148
|
+
eod_samples = {model: [] for model in ai_columns}
|
|
149
|
+
aaod_samples = {model: [] for model in ai_columns}
|
|
150
|
+
|
|
151
|
+
# Preassign seeds for each bootstrap iteration.
|
|
152
|
+
seeds = rng.integers(0, 1_000_000, size=test_data.n_iter)
|
|
153
|
+
|
|
154
|
+
with tqdm_joblib(total=test_data.n_iter, desc=f"Bootstrapping \'{group_value}\' Group", leave=False):
|
|
155
|
+
bootstrap_results = Parallel(n_jobs=-1)(
|
|
156
|
+
delayed(compute_bootstrap_eod_aaod)(
|
|
157
|
+
test_data.matched_df, category, ref_group, group_value, test_data.truth_col, ai_columns, seed
|
|
158
|
+
) for seed in seeds
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
for result in bootstrap_results:
|
|
162
|
+
for model in ai_columns:
|
|
163
|
+
eod_samples[model].append(result[model][0])
|
|
164
|
+
aaod_samples[model].append(result[model][1])
|
|
165
|
+
|
|
166
|
+
for model in ai_columns:
|
|
167
|
+
eod_median = np.median(eod_samples[model])
|
|
168
|
+
aaod_median = np.median(aaod_samples[model])
|
|
169
|
+
eod_ci = np.percentile(eod_samples[model], [2.5, 97.5])
|
|
170
|
+
aaod_ci = np.percentile(aaod_samples[model], [2.5, 97.5])
|
|
171
|
+
eod_aaod[category][model][group_value] = {
|
|
172
|
+
'eod': (eod_median, eod_ci),
|
|
173
|
+
'aaod': (aaod_median, aaod_ci)
|
|
174
|
+
}
|
|
175
|
+
return eod_aaod
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def extract_plot_data_eod_aaod(
|
|
179
|
+
eod_aaod: Dict[str, Dict[str, Dict[Any, Dict[str, Any]]]],
|
|
180
|
+
model: str,
|
|
181
|
+
metric: str = 'eod'
|
|
182
|
+
) -> Tuple[List[str], List[float], List[float], List[float]]:
|
|
183
|
+
"""
|
|
184
|
+
Extract groups, metric values and confidence intervals for plotting.
|
|
185
|
+
|
|
186
|
+
:arg eod_aaod: Dictionary of EOD and AAOD values for each model.
|
|
187
|
+
:arg model: Name of the model to extract data for.
|
|
188
|
+
:arg metric: Metric to extract data for (EOD or AAOD).
|
|
189
|
+
|
|
190
|
+
:returns: Tuple of groups, values, lower bounds and upper bounds.
|
|
191
|
+
"""
|
|
192
|
+
groups: List[str] = []
|
|
193
|
+
values: List[float] = []
|
|
194
|
+
lower_bounds: List[float] = []
|
|
195
|
+
upper_bounds: List[float] = []
|
|
196
|
+
|
|
197
|
+
for category, model_data in eod_aaod.items():
|
|
198
|
+
if model in model_data:
|
|
199
|
+
for group, metric_list in model_data[model].items():
|
|
200
|
+
groups.append(f"{category}: {group}")
|
|
201
|
+
value, (lower, upper) = metric_list[metric]
|
|
202
|
+
values.append(value)
|
|
203
|
+
lower_bounds.append(lower)
|
|
204
|
+
upper_bounds.append(upper)
|
|
205
|
+
|
|
206
|
+
return groups, values, lower_bounds, upper_bounds
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def generate_plot_data_eod_aaod(
|
|
210
|
+
eod_aaod: Dict[str, Dict[str, Dict[Any, Dict[str, Any]]]],
|
|
211
|
+
test_cols: List[str],
|
|
212
|
+
metrics: List[str] = ('eod', 'aaod')
|
|
213
|
+
) -> Tuple[Dict[str, Dict[str, Tuple[List[str], List[float], List[float], List[float]]]],
|
|
214
|
+
Dict[str, float], Dict[str, float]]:
|
|
215
|
+
"""
|
|
216
|
+
Generate plot data for each metric and compute global axis limits.
|
|
217
|
+
|
|
218
|
+
:arg eod_aaod: Dictionary of EOD and AAOD values for each model.
|
|
219
|
+
:arg test_cols: List of test columns.
|
|
220
|
+
:arg metrics: List of metrics to plot.
|
|
221
|
+
|
|
222
|
+
:returns: Tuple of plot data dictionary, global minimum and maximum values.
|
|
223
|
+
"""
|
|
224
|
+
plot_data_dict: Dict[str, Dict[str, Tuple[List[str], List[float], List[float], List[float]]]] = {}
|
|
225
|
+
global_min = {}
|
|
226
|
+
global_max = {}
|
|
227
|
+
|
|
228
|
+
for metric in metrics:
|
|
229
|
+
all_values: List[float] = []
|
|
230
|
+
plot_data_dict[metric] = {}
|
|
231
|
+
for model in test_cols:
|
|
232
|
+
groups, values, lower, upper = extract_plot_data_eod_aaod(eod_aaod, model, metric)
|
|
233
|
+
plot_data_dict[metric][model] = (groups, values, lower, upper)
|
|
234
|
+
all_values.extend(lower + upper)
|
|
235
|
+
|
|
236
|
+
global_min[metric], global_max[metric] = min(all_values) - 0.05, max(all_values) + 0.05
|
|
237
|
+
|
|
238
|
+
return plot_data_dict, global_min, global_max
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
def create_spider_plot_data_eod_aaod(
|
|
242
|
+
plot_data_dict: Dict[str, Dict[str, Tuple[List[str], List[float], List[float], List[float]]]],
|
|
243
|
+
test_cols: List[str],
|
|
244
|
+
metrics: List[str] = ('eod', 'aaod'),
|
|
245
|
+
base_plot_data: Optional[SpiderPlotData] = None,
|
|
246
|
+
) -> List[SpiderPlotData]:
|
|
247
|
+
plot_data_list: List[SpiderPlotData] = []
|
|
248
|
+
if base_plot_data is None:
|
|
249
|
+
base_plot_data = SpiderPlotData()
|
|
250
|
+
for metric in metrics:
|
|
251
|
+
for model in test_cols:
|
|
252
|
+
# Create a new copy based on the base instance
|
|
253
|
+
plot_data = SpiderPlotData(**base_plot_data.__dict__)
|
|
254
|
+
plot_data.metric = metric
|
|
255
|
+
plot_data.model_name = model
|
|
256
|
+
plot_data.groups, plot_data.values, plot_data.lower_bounds, plot_data.upper_bounds = \
|
|
257
|
+
plot_data_dict[metric][model]
|
|
258
|
+
plot_data_list.append(plot_data)
|
|
259
|
+
return plot_data_list
|
|
260
|
+
|
|
261
|
+
def plot_data_eod_aaod(
|
|
262
|
+
plot_data_dict: Dict[str, Dict[str, Tuple[List[str], List[float], List[float], List[float]]]],
|
|
263
|
+
test_cols: List[str],
|
|
264
|
+
metrics: List[str] = ('eod', 'aaod'),
|
|
265
|
+
base_plot_data: Optional[SpiderPlotData] = None,
|
|
266
|
+
) -> Dict[str, List[plt.Figure]]:
|
|
267
|
+
"""
|
|
268
|
+
Plot EOD and AAOD spider charts for each model.
|
|
269
|
+
|
|
270
|
+
:arg plot_data_dict: Dictionary of plot data for each metric.
|
|
271
|
+
:arg test_cols: List of test columns.
|
|
272
|
+
:arg metrics: List of metrics to plot.
|
|
273
|
+
:arg base_plot_data: Base SpiderPlotData instance for plot configuration.
|
|
274
|
+
|
|
275
|
+
:returns: Dictionary of generated figures for each metric.
|
|
276
|
+
"""
|
|
277
|
+
plot_data_list = create_spider_plot_data_eod_aaod(plot_data_dict, test_cols, metrics, base_plot_data)
|
|
278
|
+
figures_dict: Dict[str, List[Any]] = {metric: [] for metric in metrics}
|
|
279
|
+
|
|
280
|
+
for plot_data in plot_data_list:
|
|
281
|
+
fig = plot_spider_chart(plot_data)
|
|
282
|
+
figures_dict[plot_data.metric].append(fig)
|
|
283
|
+
|
|
284
|
+
grid_figs = []
|
|
285
|
+
for figures in figures_dict.values():
|
|
286
|
+
grid_fig = display_figures_grid(figures)
|
|
287
|
+
grid_figs.append(grid_fig)
|
|
288
|
+
|
|
289
|
+
# all_figs = [fig for figs in figures_dict.values() for fig in figs] + [g for g in grid_figs if g is not None]
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
return figures_dict
|
|
@@ -0,0 +1,69 @@
|
|
|
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
|
+
"""This script generates EOD and AAOD spider plots for multiple models across different categories."""
|
|
17
|
+
from dataclasses import replace
|
|
18
|
+
|
|
19
|
+
import matplotlib.pyplot as plt
|
|
20
|
+
from pylab import get_current_fig_manager
|
|
21
|
+
import yaml
|
|
22
|
+
|
|
23
|
+
from MIDRC_MELODY.common.data_loading import build_test_and_demographic_data, save_pickled_data
|
|
24
|
+
from MIDRC_MELODY.common.eod_aaod_metrics import (binarize_scores, calculate_eod_aaod, generate_plot_data_eod_aaod,
|
|
25
|
+
plot_data_eod_aaod)
|
|
26
|
+
from MIDRC_MELODY.common.plot_tools import SpiderPlotData
|
|
27
|
+
from MIDRC_MELODY.common.table_tools import print_table_of_nonzero_eod_aaod
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def generate_eod_aaod_spiders(cfg_path: str = "config.yaml"):
|
|
31
|
+
# Load configuration
|
|
32
|
+
with open(cfg_path, 'r', encoding='utf-8') as stream:
|
|
33
|
+
config = yaml.load(stream, Loader=yaml.CLoader)
|
|
34
|
+
|
|
35
|
+
# Load data
|
|
36
|
+
t_data = build_test_and_demographic_data(config)
|
|
37
|
+
|
|
38
|
+
# Binarize scores
|
|
39
|
+
threshold = config['binary threshold']
|
|
40
|
+
matched_df = binarize_scores(t_data.matched_df, t_data.truth_col, t_data.test_cols, threshold=threshold)
|
|
41
|
+
test_data = replace(t_data, matched_df=matched_df)
|
|
42
|
+
|
|
43
|
+
# Calculate EOD and AAOD
|
|
44
|
+
eod_aaod = calculate_eod_aaod(test_data)
|
|
45
|
+
|
|
46
|
+
# Print tables for EOD and AAOD using median values
|
|
47
|
+
print_table_of_nonzero_eod_aaod(eod_aaod, tablefmt="rounded_outline")
|
|
48
|
+
|
|
49
|
+
# Generate and save plots
|
|
50
|
+
metrics = ['eod', 'aaod']
|
|
51
|
+
plot_data_dict, global_min, global_max = generate_plot_data_eod_aaod(eod_aaod, test_data.test_cols, metrics=metrics)
|
|
52
|
+
|
|
53
|
+
# Save the EOD and AAOD data
|
|
54
|
+
for metric in metrics:
|
|
55
|
+
save_pickled_data(config['output'], metric, plot_data_dict[metric])
|
|
56
|
+
|
|
57
|
+
base_plot_data = SpiderPlotData(ylim_min=global_min, ylim_max=global_max, plot_config=config['plot'])
|
|
58
|
+
figures_dict = plot_data_eod_aaod(plot_data_dict, # noqa: F841
|
|
59
|
+
test_data.test_cols,
|
|
60
|
+
metrics=metrics,
|
|
61
|
+
base_plot_data=base_plot_data,
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
print("\nClose all figures to continue...", flush=True)
|
|
65
|
+
plt.show()
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
if __name__ == '__main__':
|
|
69
|
+
generate_eod_aaod_spiders()
|
|
@@ -0,0 +1,56 @@
|
|
|
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
|
+
"""This script generates QWK spider plots for multiple models across different categories."""
|
|
17
|
+
|
|
18
|
+
import matplotlib.pyplot as plt
|
|
19
|
+
import yaml
|
|
20
|
+
|
|
21
|
+
from MIDRC_MELODY.common.data_loading import build_test_and_demographic_data, save_pickled_data
|
|
22
|
+
from MIDRC_MELODY.common.qwk_metrics import (calculate_delta_kappa, calculate_kappas_and_intervals,
|
|
23
|
+
generate_plots_from_delta_kappas)
|
|
24
|
+
from MIDRC_MELODY.common.table_tools import print_table_of_nonzero_deltas
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def generate_qwk_spiders(cfg_path: str = "config.yaml"):
|
|
28
|
+
# Load configuration
|
|
29
|
+
with open(cfg_path, 'r', encoding='utf-8') as stream:
|
|
30
|
+
config = yaml.load(stream, Loader=yaml.CLoader)
|
|
31
|
+
|
|
32
|
+
# Load data
|
|
33
|
+
test_data = build_test_and_demographic_data(config)
|
|
34
|
+
|
|
35
|
+
# Calculate Kappas and intervals, prints the table of Kappas and intervals
|
|
36
|
+
kappas, intervals = calculate_kappas_and_intervals(test_data)
|
|
37
|
+
|
|
38
|
+
# Bootstrap delta QWKs
|
|
39
|
+
print("Bootstrapping ∆κ, this may take a while", flush=True)
|
|
40
|
+
delta_kappas = calculate_delta_kappa(test_data)
|
|
41
|
+
|
|
42
|
+
# Print the table of non-zero delta Kappas
|
|
43
|
+
print_table_of_nonzero_deltas(delta_kappas, tablefmt="rounded_outline")
|
|
44
|
+
|
|
45
|
+
# Save the delta Kappas
|
|
46
|
+
save_pickled_data(config['output'], "QWK", delta_kappas)
|
|
47
|
+
|
|
48
|
+
# Generate and save plots
|
|
49
|
+
generate_plots_from_delta_kappas(delta_kappas, test_data.test_cols, plot_config=config['plot'])
|
|
50
|
+
|
|
51
|
+
print("\nClose all figures to continue...", flush=True)
|
|
52
|
+
plt.show()
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
if __name__ == '__main__':
|
|
56
|
+
generate_qwk_spiders()
|