From 35f7c0064c6f17facdb5d1fe7633e843e646fc36 Mon Sep 17 00:00:00 2001 From: Richard Gerum <14153051+rgerum@users.noreply.github.com> Date: Fri, 29 Dec 2023 13:22:03 -0500 Subject: [PATCH] spheroid use also common analysis widget --- saenopy/gui/common/plot_window.py | 27 +- saenopy/gui/gui_master.py | 2 +- saenopy/gui/spheroid/analyze/plot_window.py | 511 ++-------------- .../gui/spheroid/gui_deformation_spheroid.py | 6 +- saenopy/gui/spheroid/modules/result.py | 6 +- saenopy/gui/tfm2d/analyze/plot_window.py | 557 +----------------- saenopy/gui/tfm2d/modules/result.py | 4 +- 7 files changed, 70 insertions(+), 1043 deletions(-) diff --git a/saenopy/gui/common/plot_window.py b/saenopy/gui/common/plot_window.py index 2a01aa88..8708222c 100644 --- a/saenopy/gui/common/plot_window.py +++ b/saenopy/gui/common/plot_window.py @@ -74,7 +74,7 @@ def __init__(self, parent=None): super().__init__(parent) # QSettings - self.settings = QtCore.QSettings("Saenopy", "Saenopy") + self.settings = QtCore.QSettings("Saenopy", self.settings_key) self.setMinimumWidth(800) self.setMinimumHeight(400) @@ -221,7 +221,8 @@ def check_results_with_time(self): print("time_values", time_values) if time_values is False: self.barplot() - self.agg.setEnabled(time_values) + if getattr(self, "agg", None) is not None: + self.agg.setEnabled(time_values) self.button_run.setEnabled(time_values) self.button_run2.setEnabled(time_values) @@ -288,13 +289,16 @@ def barplot(self): # get all the data as a pandas dataframe res = self.getAllCurrentPandasData() - # limit the dataframe to the comparison time - res0 = res.groupby("filename").agg("max") - del res["group"] - res = res.groupby("filename").agg(self.agg.value()) - res["group"] = res0["group"] - #index = self.get_comparison_index() - #res = res[res.index == index] + if getattr(self, "agg", None) is not None: + # limit the dataframe to the comparison time + res0 = res.groupby("filename").agg("max") + del res["group"] + res = res.groupby("filename").agg(self.agg.value()) + res["group"] = res0["group"] + else: + # limit the dataframe to the comparison time + index = self.get_comparison_index() + res = res[res.index == index] code_data = [res, ["group", mu_name]] @@ -340,6 +344,11 @@ def plot_groups(self): code_data = [res, ["t", "group", mu_name, "filename"]] + # add a vertical line where the comparison time is + if getattr(self, "input_tbar", None) and self.input_tbar.value() is not None: + comp_h = self.get_comparison_index() * (res.iloc[1]["t"] - res.iloc[0]["t"]) + plt.axvline(comp_h, color="k") + color_dict = {d[0]: d[3] for d in self.data_folders} def plot(res, mu_name, y_label, color_dict2): diff --git a/saenopy/gui/gui_master.py b/saenopy/gui/gui_master.py index 383bcabe..d83e88af 100644 --- a/saenopy/gui/gui_master.py +++ b/saenopy/gui/gui_master.py @@ -137,7 +137,7 @@ def timer(): self.orientation.tabs.setCurrentIndex(1) self.orientation.plotting_window.load(file) continue - except: + except (IndexError, KeyError): continue if file.endswith(".py"): self.setTab(6) diff --git a/saenopy/gui/spheroid/analyze/plot_window.py b/saenopy/gui/spheroid/analyze/plot_window.py index f1b35803..9d3751f0 100644 --- a/saenopy/gui/spheroid/analyze/plot_window.py +++ b/saenopy/gui/spheroid/analyze/plot_window.py @@ -1,273 +1,47 @@ -import sys - -# Setting the Qt bindings for QtPy -import os - -import pandas as pd -import qtawesome as qta - -os.environ["QT_API"] = "pyqt5" - -from qtpy import QtCore, QtWidgets, QtGui - import numpy as np -from natsort import natsorted - - -from saenopy.gui.common import QtShortCuts, QExtendedGraphicsView -from qimage2ndarray import array2qimage -import matplotlib.pyplot as plt -import imageio -import threading -import glob - - -from matplotlib.figure import Figure -import jointforces as jf -import urllib -from pathlib import Path - -import ctypes - -from saenopy.gui.spheroid.modules.ListWidget import ListWidget -from saenopy.gui.spheroid.modules.MatplotlibWidget import MatplotlibWidget, NavigationToolbar -settings = QtCore.QSettings("Saenopy", "Saenopy") - - -class PlottingWindow(QtWidgets.QWidget): - progress_signal = QtCore.Signal(int, int, int, int) - finished_signal = QtCore.Signal() - thread = None - - def __init__(self, parent=None): - super().__init__(parent) - - # QSettings - self.settings = QtCore.QSettings("Saenopy", "Saenopy") - - self.setMinimumWidth(800) - self.setMinimumHeight(400) - self.setWindowTitle("Saenopy Evaluation") - - self.images = [] - self.data_folders = [] - self.current_plot_func = lambda: None - - with QtShortCuts.QVBoxLayout(self) as main_layout0: - with QtShortCuts.QHBoxLayout() as main_layout00: - self.button_save = QtShortCuts.QPushButton(None, "save", self.save) - self.button_load = QtShortCuts.QPushButton(None, "load", self.load) - main_layout00.addStretch() - with QtShortCuts.QHBoxLayout() as main_layout: - with QtShortCuts.QVBoxLayout() as layout: - with QtShortCuts.QGroupBox(None, "Groups") as (_, layout2): - layout2.setContentsMargins(0, 3, 0, 1) - self.list = ListWidget(layout2, True, add_item_button="add group", color_picker=True) - self.list.setStyleSheet("QListWidget{border: none}") - self.list.itemSelectionChanged.connect(self.listSelected) - self.list.itemChanged.connect(self.replot) - self.list.itemChanged.connect(self.update_group_name) - self.list.addItemClicked.connect(self.addGroup) - - with QtShortCuts.QGroupBox(layout, "Group") as (self.box_group, layout2): - layout2.setContentsMargins(0, 3, 0, 1) - self.list2 = ListWidget(layout2, add_item_button="add files") - self.list2.setStyleSheet("QListWidget{border: none}") - self.list2.itemSelectionChanged.connect(self.run2) - self.list2.itemChanged.connect(self.replot) - self.list2.addItemClicked.connect(self.addFiles) - - self.setAcceptDrops(True) - - with QtShortCuts.QGroupBox(main_layout, "Plot Forces") as (_, layout): - self.type = QtShortCuts.QInputChoice(None, "type", "Pressure", ["Pressure", "Contractility"]) - self.type.valueChanged.connect(self.replot) - self.dt = QtShortCuts.QInputString(None, "dt", 2, unit="min", type=float) - self.dt.valueChanged.connect(self.replot) - self.input_tbar = QtShortCuts.QInputString(None, "Comparison Time", 2, type=float) - self.input_tbar_unit = QtShortCuts.QInputChoice(self.input_tbar.layout(), None, "min", ["steps", "min", "h"]) - self.input_tbar_unit.valueChanged.connect(self.replot) - self.input_tbar.valueChanged.connect(self.replot) - - self.canvas = MatplotlibWidget(self) - layout.addWidget(self.canvas) - layout.addWidget(NavigationToolbar(self.canvas, self)) - - with QtShortCuts.QHBoxLayout() as layout2: - self.button_export = QtShortCuts.QPushButton(layout2, "Export", self.export) - layout2.addStretch() - self.button_run = QtShortCuts.QPushButton(layout2, "Single Time Course", self.run2) - self.button_run2 = QtShortCuts.QPushButton(layout2, "Grouped Time Courses", self.plot_groups) - self.button_run3 = QtShortCuts.QPushButton(layout2, "Grouped Bar Plot", self.barplot) - self.plot_buttons = [self.button_run, self.button_run2, self.button_run3] - for button in self.plot_buttons: - button.setCheckable(True) - - self.list.setData(self.data_folders) - self.addGroup() - self.current_plot_func = self.run2 - - def save(self): - new_path = QtWidgets.QFileDialog.getSaveFileName(None, "Save Session", os.getcwd(), "JSON File (*.json)") - if new_path: - if isinstance(new_path, tuple): - new_path = new_path[0] - else: - new_path = str(new_path) - list_new = [] - for item in self.list.data: - list_new.append({"name": item[0], "selected": item[1], "color": item[3], "paths": []}) - for item2 in item[2]: - list_new[-1]["paths"].append({"path": item2[0], "selected": item[1]}) - import json - with open(new_path, "w") as fp: - json.dump(list_new, fp, indent=2) - - def load(self): - new_path = QtWidgets.QFileDialog.getOpenFileName(None, "Save Session", os.getcwd(), "JSON File (*.json)") - if new_path: - if isinstance(new_path, tuple): - new_path = new_path[0] - else: - new_path = str(new_path) - import json - with open(new_path, "r") as fp: - list_new = json.load(fp) - self.list.clear() - self.list.setData([[i["name"], i["selected"], [], i["color"]] for i in list_new]) - self.data_folders = self.list.data - - for i, d in enumerate(list_new): - self.list.setCurrentRow(i) - self.list.listSelected() - self.listSelected() - self.list2.data = self.list.data[i][2] - self.add_files([d_0["path"] for d_0 in d["paths"]]) - - for ii, d_0 in enumerate(d["paths"]): - self.list2.data[ii][1] = d_0["selected"] - - def update_group_name(self): - if self.list.currentItem() is not None: - self.box_group.setTitle(f"Files for '{self.list.currentItem().text()}'") - self.box_group.setEnabled(True) - else: - self.box_group.setEnabled(False) - - def addGroup(self): - import matplotlib as mpl - text = f"Group{1+len(self.data_folders)}" - item = self.list.addData(text, True, [], mpl.colors.to_hex(f"C{len(self.data_folders)}")) - self.list.setCurrentItem(item) - self.list.editItem(item) - - - def dragEnterEvent(self, event: QtGui.QDragEnterEvent): - # accept url lists (files by drag and drop) - for url in event.mimeData().urls(): - # if str(url.toString()).strip().endswith(".npz"): - event.accept() - return - event.ignore() - - def dragMoveEvent(self, event: QtGui.QDragMoveEvent): - event.acceptProposedAction() - - def dropEvent(self, event: QtCore.QEvent): - urls = [] - for url in event.mimeData().urls(): - print(url) - url = url.toLocalFile() - if url[0] == "/" and url[2] == ":": - url = url[1:] - print(url) - if url.endswith("result.xlsx"): - urls += [url] - else: - urls += glob.glob(url + "/**/result.xlsx", recursive=True) - self.add_files(urls) - - - def addFiles(self): - settings = self.settings - class AddFilesDialog(QtWidgets.QDialog): - def __init__(self, parent): - super().__init__(parent) - self.setWindowTitle("Add Files") - with QtShortCuts.QVBoxLayout(self) as layout: - self.label = QtWidgets.QLabel("Select a path as an input wildcard. Use * to specify a placeholder. All paths that match the wildcard will be added.") - layout.addWidget(self.label) - def checker(filename): - return filename + "/**/result.xlsx" - self.inputText = QtShortCuts.QInputFolder(None, None, settings=settings, filename_checker=checker, - settings_key="batch_eval/wildcard", allow_edit=True) - with QtShortCuts.QHBoxLayout() as layout3: - # self.button_clear = QtShortCuts.QPushButton(None, "clear list", self.clear_files) - layout3.addStretch() - self.button_addList = QtShortCuts.QPushButton(None, "cancel", self.reject) - self.button_addList = QtShortCuts.QPushButton(None, "ok", self.accept) - - dialog = AddFilesDialog(self) - if not dialog.exec(): - return - - text = os.path.normpath(dialog.inputText.value()) - print(text) - files = glob.glob(text, recursive=True) - print(files) - - self.add_files(files) - - def add_files(self, files): - current_group = self.list2.data - current_files = [d[0] for d in current_group] - for file in files: - if file in current_files: - #print("File already in list", file) - continue - try: - #print("Add file", file) - res = self.getPandasData(file) - if self.list2.data is current_group: - self.list2.addData(file, True, res) - #print("replot") - self.replot() - app.processEvents() - except FileNotFoundError: - continue - - def getPandasData(self, file): - res = pd.read_excel(file) - res["filename"] = file - res["index"] = res["Unnamed: 0"] - del res["Unnamed: 0"] - res["group"] = file - return res - - def listSelected(self): - try: - data = self.data_folders[self.list.currentRow()] - except IndexError: - return - self.update_group_name() - self.list2.setData(data[2]) +import pandas as pd - def getAllCurrentPandasData(self): - results = [] - for name, checked, files, color in self.data_folders: - if checked != 0: - for name2, checked2, res, color in files: - if checked2 != 0: - res["group"] = name - results.append(res) - res = pd.concat(results) - res["t"] = res["index"] * self.dt.value() / 60 - res.to_csv("tmp_pandas.csv") +from saenopy.gui.spheroid.modules.result import ResultSpheroid +from saenopy.gui.common import QtShortCuts + +from saenopy.gui.common.plot_window import PlottingWindow + + +class PlottingWindow(PlottingWindow): + settings_key = "saenopy_sheroid" + file_extension = ".saenopySpheroid" + + dt = 1 + + def add_parameters(self): + self.type = QtShortCuts.QInputChoice(None, "type", "Pressure", ["Pressure", "Contractility"]) + self.type.valueChanged.connect(self.replot) + self.input_tbar = QtShortCuts.QInputString(None, "Comparison Time", 2, type=float) + self.input_tbar_unit = QtShortCuts.QInputChoice(self.input_tbar.layout(), None, "min", ["steps", "min", "h"]) + self.input_tbar_unit.valueChanged.connect(self.replot) + self.input_tbar.valueChanged.connect(self.replot) + + def load_file(self, file): + res: ResultSpheroid = ResultSpheroid.load(file) + data_list = [] + self.dt = res.time_delta / (60 * 60) + for i in range(len(res.res_data["pressure_mean"])): + data_list.append({}) + for name in res.res_data.keys(): + data_list[i][name] = res.res_data[name][i] + data_list[i]["filename"] = file + data_list[i]["t"] = res.time_delta * i / (60 * 60) + res.resulting_data = pd.DataFrame(data_list) return res - def replot(self): - if self.current_plot_func is not None: - self.current_plot_func() + def get_label(self): + if self.type.value() == "Contractility": + mu_name = 'contractility_mean' + y_label = 'Contractility (µN)' + else: + mu_name = 'pressure_mean' + y_label = 'Pressure (Pa)' + return mu_name, y_label def get_comparison_index(self): if self.input_tbar.value() is None: @@ -275,208 +49,7 @@ def get_comparison_index(self): if self.input_tbar_unit.value() == "steps": index = int(np.floor(self.input_tbar.value() + 0.5)) elif self.input_tbar_unit.value() == "min": - index = int(np.floor(self.input_tbar.value() / self.dt.value() + 0.5)) + index = int(np.floor(self.input_tbar.value() / 60 / self.dt + 0.5)) else: - index = int(np.floor(self.input_tbar.value() * 60 / self.dt.value() + 0.5)) + index = int(np.floor(self.input_tbar.value() / self.dt + 0.5)) return index - - def barplot(self): - for button in self.plot_buttons: - button.setChecked(False) - self.button_run3.setChecked(True) - self.current_plot_func = self.barplot - self.canvas.setActive() - plt.cla() - if self.type.value() == "Contractility": - mu_name = 'Mean Contractility (µN)' - y_label = 'Contractility (µN)' - else: - mu_name = 'Mean Pressure (Pa)' - y_label = 'Pressure (Pa)' - - # get all the data as a pandas dataframe - res = self.getAllCurrentPandasData() - - # limit the dataframe to the comparison time - index = self.get_comparison_index() - res = res[res.index == index] - - code_data = [res, ["t", "group", mu_name, "filename"]] - - color_dict = {d[0]: d[3] for d in self.data_folders} - - def plot(res, mu_name, y_label, color_dict2): - # define the colors - color_dict = color_dict2 - - # iterate over the groups - for name, data in res.groupby("group", sort=False)[mu_name]: - # add the bar with the mean value and the standard error as errorbar - plt.bar(name, data.mean(), yerr=data.sem(), error_kw=dict(capsize=5), color=color_dict[name]) - # add the number of averaged points - plt.text(name, data.mean() + data.sem(), f"n={data.count()}", ha="center", va="bottom") - - # add ticks and labels - plt.ylabel(y_label) - # despine the axes - plt.gca().spines["top"].set_visible(False) - plt.gca().spines["right"].set_visible(False) - plt.tight_layout() - # show the plot - self.canvas.draw() - - code = execute(plot, code_data[0][code_data[1]], mu_name=mu_name, y_label=y_label, color_dict2=color_dict) - - self.export_data = [code, code_data] - - def plot_groups(self): - for button in self.plot_buttons: - button.setChecked(False) - self.button_run2.setChecked(True) - self.current_plot_func = self.plot_groups - if self.type.value() == "Contractility": - mu_name = 'Mean Contractility (µN)' - std_name = 'St.dev. Contractility (µN)' - y_label = 'Contractility (µN)' - else: - mu_name = 'Mean Pressure (Pa)' - std_name = 'St.dev. Pressure (Pa)' - y_label = 'Pressure (Pa)' - - self.canvas.setActive() - plt.cla() - res = self.getAllCurrentPandasData() - - code_data = [res, ["t", "group", mu_name, "filename"]] - - # add a vertical line where the comparison time is - if self.input_tbar.value() is not None: - comp_h = self.get_comparison_index() * self.dt.value() / 60 - plt.axvline(comp_h, color="k") - - color_dict = {d[0]: d[3] for d in self.data_folders} - - def plot(res, mu_name, y_label, color_dict2): - # define the colors - color_dict = color_dict2 - - # iterate over the groups - for group_name, data in res.groupby("group", sort=False): - # get the mean and sem - x = data.groupby("t")[mu_name].agg(["mean", "sem", "count"]) - # plot the mean curve - p, = plt.plot(x.index, x["mean"], color=color_dict[group_name], lw=2, label=f"{group_name} (n={int(x['count'].mean())})") - # add a shaded area for the standard error - plt.fill_between(x.index, x["mean"] - x["sem"], x["mean"] + x["sem"], facecolor=p.get_color(), lw=0, alpha=0.5) - - # add a grid - plt.grid(True) - # add labels - plt.xlabel('Time (h)') - plt.ylabel(y_label) - plt.tight_layout() - plt.legend() - - # show - self.canvas.draw() - - code = execute(plot, code_data[0][code_data[1]], mu_name=mu_name, y_label=y_label, color_dict2=color_dict) - - self.export_data = [code, code_data] - return - - def run2(self): - for button in self.plot_buttons: - button.setChecked(False) - self.button_run.setChecked(True) - self.current_plot_func = self.run2 - if self.type.value() == "Contractility": - mu_name = 'Mean Contractility (µN)' - std_name = 'St.dev. Contractility (µN)' - y_label = 'Contractility (µN)' - else: - mu_name = 'Mean Pressure (Pa)' - std_name = 'St.dev. Pressure (Pa)' - y_label = 'Pressure (Pa)' - - try: - res = self.data_folders[self.list.currentRow()][2][self.list2.currentRow()][2] - except IndexError: - return - - #plt.figure(figsize=(6, 3)) - code_data = [res, ["t", mu_name, std_name]] - - res["t"] = res.index * self.dt.value() / 60 - - self.canvas.setActive() - plt.cla() - - def plot(res, mu_name, std_name, y_label, plot_color): - mu = res[mu_name] - std = res[std_name] - - # plot time course of mean values - p, = plt.plot(res.t, mu, lw=2, color=plot_color) - # add standard deviation area - plt.fill_between(res.t, mu - std, mu + std, facecolor=p.get_color(), lw=0, alpha=0.5) - - # add grid - plt.grid(True) - # add labels - plt.xlabel('Time (h)') - plt.ylabel(y_label) - plt.tight_layout() - - # show the plot - self.canvas.draw() - - code = execute(plot, code_data[0][code_data[1]], mu_name=mu_name, std_name=std_name, y_label=y_label, plot_color=self.data_folders[self.list.currentRow()][3]) - - self.export_data = [code, code_data] - - def export(self): - settings = self.settings - class AddFilesDialog(QtWidgets.QDialog): - def __init__(self, parent): - super().__init__(parent) - self.setWindowTitle("Export Plot") - with QtShortCuts.QVBoxLayout(self) as layout: - self.label = QtWidgets.QLabel("Select a path to export the plot script with the data.") - layout.addWidget(self.label) - self.inputText = QtShortCuts.QInputFilename(None, None, file_type="Python Script (*.py)", settings=settings, - settings_key="batch_eval/export_plot", existing=False) - self.strip_data = QtShortCuts.QInputBool(None, "export only essential data columns", True, settings=settings, settings_key="batch_eval/export_complete_df") - self.include_df = QtShortCuts.QInputBool(None, "include dataframe in script", True, settings=settings, settings_key="batch_eval/export_include_df") - with QtShortCuts.QHBoxLayout() as layout3: - # self.button_clear = QtShortCuts.QPushButton(None, "clear list", self.clear_files) - layout3.addStretch() - self.button_addList = QtShortCuts.QPushButton(None, "cancel", self.reject) - self.button_addList = QtShortCuts.QPushButton(None, "ok", self.accept) - - dialog = AddFilesDialog(self) - if not dialog.exec(): - return - - with open(str(dialog.inputText.value()), "wb") as fp: - code = "" - code += "import matplotlib.pyplot as plt\n" - code += "import pandas as pd\n" - code += "import io\n" - code += "\n" - code += "# the data for the plot\n" - res, columns = self.export_data[1] - if dialog.strip_data.value() is False: - columns = None - if dialog.include_df.value() is True: - code += "csv_data = r'''" + res.to_csv(columns=columns) + "'''\n" - code += "# load the data as a DataFrame\n" - code += "res = pd.read_csv(io.StringIO(csv_data))\n\n" - else: - csv_file = str(dialog.inputText.value()).replace(".py", "_data.csv") - res.to_csv(csv_file, columns=columns) - code += "# load the data from file\n" - code += f"res = pd.read_csv('{csv_file}')\n\n" - code += self.export_data[0] - fp.write(code.encode("utf8")) - diff --git a/saenopy/gui/spheroid/gui_deformation_spheroid.py b/saenopy/gui/spheroid/gui_deformation_spheroid.py index 647ba0ca..544ec4a0 100644 --- a/saenopy/gui/spheroid/gui_deformation_spheroid.py +++ b/saenopy/gui/spheroid/gui_deformation_spheroid.py @@ -76,13 +76,11 @@ def __init__(self, parent=None): """ """ with self.tabs.createTab("Data Analysis") as v_layout: - v_layout.setContentsMargins(0, 0, 0, 0) with QtShortCuts.QHBoxLayout() as h_layout: - h_layout.setContentsMargins(0, 0, 0, 0) #self.deformations = Force(h_layout, self) - self.deformations = PlottingWindow(self) - h_layout.addWidget(self.deformations) + self.plotting_window = PlottingWindow(self) + h_layout.addWidget(self.plotting_window) self.description = QtWidgets.QTextEdit() self.description.setReadOnly(True) diff --git a/saenopy/gui/spheroid/modules/result.py b/saenopy/gui/spheroid/modules/result.py index 52d5d04e..d33b8b31 100644 --- a/saenopy/gui/spheroid/modules/result.py +++ b/saenopy/gui/spheroid/modules/result.py @@ -56,9 +56,9 @@ def __init__(self, template, images, output, **kwargs): self.template = template self.images = images self.output = str(output) - if "res_dict" not in kwargs: - kwargs["res_dict"] = {} - self.res_dict = {} + if "res_data" not in kwargs: + kwargs["res_data"] = {} + self.res_data = {} if "res_angles" not in kwargs: kwargs["res_angles"] = {} self.res_angles = {} diff --git a/saenopy/gui/tfm2d/analyze/plot_window.py b/saenopy/gui/tfm2d/analyze/plot_window.py index 9362431b..643145bd 100644 --- a/saenopy/gui/tfm2d/analyze/plot_window.py +++ b/saenopy/gui/tfm2d/analyze/plot_window.py @@ -1,63 +1,14 @@ -import glob -import json -import os - -import matplotlib as mpl -import numpy as np import pandas as pd -from matplotlib import pyplot as plt -from matplotlib.backends.backend_qt import NavigationToolbar2QT as NavigationToolbar -from qtpy import QtWidgets, QtCore, QtGui from saenopy.gui.tfm2d.modules.result import Result2D from saenopy.gui.common import QtShortCuts -from saenopy.gui.common.gui_classes import ListWidget, MatplotlibWidget, execute - - -class AddFilesDialog(QtWidgets.QDialog): - def __init__(self, parent, settings): - super().__init__(parent) - self.setWindowTitle("Add Files") - with QtShortCuts.QVBoxLayout(self) as layout: - self.label = QtWidgets.QLabel( - "Select a path as an input wildcard. Use * to specify a placeholder. All paths that match the wildcard will be added.") - layout.addWidget(self.label) - - def checker(filename): - return filename + "/**/*.saenopy2D" - - self.inputText = QtShortCuts.QInputFolder(None, None, settings=settings, filename_checker=checker, - settings_key="pytfm/analyse_force_wildcard", allow_edit=True) - with QtShortCuts.QHBoxLayout() as layout3: - # self.button_clear = QtShortCuts.QPushButton(None, "clear list", self.clear_files) - layout3.addStretch() - self.button_addList = QtShortCuts.QPushButton(None, "cancel", self.reject) - self.button_addList = QtShortCuts.QPushButton(None, "ok", self.accept) - - -class ExportDialog(QtWidgets.QDialog): - def __init__(self, parent, settings): - super().__init__(parent) - self.setWindowTitle("Export Plot") - with QtShortCuts.QVBoxLayout(self) as layout: - self.label = QtWidgets.QLabel("Select a path to export the plot script with the data.") - layout.addWidget(self.label) - self.inputText = QtShortCuts.QInputFilename(None, None, file_type="Python Script (*.py)", settings=settings, - settings_key="pytfm/export_plot", existing=False) - self.strip_data = QtShortCuts.QInputBool(None, "export only essential data columns", True, settings=settings, settings_key="pytfm/export_complete_df") - self.include_df = QtShortCuts.QInputBool(None, "include dataframe in script", True, settings=settings, settings_key="pytfm/export_include_df") - with QtShortCuts.QHBoxLayout() as layout3: - # self.button_clear = QtShortCuts.QPushButton(None, "clear list", self.clear_files) - layout3.addStretch() - self.button_addList = QtShortCuts.QPushButton(None, "cancel", self.reject) - self.button_addList = QtShortCuts.QPushButton(None, "ok", self.accept) - from saenopy.gui.common.plot_window import PlottingWindow + class PlottingWindow(PlottingWindow): - settings_key = "Seanopy_deformation" - file_extension = ".saenopy" + settings_key = "Seanopy_2d" + file_extension = ".saenopy2D" def add_parameters(self): self.type = QtShortCuts.QInputChoice(None, "type", "area", @@ -173,505 +124,3 @@ def get_label(self): y_label = '' return mu_name, y_label - -class PlottingWindowX(QtWidgets.QWidget): - progress_signal = QtCore.Signal(int, int, int, int) - finished_signal = QtCore.Signal() - thread = None - - def __init__(self, parent=None): - super().__init__(parent) - - # QSettings - self.settings = QtCore.QSettings("Saenopy", "Saenopy") - - self.setMinimumWidth(800) - self.setMinimumHeight(400) - self.setWindowTitle("Saenopy Evaluation") - - self.images = [] - self.data_folders = [] - self.current_plot_func = lambda: None - - with QtShortCuts.QVBoxLayout(self) as main_layout0: - main_layout0.setContentsMargins(0, 0, 0, 0) - with QtShortCuts.QHBoxLayout() as main_layout00: - self.button_save = QtShortCuts.QPushButton(None, "save", self.save) - self.button_load = QtShortCuts.QPushButton(None, "load", self.load) - main_layout00.addStretch() - with QtShortCuts.QHBoxLayout() as main_layout: - with QtShortCuts.QVBoxLayout() as layout: - with QtShortCuts.QGroupBox(None, "Groups") as (_, layout2): - layout2.setContentsMargins(0, 3, 0, 1) - self.list = ListWidget(layout2, True, add_item_button="add group", color_picker=True) - self.list.setStyleSheet("QListWidget{border: none}") - self.list.itemSelectionChanged.connect(self.listSelected) - self.list.itemChanged.connect(self.replot) - self.list.itemChanged.connect(self.update_group_name) - self.list.addItemClicked.connect(self.addGroup) - - with QtShortCuts.QGroupBox(layout, "Group") as (self.box_group, layout2): - layout2.setContentsMargins(0, 3, 0, 1) - self.list2 = ListWidget(layout2, add_item_button="add files") - self.list2.setStyleSheet("QListWidget{border: none}") - self.list2.itemSelectionChanged.connect(self.run2) - self.list2.itemChanged.connect(self.replot) - self.list2.addItemClicked.connect(self.addFiles) - - self.setAcceptDrops(True) - - with QtShortCuts.QGroupBox(main_layout, "Plot Forces") as (_, layout): - self.type = QtShortCuts.QInputChoice(None, "type", "area", - ["area", - "cell number", - "mean normal stress", - "max normal stress", - "max shear stress", - "cv mean normal stress", - "cv max normal stress", - "cv max shear stress", - "average magnitude line tension", - "std magnitude line tension", - "average normal line tension", - "std normal line tension", - "average shear line tension", - "std shear line tension", - - "average cell force", - "average cell pressure", - "average cell shear", - "std cell force", - "std cell pressure", - "std cell shear", - - "contractility", - "strain energy", - ]) - self.type.valueChanged.connect(self.replot) - self.agg = QtShortCuts.QInputChoice(None, "aggregate", "mean", - ["mean", "max", "min", "median"]) - self.agg.valueChanged.connect(self.replot) - self.agg.setHidden(True) - - self.canvas = MatplotlibWidget(self) - layout.addWidget(self.canvas) - layout.addWidget(NavigationToolbar(self.canvas, self)) - - with QtShortCuts.QHBoxLayout() as layout2: - self.button_export = QtShortCuts.QPushButton(layout2, "Export", self.export) - layout2.addStretch() - #self.button_run = QtShortCuts.QPushButton(layout2, "Single Time Course", self.run2) - #self.button_run2 = QtShortCuts.QPushButton(layout2, "Grouped Time Courses", self.plot_groups) - self.button_run3 = QtShortCuts.QPushButton(layout2, "Grouped Bar Plot", self.barplot) - self.plot_buttons = [self.button_run3] - for button in self.plot_buttons: - button.setCheckable(True) - - self.list.setData(self.data_folders) - self.addGroup() - self.current_plot_func = self.barplot - - def save(self): - new_path = QtWidgets.QFileDialog.getSaveFileName(None, "Save Session", os.getcwd(), "JSON File (*.json)") - if new_path: - if not new_path.endswith(".json"): - new_path += ".json" - list_new = [] - for item in self.list.data: - list_new.append({"name": item[0], "selected": item[1], "color": item[3], "paths": []}) - for item2 in item[2]: - list_new[-1]["paths"].append({"path": item2[0], "selected": item[1]}) - - with open(new_path, "w") as fp: - json.dump(list_new, fp, indent=2) - - def load(self): - new_path = QtWidgets.QFileDialog.getOpenFileName(None, "Load Session", os.getcwd(), "JSON File (*.json)") - if new_path: - with open(new_path, "r") as fp: - list_new = json.load(fp) - self.list.clear() - self.list.setData([[i["name"], i["selected"], [], i["color"]] for i in list_new]) - self.data_folders = self.list.data - - for i, d in enumerate(list_new): - self.list.setCurrentRow(i) - self.list.listSelected() - self.listSelected() - self.list2.data = self.list.data[i][2] - self.add_files([d_0["path"] for d_0 in d["paths"]]) - - for ii, d_0 in enumerate(d["paths"]): - self.list2.data[ii][1] = d_0["selected"] - - def dragEnterEvent(self, event: QtGui.QDragEnterEvent): - # accept url lists (files by drag and drop) - for url in event.mimeData().urls(): - # if str(url.toString()).strip().endswith(".npz"): - event.accept() - return - event.ignore() - - def dragMoveEvent(self, event: QtGui.QDragMoveEvent): - event.acceptProposedAction() - - def dropEvent(self, event: QtCore.QEvent): - urls = [] - for url in event.mimeData().urls(): - url = url.toLocalFile() - if url[0] == "/" and url[2] == ":": - url = url[1:] - if url.endswith(".saenopy2D"): - urls += [url] - else: - urls += glob.glob(url + "/**/*.saenopy2D", recursive=True) - self.add_files(urls) - - def add_files(self, urls): - current_group = self.list2.data - current_files = [d[0] for d in current_group] - for file in urls: - if file in current_files: - print("File already in list", file) - continue - try: - print("Add file", file) - - if self.list2.data is current_group: - self.list2.addData(file, True, res) - self.replot() - #app.processEvents() - except FileNotFoundError: - continue - self.check_results_with_time() - - def check_results_with_time(self): - time_values = False - if time_values is False: - self.barplot() - self.agg.setEnabled(time_values) - #self.button_run.setEnabled(time_values) - #self.button_run2.setEnabled(time_values) - - - def update_group_name(self): - if self.list.currentItem() is not None: - self.box_group.setTitle(f"Files for '{self.list.currentItem().text()}'") - self.box_group.setEnabled(True) - else: - self.box_group.setEnabled(False) - - def addGroup(self): - text = f"Group{1+len(self.data_folders)}" - item = self.list.addData(text, True, [], mpl.colors.to_hex(f"C{len(self.data_folders)}")) - self.list.setCurrentItem(item) - self.list.editItem(item) - - def addFiles(self): - dialog = AddFilesDialog(self, self.settings) - if not dialog.exec(): - return - - text = os.path.normpath(dialog.inputText.value()) - files = glob.glob(text, recursive=True) - - self.add_files(files) - - def listSelected(self): - try: - data = self.data_folders[self.list.currentRow()] - except IndexError: - return - self.update_group_name() - self.list2.setData(data[2]) - - def getAllCurrentPandasData(self): - results = [] - for name, checked, files, color in self.data_folders: - if checked != 0: - for name2, checked2, res, color in files: - if checked2 != 0: - res.resulting_data["group"] = name - results.append(res.resulting_data) - res = pd.concat(results) - #res["t"] = res["index"] * self.dt.value() / 60 - res.to_csv("tmp_pandas.csv") - return res - - def replot(self): - if self.current_plot_func is not None: - self.current_plot_func() - - def barplot(self): - for button in self.plot_buttons: - button.setChecked(False) - self.button_run3.setChecked(True) - self.current_plot_func = self.barplot - self.canvas.setActive() - plt.cla() - if self.type.value() == "area": - mu_name = 'area Cell Area' - y_label = 'area (m$^2$)' - elif self.type.value() == "cell number": - mu_name = 'cell number' - y_label = 'cell number' - elif self.type.value() == "mean normal stress": - mu_name = 'mean normal stress Cell Area' - y_label = 'mean normal stress (N/m)' - elif self.type.value() == "max normal stress": - mu_name = 'max normal stress Cell Area' - y_label = 'max normal stress (N/m)' - elif self.type.value() == "max shear stress": - mu_name = 'max shear stress Cell Area' - y_label = 'max shear stress (N/m)' - elif self.type.value() == "cv mean normal stress": - mu_name = 'cv mean normal stress Cell Area' - y_label = 'cv mean normal stress' - elif self.type.value() == "cv max normal stress": - mu_name = 'cv max normal stress Cell Area' - y_label = 'cv max normal stress' - elif self.type.value() == "cv max shear stress": - mu_name = 'cv max shear stress Cell Area' - y_label = 'cv max shear stress' - elif self.type.value() == "average magnitude line tension": - mu_name = 'average magnitude line tension' - y_label = 'average magnitude line tension (N/m)' - elif self.type.value() == "std magnitude line tension": - mu_name = 'std magnitude line tension' - y_label = 'std magnitude line tension' - elif self.type.value() == "average normal line tension": - mu_name = 'average normal line tension' - y_label = 'average normal line tension (N/m)' - elif self.type.value() == "std normal line tension": - mu_name = 'std normal line tension' - y_label = 'std normal line tension' - elif self.type.value() == "average shear line tension": - mu_name = 'average shear line tension' - y_label = 'average shear line tension (N/m)' - elif self.type.value() == "std shear line tension": - mu_name = 'std shear line tension' - y_label = 'std shear line tension' - elif self.type.value() == "average cell force": - mu_name = 'average cell force' - y_label = 'average cell force (N/m)' - elif self.type.value() == "average cell pressure": - mu_name = 'average cell pressure' - y_label = 'average cell pressure (N/m)' - elif self.type.value() == "average cell shear": - mu_name = 'average cell shear' - y_label = 'average cell shear (N/m)' - elif self.type.value() == "std cell force": - mu_name = 'std cell force' - y_label = 'std cell force' - elif self.type.value() == "std cell pressure": - mu_name = 'std cell pressure' - y_label = 'std cell pressure' - elif self.type.value() == "std cell shear": - mu_name = 'std cell shear' - y_label = 'std cell shear' - elif self.type.value() == "contractility": - mu_name = 'contractility' - y_label = 'contractility (N)' - elif self.type.value() == "strain energy": - mu_name = 'strain energy' - y_label = 'strain energy (J)' - elif self.type.value() == "": - mu_name = '' - y_label = '' - - # get all the data as a pandas dataframe - res = self.getAllCurrentPandasData() - - # limit the dataframe to the comparison time - res0 = res.groupby("filename").agg("max") - del res["group"] - res = res.groupby("filename").agg(self.agg.value()) - res["group"] = res0["group"] - if mu_name not in res: - res[mu_name] = np.nan - #index = self.get_comparison_index() - #res = res[res.index == index] - - code_data = [res, ["group", mu_name]] - - color_dict = {d[0]: d[3] for d in self.data_folders} - - def plot(res, mu_name, y_label, color_dict2): - # define the colors - color_dict = color_dict2 - - # iterate over the groups - for name, data in res.groupby("group", sort=False)[mu_name]: - # add the bar with the mean value and the standard error as errorbar - if np.isnan(data.sem()): - plt.bar(name, data.mean(), color=color_dict[name]) - else: - plt.bar(name, data.mean(), yerr=data.sem(), error_kw=dict(capsize=5), color=color_dict[name]) - # add the number of averaged points - plt.text(name, data.mean() + data.sem(), f"n={data.count()}", ha="center", va="bottom") - - # add ticks and labels - plt.ylabel(y_label) - # despine the axes - plt.gca().spines["top"].set_visible(False) - plt.gca().spines["right"].set_visible(False) - plt.tight_layout() - # show the plot - self.canvas.draw() - - code = execute(plot, code_data[0][code_data[1]], mu_name=mu_name, y_label=y_label, color_dict2=color_dict) - - self.export_data = [code, code_data] - - def plot_groups(self): - for button in self.plot_buttons: - button.setChecked(False) - self.button_run2.setChecked(True) - self.current_plot_func = self.plot_groups - if self.type.value() == "strain_energy": - mu_name = 'strain_energy' - y_label = 'Strain Energy' - elif self.type.value() == "contractility": - mu_name = 'contractility' - y_label = 'Contractility' - elif self.type.value() == "polarity": - mu_name = 'polarity' - y_label = 'Polarity' - elif self.type.value() == "99_percentile_deformation": - mu_name = '99_percentile_deformation' - y_label = 'Deformation' - elif self.type.value() == "99_percentile_force": - mu_name = '99_percentile_force' - y_label = 'Force' - - self.canvas.setActive() - plt.cla() - res = self.getAllCurrentPandasData() - - code_data = [res, ["t", "group", mu_name, "filename"]] - - color_dict = {d[0]: d[3] for d in self.data_folders} - - def plot(res, mu_name, y_label, color_dict2): - # define the colors - color_dict = color_dict2 - - # iterate over the groups - for group_name, data in res.groupby("group", sort=False): - # get the mean and sem - x = data.groupby("t")[mu_name].agg(["mean", "sem", "count"]) - # plot the mean curve - p, = plt.plot(x.index, x["mean"], color=color_dict[group_name], lw=2, label=f"{group_name} (n={int(x['count'].mean())})") - # add a shaded area for the standard error - plt.fill_between(x.index, x["mean"] - x["sem"], x["mean"] + x["sem"], facecolor=p.get_color(), lw=0, alpha=0.5) - - # add a grid - plt.grid(True) - # add labels - plt.xlabel('Time (h)') - plt.ylabel(y_label) - plt.tight_layout() - plt.legend() - - # show - self.canvas.draw() - - code = execute(plot, code_data[0][code_data[1]], mu_name=mu_name, y_label=y_label, color_dict2=color_dict) - - self.export_data = [code, code_data] - return - - def run2(self): - if not self.button_run.isEnabled(): - return - for button in self.plot_buttons: - button.setChecked(False) - self.button_run.setChecked(True) - #return - self.current_plot_func = self.run2 - if self.type.value() == "area Cell Area": - mu_name = 'area Cell Area' - y_label = 'area Cell Area' - elif self.type.value() == "contractility": - mu_name = 'contractility' - y_label = 'Contractility' - elif self.type.value() == "polarity": - mu_name = 'polarity' - y_label = 'Polarity' - elif self.type.value() == "99_percentile_deformation": - mu_name = '99_percentile_deformation' - y_label = 'Deformation' - elif self.type.value() == "99_percentile_force": - mu_name = '99_percentile_force' - y_label = 'Force' - if 0: - if self.type.value() == "Contractility": - mu_name = 'Mean Contractility (µN)' - std_name = 'St.dev. Contractility (µN)' - y_label = 'Contractility (µN)' - else: - mu_name = 'Mean Pressure (Pa)' - std_name = 'St.dev. Pressure (Pa)' - y_label = 'Pressure (Pa)' - - try: - res = self.data_folders[self.list.currentRow()][2][self.list2.currentRow()][2].resulting_data - except IndexError: - return - - #plt.figure(figsize=(6, 3)) - print(res) - code_data = [res, [mu_name]] - - #res["t"] = res.index * self.dt.value() / 60 - - self.canvas.setActive() - plt.cla() - - def plot(res, mu_name, y_label, plot_color): - mu = res[mu_name] - - # plot time course of mean values - p, = plt.plot(res.t, mu, lw=2, color=plot_color) - - # add grid - plt.grid(True) - # add labels - plt.xlabel('Time (h)') - plt.ylabel(y_label) - plt.tight_layout() - - # show the plot - self.canvas.draw() - - code = execute(plot, code_data[0][code_data[1]], mu_name=mu_name, y_label=y_label, plot_color=self.data_folders[self.list.currentRow()][3]) - - self.export_data = [code, code_data] - - def export(self): - dialog = ExportDialog(self, self.settings) - if not dialog.exec(): - return - - with open(str(dialog.inputText.value()), "wb") as fp: - code = "" - code += "import matplotlib.pyplot as plt\n" - code += "import pandas as pd\n" - code += "import numpy as np\n" - code += "import io\n" - code += "\n" - code += "# the data for the plot\n" - res, columns = self.export_data[1] - if dialog.strip_data.value() is False: - columns = None - if dialog.include_df.value() is True: - code += "csv_data = r'''" + res.to_csv(columns=columns) + "'''\n" - code += "# load the data as a DataFrame\n" - code += "res = pd.read_csv(io.StringIO(csv_data))\n\n" - else: - csv_file = str(dialog.inputText.value()).replace(".py", "_data.csv") - res.to_csv(csv_file, columns=columns) - code += "# load the data from file\n" - code += f"res = pd.read_csv('{csv_file}')\n\n" - code += self.export_data[0] - fp.write(code.encode("utf8")) diff --git a/saenopy/gui/tfm2d/modules/result.py b/saenopy/gui/tfm2d/modules/result.py index 3d106eb8..0baf1e69 100644 --- a/saenopy/gui/tfm2d/modules/result.py +++ b/saenopy/gui/tfm2d/modules/result.py @@ -203,8 +203,7 @@ class Mesh2D: vx = self.tx vy = self.ty vf = 0.1 - print("vx", vx) - print("vx shape", vx is not None) + if vx is not None: mesh = Mesh2D() mesh.units = "pixels" @@ -216,7 +215,6 @@ class Mesh2D: mesh.nodes = np.array([x.ravel(), y.ravel()]).T mesh.displacements_measured = np.array([vx.ravel(), -vy.ravel()]).T * vf return mesh, mesh.displacements_measured - #print("tx", self.tx.shape) return None, None