diff --git a/src/midrc_react/core/jsdcontroller.py b/src/midrc_react/core/jsdcontroller.py index 649160c..6a8bf96 100644 --- a/src/midrc_react/core/jsdcontroller.py +++ b/src/midrc_react/core/jsdcontroller.py @@ -324,6 +324,9 @@ def build_date_list(df1, df2): str_col = category[:-6] num_col = data_source_1.numeric_cols[str_col]['raw column'] + # remove the 'Not Reported' data before calculation + combined_df = combined_df[pd.to_numeric(combined_df[num_col], errors='coerce').notnull()] + input_data = [float(calc_ks2_samp_by_feature(combined_df[combined_df['date'] <= date], num_col)['Dataset 0 vs Dataset 1']) for date in date_list] diff --git a/src/midrc_react/gui/pyside6/grabbablewidget.py b/src/midrc_react/gui/pyside6/grabbablewidget.py index b987486..99a82d1 100644 --- a/src/midrc_react/gui/pyside6/grabbablewidget.py +++ b/src/midrc_react/gui/pyside6/grabbablewidget.py @@ -49,6 +49,7 @@ def __init__(self, parent: QWidget = None, save_file_prefix: str = DEFAULT_SAVE_ """ self.save_dialog_open = False # Indicates if the save dialog is currently open super().__init__(parent) + self.copyable_data: str | None = None # new attribute for copyable text data self.save_file_prefix = save_file_prefix parent.setContextMenuPolicy(Qt.CustomContextMenu) parent.customContextMenuRequested.connect(self.show_context_menu) @@ -82,8 +83,13 @@ def show_context_menu(self, pos): None """ context_menu = QMenu(self.parent) - copy_action = QAction("Copy", self.parent) - save_action = QAction("Save", self.parent) + # New action: Copy Data, enabled only if copyable_data is set + copy_data_action = QAction("Copy Data", self.parent) + copy_data_action.setEnabled(bool(self.copyable_data)) + copy_data_action.triggered.connect(self.copy_data_to_clipboard) + context_menu.addAction(copy_data_action) + copy_action = QAction("Copy Image", self.parent) + save_action = QAction("Save Image", self.parent) copy_action.triggered.connect(self.copy_to_clipboard) save_action.triggered.connect(self.save_to_disk) @@ -93,7 +99,7 @@ def show_context_menu(self, pos): # Only display this action if we don't have the high-res save dialog open for this widget if not self.save_dialog_open: - save_high_res_action = QAction("Save High Resolution", self.parent) + save_high_res_action = QAction("Save High Resolution Image", self.parent) save_high_res_action.triggered.connect(self.save_high_res_to_disk) context_menu.addAction(save_high_res_action) @@ -190,6 +196,14 @@ def save_high_res_to_disk(self) -> None: self.save_dialog_open = False + def copy_data_to_clipboard(self): + """ + Copy the stored copyable_data text to the clipboard. + """ + if self.copyable_data: + clipboard = QApplication.clipboard() + clipboard.setText(self.copyable_data) + class SaveWidgetAsImageDialog(QDialog): """ @@ -366,3 +380,4 @@ def save_chart_to_disk(self): None """ self.grabbable_mixin.save_to_disk() + diff --git a/src/midrc_react/gui/pyside6/jsdview.py b/src/midrc_react/gui/pyside6/jsdview.py index 473029f..1d93e5d 100644 --- a/src/midrc_react/gui/pyside6/jsdview.py +++ b/src/midrc_react/gui/pyside6/jsdview.py @@ -289,6 +289,7 @@ def add_spider_chart_view(self) -> GrabbableChartView: self.spider_chart, save_file_prefix="MIDRC-REACT_spider_chart", ) self.spider_chart_vbox.addWidget(spider_chart_view) + self.spider_chart_view = spider_chart_view # store reference for copyable data return spider_chart_view def update_pie_chart_dock(self, sheet_dict: Dict[Any, Any]) -> None: @@ -384,6 +385,26 @@ def _create_pie_chart_series(series: QPieSeries, category: str) -> GrabbableChar chart.legend().setAlignment(Qt.AlignRight) return chart_view + def _set_spider_chart_copyable_data(self, spider_plot_values_dict: Dict[Any, Dict[str, float]]) -> None: + """ + Set the copyable data for the spider chart. + + Args: + spider_plot_values_dict (dict): Dictionary containing series values keyed by index pairs. + + Returns: + None + """ + if hasattr(self, 'spider_chart_view') and spider_plot_values_dict: + headers = sorted(next(iter(spider_plot_values_dict.values())).keys()) + formatted_text = "File 1\tFile 2\t" + "\t".join(headers) + "\n" + for series_key, series in spider_plot_values_dict.items(): + file1 = self._dataselectiongroupbox.file_comboboxes[series_key[0]].currentText() + file2 = self._dataselectiongroupbox.file_comboboxes[series_key[1]].currentText() + values = "\t".join(str(series[label]) for label in headers) + formatted_text += f"{file1}\t{file2}\t{values}\n" + self.spider_chart_view.grabbable_mixin.copyable_data = formatted_text + def update_spider_chart(self, spider_plot_values_dict: Dict[Any, Dict[str, float]]) -> bool: """ Update the spider chart with the provided plot values. @@ -397,6 +418,8 @@ def update_spider_chart(self, spider_plot_values_dict: Dict[Any, Dict[str, float if not spider_plot_values_dict: return False + self._set_spider_chart_copyable_data(spider_plot_values_dict) + self.spider_chart.removeAllSeries() for axis in self.spider_chart.axes(): self.spider_chart.removeAxis(axis) @@ -570,7 +593,7 @@ def update_jsd_timeline_plot(self, jsd_model) -> bool: for c, column_info in enumerate(jsd_model.column_infos): col: int = c * 2 series: QLineSeries = QLineSeries() - series.setName(f"{column_info['file1']} vs {column_info['file2']} {column_info['category']} JSD") + series.setName(f"{column_info['file1']} vs {column_info['file2']} {column_info['category']} Distance") row_count: int = jsd_model.rowCount(jsd_model.createIndex(0, col)) for i in range(row_count): time_point: Optional[float] = convert_date_to_milliseconds(jsd_model.input_data[col][i]) @@ -712,3 +735,6 @@ def clear_layout(layout: Optional[QLayout]) -> bool: layout.removeItem(child) return True + + + diff --git a/src/midrc_react/plugins/missing_date_plugin.py b/src/midrc_react/plugins/missing_date_plugin.py new file mode 100644 index 0000000..c05bb22 --- /dev/null +++ b/src/midrc_react/plugins/missing_date_plugin.py @@ -0,0 +1,33 @@ +# Copyright (c) 2025 Medical Imaging and Data Resource Center (MIDRC). +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +""" +This module contains functions for processing TSV files downloaded from the data.MIDRC.org website. +""" + +from datetime import datetime +import re + +import pandas as pd + +def process_dataframe(df): + """Applies both transformations on a pandas DataFrame.""" + df['date'] = pd.to_datetime(datetime.today().date()) + return df + +def preprocess_data(df): + """Preprocesses a pandas DataFrame.""" + return process_dataframe(df) +