diff --git a/saenopy/gui/orientation/analyze/PlottingWindow.py b/saenopy/gui/orientation/analyze/PlottingWindow.py new file mode 100644 index 0000000..eae8761 --- /dev/null +++ b/saenopy/gui/orientation/analyze/PlottingWindow.py @@ -0,0 +1,342 @@ +# Setting the Qt bindings for QtPy +import os + +import pandas as pd +from qtpy import QtCore, QtWidgets, QtGui +from saenopy.gui.common import QtShortCuts +from saenopy.gui.common.gui_classes import Spoiler, CheckAbleGroup, QHLine, QVLine, MatplotlibWidget, NavigationToolbar, execute, kill_thread, ListWidget +import matplotlib.pyplot as plt +import imageio +import glob +from pathlib import Path + +settings = QtCore.QSettings("FabryLab", "CompactionAnalyzer") + +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) + + self.setMinimumWidth(800) + self.setMinimumHeight(400) + self.setWindowTitle("Saenopy Evaluation") + + self.images = [] + self.data_folders = [] + self.current_plot_func = lambda: None + + with QtShortCuts.QHBoxLayout(self) 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) + + with QtShortCuts.QGroupBox(main_layout, "Plot Forces") as (_, layout): + self.type = QtShortCuts.QInputChoice(None, "type", "global orientation", ["global orientation", "normalized intensity (first shell)", "orientation over distance", "normed intensity over distance"]) + self.type.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 Plot", self.run2) + self.button_run2 = QtShortCuts.QPushButton(layout2, "Grouped Plot", self.plot_groups) + self.plot_buttons = [self.button_run, self.button_run2] + for button in self.plot_buttons: + button.setCheckable(True) + + self.list.setData(self.data_folders) + self.addGroup() + self.current_plot_func = self.run2 + + 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 addFiles(self): + 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 + "/**/results_total.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_addList0 = QtShortCuts.QPushButton(None, "cancel", self.reject) + self.button_addList1 = QtShortCuts.QPushButton(None, "ok", self.accept) + + dialog = AddFilesDialog(self) + if not dialog.exec(): + return + + text = os.path.normpath(dialog.inputText.value()) + files = glob.glob(text, recursive=True) + + 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 + if self.list2.data is current_group: + data = {"results_total": pd.read_excel(file), + "results_distance": pd.read_excel(Path(file).parent / "results_distance.xlsx"), + "image": Path(file).parent / "Figures" / "overlay2.png"} + self.list2.addData(file, True, data) + + 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, key, only_first_line=False): + results = [] + for name, checked, files, color in self.data_folders: + if checked != 0: + for name2, checked2, res, color in files: + if checked2 != 0: + res[key]["group"] = name + if only_first_line is True: + results.append(res[key].iloc[0:1]) + else: + results.append(res[key]) + res = pd.concat(results) + res.to_csv("tmp_pandas.csv") + return res + + def replot(self): + if self.current_plot_func is not None: + self.current_plot_func() + + 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 + + self.button_export.setDisabled(False) + + self.canvas.setActive() + plt.cla() + plt.axis("auto") + if self.type.value() == "global orientation": + res = self.getAllCurrentPandasData("results_total") + code_data = [res, ["group", 'Orientation (weighted by intensity and coherency)']] + def plot(res, color_dict2): + # define the colors + color_dict = color_dict2 + + # iterate over the groups + for name, d in res.groupby("group")['Orientation (weighted by intensity and coherency)']: + plt.bar(name, d.mean(), yerr=d.sem(), color=color_dict[name]) + + # add ticks and labels + plt.ylabel("orientation") + # 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() + + elif self.type.value() == "normalized intensity (first shell)": + + res = self.getAllCurrentPandasData("results_distance", only_first_line=True) + code_data = [res, ["group", 'Intensity Norm (individual)']] + + def plot(res, color_dict2): + # define the colors + color_dict = color_dict2 + + # iterate over the groups + for name, d in res.groupby("group")['Intensity Norm (individual)']: + plt.bar(name, d.mean(), yerr=d.sem(), color=color_dict[name]) + + # add ticks and labels + plt.ylabel("normalized intensity") + # 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() + + elif self.type.value() == "orientation over distance": + res = self.getAllCurrentPandasData("results_distance") + code_data = [res, ["group", "Shell_mid (µm)", "Orientation (individual)"]] + + def plot(res, color_dict2): + # define the colors + color_dict = color_dict2 + + # iterate over the groups + for name, d in res.groupby("group"): + d = d.groupby("Shell_mid (µm)")["Orientation (individual)"].agg(["mean", "sem"]) + plt.plot(d.index,d["mean"], color=color_dict[name]) + plt.fill_between(d.index, d["mean"]-d["sem"], d["mean"]+d["sem"], color=color_dict[name], alpha=0.5) + + # add ticks and labels + plt.xlabel("shell mid (µm)") + plt.ylabel("individual orientation") + # 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() + + elif self.type.value() == "normed intensity over distance": + res = self.getAllCurrentPandasData("results_distance") + code_data = [res, ["group", "Shell_mid (µm)", 'Intensity Norm (individual)']] + + def plot(res, color_dict2): + # define the colors + color_dict = color_dict2 + + # iterate over the groups + for name, d in res.groupby("group"): + d = d.groupby("Shell_mid (µm)")['Intensity Norm (individual)'].agg(["mean", "sem"]) + plt.plot(d.index, d["mean"], color=color_dict[name]) + plt.fill_between(d.index, d["mean"] - d["sem"], d["mean"] + d["sem"], color=color_dict[name], alpha=0.5) + + # add ticks and labels + plt.xlabel("shell mid (µm)") + plt.ylabel("normalized intensity") + # 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() + + self.canvas.setActive() + plt.cla() + + color_dict = {d[0]: d[3] for d in self.data_folders} + + code = execute(plot, code_data[0][code_data[1]], 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 + + self.button_export.setDisabled(True) + + data = self.list2.data[self.list2.currentRow()][2] + im = imageio.v2.imread(data["image"]) + + plot_color = self.list.data[self.list.currentRow()][3] + + self.canvas.setActive() + plt.cla() + plt.axis("auto") + if self.type.value() == "global orientation": + plt.imshow(im) + plt.title(f"Orientation {data['results_total']['Orientation (weighted by intensity and coherency)'].iloc[0]:.3f}") + plt.axis("off") + elif self.type.value() == "normalized intensity (first shell)": + plt.imshow(im) + plt.title(f"Normalized Intensity {data['results_distance']['Intensity Norm (individual)'].iloc[0]:.3f}") + plt.axis("off") + elif self.type.value() == "normed intensity over distance": + plt.plot(data["results_distance"]["Shell_mid (µm)"], + data["results_distance"]["Intensity Norm (individual)"], color=plot_color) + plt.xlabel("shell mid (µm)") + plt.ylabel("intensity norm") + elif self.type.value() == "orientation over distance": + plt.plot(data["results_distance"]["Shell_mid (µm)"], + data["results_distance"]["Orientation (individual)"], color=plot_color) + plt.xlabel("shell mid (µm)") + plt.ylabel("individual orientation") + # despine the axes + plt.gca().spines["top"].set_visible(False) + plt.gca().spines["right"].set_visible(False) + plt.tight_layout() + self.canvas.draw() + + def export(self): + 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")) \ No newline at end of file diff --git a/saenopy/gui/orientation/gui_orientation.py b/saenopy/gui/orientation/gui_orientation.py index a25c891..e6f0538 100644 --- a/saenopy/gui/orientation/gui_orientation.py +++ b/saenopy/gui/orientation/gui_orientation.py @@ -1,26 +1,12 @@ import sys -# Setting the Qt bindings for QtPy -import os - -import pandas as pd -import qtawesome as qta from qtpy import QtCore, QtWidgets, QtGui -import numpy as np -from saenopy.gui.common import QtShortCuts, QExtendedGraphicsView -from saenopy.gui.common.gui_classes import Spoiler, CheckAbleGroup, QHLine, QVLine, MatplotlibWidget, NavigationToolbar, execute, kill_thread, ListWidget -import imageio -from qimage2ndarray import array2qimage -import matplotlib.pyplot as plt -import glob -import imageio -import threading +from saenopy.gui.common import QtShortCuts +from saenopy.gui.common.gui_classes import QHLine import glob -import re -from pathlib import Path -from saenopy.gui.orientation.modules.AddFilesDialog import AddFilesDialog -from saenopy.examples import get_examples_orientation +from saenopy.gui.orientation.analyze.PlottingWindow import PlottingWindow +from saenopy.gui.orientation.modules.BatchEvaluate import BatchEvaluate ################ @@ -144,791 +130,6 @@ def previous(self): self.tabs.setCurrentIndex(self.tabs.currentIndex()-1) - - - -class BatchEvaluate(QtWidgets.QWidget): - progress_signal = QtCore.Signal(int, int, int, int) - measurement_evaluated_signal = QtCore.Signal(int, int) - finished_signal = QtCore.Signal() - thread = None - - def __init__(self, parent=None): - super().__init__(parent) - - self.setMinimumWidth(800) - self.setMinimumHeight(400) - self.setWindowTitle("Saenopy Viewer") - - with QtShortCuts.QHBoxLayout(self) as main_layout: - with QtShortCuts.QSplitter() as lay: - with QtShortCuts.QVBoxLayout() as layout: - layout.setContentsMargins(0, 0, 0, 0) - self.list = ListWidget(layout, add_item_button="add measurements") - self.list.addItemClicked.connect(self.show_files) - self.list.itemSelectionChanged.connect(self.listSelected) - self.progress1 = QtWidgets.QProgressBar() - layout.addWidget(self.progress1) - - with QtShortCuts.QVBoxLayout() as layout: - self.label_text = QtWidgets.QLabel().addToLayout() - - self.label = QExtendedGraphicsView.QExtendedGraphicsView().addToLayout() - self.label.setMinimumWidth(300) - self.pixmap = QtWidgets.QGraphicsPixmapItem(self.label.origin) - self.contour = QtWidgets.QGraphicsPathItem(self.label.origin) - pen = QtGui.QPen(QtGui.QColor(255, 0, 0)) - pen.setCosmetic(True) - self.contour.setPen(pen) - - self.label2 = QExtendedGraphicsView.QExtendedGraphicsView().addToLayout() - self.pixmap2 = QtWidgets.QGraphicsPixmapItem(self.label2.origin) - - self.contour2 = QtWidgets.QGraphicsPathItem(self.label2.origin) - self.contour2.setPen(pen) - - #self.label_text2 = QtWidgets.QLabel().addToLayout() - #self.progress2 = QtWidgets.QProgressBar().addToLayout() - - frame = QtWidgets.QFrame().addToLayout() - frame.setMaximumWidth(300) - with QtShortCuts.QVBoxLayout(frame) as layout: - frame2 = QtWidgets.QFrame().addToLayout() - with QtShortCuts.QVBoxLayout(frame2, no_margins=True) as layout: - with QtShortCuts.QHBoxLayout(): - self.scale = QtShortCuts.QInputString(None, "scale", "1.0", type=float, settings=settings, settings_key="orientation/scale") - QtWidgets.QLabel("um/px").addToLayout() - with QtShortCuts.QHBoxLayout(): - self.sigma_tensor = QtShortCuts.QInputString(None, "sigma_tensor", "7.0", type=float, settings=settings, settings_key="orientation/sigma_tensor") - self.sigma_tensor_type = QtShortCuts.QInputChoice(None, "", "um", ["um", "pixel"], settings=settings, settings_key="orientation/sigma_tensor_unit") - self.sigma_tensor_button = QtShortCuts.QPushButton(None, "detect", self.sigma_tensor_button_clicked) - self.sigma_tensor_button.setDisabled(True) - with QtShortCuts.QHBoxLayout(): - self.edge = QtShortCuts.QInputString(None, "edge", "40", type=int, settings=settings, settings_key="orientation/edge", tooltip="How many pixels to cut at the edge of the image.") - QtWidgets.QLabel("px").addToLayout() - self.max_dist = QtShortCuts.QInputString(None, "max_dist", "None", type=int, settings=settings, settings_key="orientation/max_dist", tooltip="Optional: specify the maximal distance around the cell center", none_value=None) - QtWidgets.QLabel("px").addToLayout() - - with QtShortCuts.QHBoxLayout(): - self.sigma_first_blur = QtShortCuts.QInputString(None, "sigma_first_blur", "0.5", type=float, settings=settings, settings_key="orientation/sigma_first_blur") - QtWidgets.QLabel("px").addToLayout() - with QtShortCuts.QHBoxLayout(): - self.angle_sections = QtShortCuts.QInputString(None, "angle_sections", "5", type=int, settings=settings, settings_key="orientation/angle_sections") - QtWidgets.QLabel("deg").addToLayout() - - with QtShortCuts.QHBoxLayout(): - self.shell_width = QtShortCuts.QInputString(None, "shell_width", "5", type=float, - settings=settings, - settings_key="orientation/shell_width") - self.shell_width_type = QtShortCuts.QInputChoice(None, "", "um", ["um", "pixel"], - settings=settings, - settings_key="orientation/shell_width_type") - - with QtShortCuts.QGroupBox(None, "Segmentation Parameters"): - self.segmention_thres = QtShortCuts.QInputString(None, "segmention_thresh", "1.0", type=float, - settings=settings, - settings_key="orientation/segmention_thres") - self.segmention_thres.valueChanged.connect(self.listSelected) - with QtShortCuts.QHBoxLayout(): - self.seg_gaus1 = QtShortCuts.QInputString(None, "seg_gauss1", "0.5", type=float, settings=settings, - settings_key="orientation/seg_gaus1") - self.seg_gaus1.valueChanged.connect(self.listSelected) - self.seg_gaus2 = QtShortCuts.QInputString(None, "seg_gauss2", "100", type=float, settings=settings, - settings_key="orientation/seg_gaus2") - self.seg_gaus2.valueChanged.connect(self.listSelected) - - with CheckAbleGroup(self, "individual segmentation").addToLayout() as self.individual_data: - with QtShortCuts.QVBoxLayout() as layout2: - self.segmention_thres_indi = QtShortCuts.QInputString(None, "segmention_thresh", None, type=float, allow_none=True) - self.segmention_thres_indi.valueChanged.connect(self.listSelected) - with QtShortCuts.QHBoxLayout(): - self.seg_gaus1_indi = QtShortCuts.QInputString(None, "seg_gauss1", None, type=float, allow_none=True) - self.seg_gaus1_indi.valueChanged.connect(self.listSelected) - self.seg_gaus2_indi = QtShortCuts.QInputString(None, "seg_gauss2", None, type=float, allow_none=True) - self.seg_gaus2_indi.valueChanged.connect(self.listSelected) - - layout.addStretch() - - self.button_run = QtShortCuts.QPushButton(None, "run", self.run) - self.images = [] - self.data = [] - self.list.setData(self.data) - - self.input_list = [ - frame2, - ] - - self.individual_data.value_changed.connect(self.changedCheckBox) - - self.progress_signal.connect(self.progress_callback) - self.measurement_evaluated_signal.connect(self.measurement_evaluated) - self.finished_signal.connect(self.finished) - - def sigma_tensor_button_clicked(self): - parent = self - class SigmaRange(QtWidgets.QDialog): - def __init__(self, parent): - super().__init__(parent) - self.setWindowTitle("Determine Sigma Tensor") - with QtShortCuts.QVBoxLayout(self) as layout: - self.output_folder = QtShortCuts.QInputFolder(None, "output folder", settings=settings, settings_key="orientation/sigma_tensor_range_output") - self.label_scale = QtWidgets.QLabel(f"Scale is {parent.scale.value()} px/um").addToLayout(layout) - with QtShortCuts.QHBoxLayout() as layout2: - self.sigma_tensor_min = QtShortCuts.QInputString(None, "min", "1.0", type=float, settings=settings, settings_key="orientation/sigma_tensor_min") - self.sigma_tensor_max = QtShortCuts.QInputString(None, "max", "15", type=float, settings=settings, settings_key="orientation/sigma_tensor_max") - self.sigma_tensor_step = QtShortCuts.QInputString(None, "step", "1", type=float, settings=settings, settings_key="orientation/sigma_tensor_step") - self.sigma_tensor_type = QtShortCuts.QInputChoice(None, "", "um", ["um", "pixel"], settings=settings, settings_key="orientation/sigma_tensor_unit") - - self.progresss = QtWidgets.QProgressBar().addToLayout(layout) - - self.canvas = MatplotlibWidget(self) - layout.addWidget(self.canvas) - layout.addWidget(NavigationToolbar(self.canvas, self)) - - with QtShortCuts.QHBoxLayout() as layout3: - # self.button_clear = QtShortCuts.QPushButton(None, "clear list", self.clear_files) - layout3.addStretch() - self.button_addList2 = QtShortCuts.QPushButton(None, "cancel", self.reject) - self.button_addList1 = QtShortCuts.QPushButton(None, "run", self.run) - - def run(self): - from natsort import natsorted - fiber, cell, output, attr = parent.data[parent.list.currentRow()][2] - - output_folder = self.output_folder.value() - - sigma_tensor_min = self.sigma_tensor_min.value() - sigma_tensor_max = self.sigma_tensor_max.value() - sigma_tensor_step = self.sigma_tensor_step.value() - if self.sigma_tensor_type.value() == "um": - sigma_tensor_min /= parent.scale.value() - sigma_tensor_max /= parent.scale.value() - sigma_tensor_step /= parent.scale.value() - shell_width = parent.shell_width.value() - if parent.shell_width_type.value() == "um": - shell_width /= parent.scale.value() - - sigma_list = np.arange(sigma_tensor_min, sigma_tensor_max+sigma_tensor_step, sigma_tensor_step) - self.progresss.setRange(0, len(sigma_list)) - - from CompactionAnalyzer.CompactionFunctions import StuctureAnalysisMain, generate_lists - for index, sigma in enumerate(sigma_list): - sigma = float(sigma) - self.progresss.setValue(index) - app.processEvents() - # Create outputfolder - output_sub = os.path.join(output_folder, rf"Sigma{str(sigma*parent.scale.value()).zfill(3)}") # subpath to store results - fiber_list, cell_list, out_list = generate_lists(fiber, cell, - output_main=output_sub) - - - StuctureAnalysisMain(fiber_list=fiber_list, - cell_list=cell_list, - out_list=out_list, - scale=parent.scale.value(), - sigma_tensor=sigma, - edge=parent.edge.value(), - max_dist=parent.max_dist.value(), - segmention_thres=parent.segmention_thres.value() if attr[ - "segmention_thres"] is None else - attr["segmention_thres"], - seg_gaus1=parent.seg_gaus1.value() if attr["seg_gaus1"] is None else attr[ - "seg_gaus1"], - seg_gaus2=parent.seg_gaus2.value() if attr["seg_gaus2"] is None else attr[ - "seg_gaus2"], - sigma_first_blur=parent.sigma_first_blur.value(), - angle_sections=parent.angle_sections.value(), - shell_width=shell_width, - regional_max_correction=True, - seg_iter=1, - SaveNumpy=False, - plotting=True, - dpi=100 - ) - self.progresss.setValue(index+1) - - ### plot results - # read in all creates result folder - result_folders = natsorted(glob.glob(os.path.join(output_folder, "Sigma*", "*", "results_total.xlsx"))) - - import yaml - sigmas = [] - orientation = [] - for folder in result_folders: - with (Path(folder).parent / "parameters.yml").open() as fp: - parameters = yaml.load(fp, Loader=yaml.SafeLoader)["Parameters"] - sigmas.append(parameters["sigma_tensor"][0] * parameters["scale"][0]) - orientation.append(pd.read_excel(folder)["Orientation (weighted by intensity and coherency)"]) - - self.canvas.setActive() - plt.cla() - plt.axis("auto") - - plt.plot(sigmas, orientation, "o-") - plt.ylabel("Orientation", fontsize=12) - plt.xlabel("Windowsize (μm)", fontsize=12) - plt.grid() - plt.tight_layout() - plt.savefig(os.path.join(output_folder, "Results.png"), dpi=500) - self.canvas.draw() - - dialog = SigmaRange(self) - if not dialog.exec(): - return - - def changedCheckBox(self): - for widget in [self.segmention_thres, self.seg_gaus1, self.seg_gaus2]: - widget.setDisabled(self.individual_data.value()) - if not self.individual_data.value(): - for widget in [self.segmention_thres_indi, self.seg_gaus1_indi, self.seg_gaus2_indi]: - widget.setValue("None") - - def show_files(self): - - - dialog = AddFilesDialog(self, settings) - if not dialog.exec(): - return - - import glob - import re - if dialog.mode == "new": - fiber_list_string = os.path.normpath(dialog.fiberText.value()) - cell_list_string = os.path.normpath(dialog.cellText.value()) - output_folder = os.path.normpath(dialog.outputText.value()) - elif dialog.mode == "example": - # get the date from the example referenced by name - example = get_examples_orientation()[dialog.mode_data] - fiber_list_string = str(example["input_fiber"]) - cell_list_string = str(example["input_cell"]) - output_folder = str(example["output_path"]) - print(fiber_list_string) - print(cell_list_string) - print(output_folder) - - from CompactionAnalyzer.CompactionFunctions import generate_lists - fiber_list, cell_list, out_list = generate_lists(fiber_list_string, cell_list_string, output_main=output_folder) - - import matplotlib as mpl - for fiber, cell, out in zip(fiber_list, cell_list, out_list): - self.list.addData(fiber, True, [fiber, cell, out, {"segmention_thres": None, "seg_gaus1": None, "seg_gaus2": None}], mpl.colors.to_hex(f"gray")) - - def clear_files(self): - self.list.clear() - self.data = {} - - last_cell = None - def listSelected(self): - def get_pixmap(im_cell, cmap="viridis"): - im_cell = im_cell.astype(np.float64) - im_cell -= np.min(im_cell) - im_cell /= np.max(im_cell) - im_cell = plt.get_cmap(cmap)(im_cell) - im_cell = (im_cell*255).astype(np.uint8) - - return QtGui.QPixmap(array2qimage(im_cell)) - - if len(self.list.selectedItems()): - self.sigma_tensor_button.setDisabled(False) - data = self.data[self.list.currentRow()][2] - attr = data[3] - if self.last_cell == self.list.currentRow(): - attr["segmention_thres"] = self.segmention_thres_indi.value() - attr["seg_gaus1"] = self.seg_gaus1_indi.value() - attr["seg_gaus2"] = self.seg_gaus2_indi.value() - else: - self.segmention_thres_indi.setValue(attr["segmention_thres"]) - self.seg_gaus1_indi.setValue(attr["seg_gaus1"]) - self.seg_gaus2_indi.setValue(attr["seg_gaus2"]) - #print("->", [v is None for v in attr.values()]) - if np.all([v is None for v in attr.values()]): - self.individual_data.setValue(False) - else: - self.individual_data.setValue(True) - self.last_cell = self.list.currentRow() - im_cell = imageio.v2.imread(data[1]) - from CompactionAnalyzer.CompactionFunctions import segment_cell, normalize - im_cell = normalize(im_cell, 1, 99) - - self.pixmap.setPixmap(get_pixmap(im_cell)) - self.label.setExtend(im_cell.shape[1], im_cell.shape[0]) - - im_fiber = imageio.v2.imread(data[0]) - im_fiber = normalize(im_fiber, 1, 99) - self.pixmap2.setPixmap(get_pixmap(im_fiber)) - self.label2.setExtend(im_fiber.shape[1], im_fiber.shape[0]) - - result = segment_cell(im_cell, - thres=self.segmention_thres.value() if attr["segmention_thres"] is None else attr["segmention_thres"], - seg_gaus1=self.seg_gaus1.value() if attr["seg_gaus1"] is None else attr["seg_gaus1"], - seg_gaus2=self.seg_gaus2.value() if attr["seg_gaus2"] is None else attr["seg_gaus2"]) - mask = result["mask"] - from skimage import measure - # Find contours at a constant value of 0.8 - contours = measure.find_contours(mask, 0.5) - - path = QtGui.QPainterPath() - for c in contours: - path.moveTo(c[0][1], c[0][0]) - for cc in c: - path.lineTo(cc[1], cc[0]) - self.contour.setPath(path) - self.contour2.setPath(path) - - self.label_text.setText(data[2]) - - self.link_views() - - - def link_views(self): - - def changes1(*args): - self.label2.setOriginScale(self.label.getOriginScale() * self.label.view_rect[0] / self.label2.view_rect[0]) - start_x, start_y, end_x, end_y = self.label.GetExtend() - center_x, center_y = start_x + (end_x - start_x) / 2, start_y + (end_y - start_y) / 2 - center_x = center_x / self.label.view_rect[0] * self.label2.view_rect[0] - center_y = center_y / self.label.view_rect[1] * self.label2.view_rect[1] - self.label2.centerOn(center_x, center_y) - - def zoomEvent(scale, pos): - changes1() - - self.label.zoomEvent = zoomEvent - self.label.panEvent = changes1 - - def changes2(*args): - self.label.setOriginScale(self.label2.getOriginScale() * self.label2.view_rect[0] / self.label.view_rect[0]) - start_x, start_y, end_x, end_y = self.label2.GetExtend() - center_x, center_y = start_x + (end_x - start_x) / 2, start_y + (end_y - start_y) / 2 - center_x = center_x / self.label2.view_rect[0] * self.label.view_rect[0] - center_y = center_y / self.label2.view_rect[1] * self.label.view_rect[1] - self.label.centerOn(center_x, center_y) - - def zoomEvent(scale, pos): - changes2() - - self.label2.zoomEvent = zoomEvent - self.label2.panEvent = changes2 - changes2() - - def run(self): - if self.thread is None: - self.thread = threading.Thread(target=self.run_thread, daemon=True) - self.thread.start() - self.setState(True) - else: - kill_thread(self.thread) - self.thread = None - self.setState(False) - - def setState(self, running): - if running: - self.button_run.setText("stop") - for widget in self.input_list: - widget.setDisabled(True) - else: - self.button_run.setText("run") - for widget in self.input_list: - widget.setDisabled(False) - - def finished(self): - self.thread = None - self.setState(False) - - def progress_callback(self, i, n, ii, nn): - self.progress1.setRange(0, n) - self.progress1.setValue(i) - #self.progress2.setRange(0, nn-1) - #self.progress2.setValue(ii) - self.list.setCurrentRow(i) - - def measurement_evaluated(self, index, state): - if state == 1: - self.list.item(index).setIcon(qta.icon("fa5s.check", options=[dict(color="darkgreen")])) - elif state == -1: - self.list.item(index).setIcon(qta.icon("fa5s.times", options=[dict(color="red")])) - else: - self.list.item(index).setIcon(qta.icon("fa5.circle", options=[dict(color="white")])) - - def run_thread(self): - try: - print("compute orientations") - n = len([1 for d in self.data if d[1]]) - counter = 0 - self.progress_signal.emit(0, n, 0, 1) - for i in range(n): - try: - if not self.data[i][1]: - continue - - fiber, cell, output, attr = self.data[i][2] - - sigma_tensor = self.sigma_tensor.value() - if self.sigma_tensor_type.value() == "um": - sigma_tensor /= self.scale.value() - shell_width = self.shell_width.value() - if self.shell_width_type.value() == "um": - shell_width /= self.scale.value() - - from CompactionAnalyzer.CompactionFunctions import StuctureAnalysisMain - StuctureAnalysisMain(fiber_list=[fiber], - cell_list=[cell], - out_list=[output], - scale=self.scale.value(), - sigma_tensor=sigma_tensor, - edge=self.edge.value(), - max_dist=self.max_dist.value(), - segmention_thres=self.segmention_thres.value() if attr["segmention_thres"] is None else attr["segmention_thres"], - seg_gaus1=self.seg_gaus1.value() if attr["seg_gaus1"] is None else attr["seg_gaus1"], - seg_gaus2=self.seg_gaus2.value() if attr["seg_gaus2"] is None else attr["seg_gaus2"], - sigma_first_blur=self.sigma_first_blur.value(), - angle_sections=self.angle_sections.value(), - shell_width=shell_width, - ) - - self.measurement_evaluated_signal.emit(i, 1) - except Exception as err: - import traceback - traceback.print_exc() - self.measurement_evaluated_signal.emit(i, -1) - counter += 1 - self.progress_signal.emit(counter, n, 0, 1) - finally: - self.finished_signal.emit() - - - - -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) - - self.setMinimumWidth(800) - self.setMinimumHeight(400) - self.setWindowTitle("Saenopy Evaluation") - - self.images = [] - self.data_folders = [] - self.current_plot_func = lambda: None - - with QtShortCuts.QHBoxLayout(self) 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) - - with QtShortCuts.QGroupBox(main_layout, "Plot Forces") as (_, layout): - self.type = QtShortCuts.QInputChoice(None, "type", "global orientation", ["global orientation", "normalized intensity (first shell)", "orientation over distance", "normed intensity over distance"]) - self.type.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 Plot", self.run2) - self.button_run2 = QtShortCuts.QPushButton(layout2, "Grouped Plot", self.plot_groups) - self.plot_buttons = [self.button_run, self.button_run2] - for button in self.plot_buttons: - button.setCheckable(True) - - self.list.setData(self.data_folders) - self.addGroup() - self.current_plot_func = self.run2 - - 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 addFiles(self): - 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 + "/**/results_total.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_addList0 = QtShortCuts.QPushButton(None, "cancel", self.reject) - self.button_addList1 = QtShortCuts.QPushButton(None, "ok", self.accept) - - dialog = AddFilesDialog(self) - if not dialog.exec(): - return - - text = os.path.normpath(dialog.inputText.value()) - files = glob.glob(text, recursive=True) - - 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 - if self.list2.data is current_group: - data = {"results_total": pd.read_excel(file), - "results_distance": pd.read_excel(Path(file).parent / "results_distance.xlsx"), - "image": Path(file).parent / "Figures" / "overlay2.png"} - self.list2.addData(file, True, data) - - 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, key, only_first_line=False): - results = [] - for name, checked, files, color in self.data_folders: - if checked != 0: - for name2, checked2, res, color in files: - if checked2 != 0: - res[key]["group"] = name - if only_first_line is True: - results.append(res[key].iloc[0:1]) - else: - results.append(res[key]) - res = pd.concat(results) - res.to_csv("tmp_pandas.csv") - return res - - def replot(self): - if self.current_plot_func is not None: - self.current_plot_func() - - 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 - - self.button_export.setDisabled(False) - - self.canvas.setActive() - plt.cla() - plt.axis("auto") - if self.type.value() == "global orientation": - res = self.getAllCurrentPandasData("results_total") - code_data = [res, ["group", 'Orientation (weighted by intensity and coherency)']] - def plot(res, color_dict2): - # define the colors - color_dict = color_dict2 - - # iterate over the groups - for name, d in res.groupby("group")['Orientation (weighted by intensity and coherency)']: - plt.bar(name, d.mean(), yerr=d.sem(), color=color_dict[name]) - - # add ticks and labels - plt.ylabel("orientation") - # 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() - - elif self.type.value() == "normalized intensity (first shell)": - - res = self.getAllCurrentPandasData("results_distance", only_first_line=True) - code_data = [res, ["group", 'Intensity Norm (individual)']] - - def plot(res, color_dict2): - # define the colors - color_dict = color_dict2 - - # iterate over the groups - for name, d in res.groupby("group")['Intensity Norm (individual)']: - plt.bar(name, d.mean(), yerr=d.sem(), color=color_dict[name]) - - # add ticks and labels - plt.ylabel("normalized intensity") - # 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() - - elif self.type.value() == "orientation over distance": - res = self.getAllCurrentPandasData("results_distance") - code_data = [res, ["group", "Shell_mid (µm)", "Orientation (individual)"]] - - def plot(res, color_dict2): - # define the colors - color_dict = color_dict2 - - # iterate over the groups - for name, d in res.groupby("group"): - d = d.groupby("Shell_mid (µm)")["Orientation (individual)"].agg(["mean", "sem"]) - plt.plot(d.index,d["mean"], color=color_dict[name]) - plt.fill_between(d.index, d["mean"]-d["sem"], d["mean"]+d["sem"], color=color_dict[name], alpha=0.5) - - # add ticks and labels - plt.xlabel("shell mid (µm)") - plt.ylabel("individual orientation") - # 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() - - elif self.type.value() == "normed intensity over distance": - res = self.getAllCurrentPandasData("results_distance") - code_data = [res, ["group", "Shell_mid (µm)", 'Intensity Norm (individual)']] - - def plot(res, color_dict2): - # define the colors - color_dict = color_dict2 - - # iterate over the groups - for name, d in res.groupby("group"): - d = d.groupby("Shell_mid (µm)")['Intensity Norm (individual)'].agg(["mean", "sem"]) - plt.plot(d.index, d["mean"], color=color_dict[name]) - plt.fill_between(d.index, d["mean"] - d["sem"], d["mean"] + d["sem"], color=color_dict[name], alpha=0.5) - - # add ticks and labels - plt.xlabel("shell mid (µm)") - plt.ylabel("normalized intensity") - # 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() - - self.canvas.setActive() - plt.cla() - - color_dict = {d[0]: d[3] for d in self.data_folders} - - code = execute(plot, code_data[0][code_data[1]], 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 - - self.button_export.setDisabled(True) - - data = self.list2.data[self.list2.currentRow()][2] - im = imageio.v2.imread(data["image"]) - - plot_color = self.list.data[self.list.currentRow()][3] - - self.canvas.setActive() - plt.cla() - plt.axis("auto") - if self.type.value() == "global orientation": - plt.imshow(im) - plt.title(f"Orientation {data['results_total']['Orientation (weighted by intensity and coherency)'].iloc[0]:.3f}") - plt.axis("off") - elif self.type.value() == "normalized intensity (first shell)": - plt.imshow(im) - plt.title(f"Normalized Intensity {data['results_distance']['Intensity Norm (individual)'].iloc[0]:.3f}") - plt.axis("off") - elif self.type.value() == "normed intensity over distance": - plt.plot(data["results_distance"]["Shell_mid (µm)"], - data["results_distance"]["Intensity Norm (individual)"], color=plot_color) - plt.xlabel("shell mid (µm)") - plt.ylabel("intensity norm") - elif self.type.value() == "orientation over distance": - plt.plot(data["results_distance"]["Shell_mid (µm)"], - data["results_distance"]["Orientation (individual)"], color=plot_color) - plt.xlabel("shell mid (µm)") - plt.ylabel("individual orientation") - # despine the axes - plt.gca().spines["top"].set_visible(False) - plt.gca().spines["right"].set_visible(False) - plt.tight_layout() - self.canvas.draw() - - def export(self): - 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")) - - if __name__ == '__main__': app = QtWidgets.QApplication(sys.argv) print(sys.argv) diff --git a/saenopy/gui/orientation/modules/AddFilesDialog.py b/saenopy/gui/orientation/modules/AddFilesDialog.py index ea1b106..ac1f045 100644 --- a/saenopy/gui/orientation/modules/AddFilesDialog.py +++ b/saenopy/gui/orientation/modules/AddFilesDialog.py @@ -1,24 +1,8 @@ -import sys - # Setting the Qt bindings for QtPy import os -import pandas as pd -import qtawesome as qta from qtpy import QtCore, QtWidgets, QtGui -import numpy as np -from saenopy.gui.common import QtShortCuts, QExtendedGraphicsView -from saenopy.gui.common.gui_classes import Spoiler, CheckAbleGroup, QHLine, QVLine, MatplotlibWidget, NavigationToolbar, execute, kill_thread, ListWidget -import imageio -from qimage2ndarray import array2qimage -import matplotlib.pyplot as plt -import glob -import imageio -import threading -import glob -import re -from pathlib import Path -import saenopy +from saenopy.gui.common import QtShortCuts from saenopy.examples import get_examples_orientation from saenopy.gui.common.AddFilesDialog import AddFilesDialog diff --git a/saenopy/gui/orientation/modules/BatchEvaluate.py b/saenopy/gui/orientation/modules/BatchEvaluate.py new file mode 100644 index 0000000..621f6de --- /dev/null +++ b/saenopy/gui/orientation/modules/BatchEvaluate.py @@ -0,0 +1,108 @@ +# Setting the Qt bindings for QtPy +import os +from qtpy import QtCore, QtWidgets, QtGui +from saenopy.gui.common import QtShortCuts +import traceback + +from saenopy.examples import get_examples_orientation +from .AddFilesDialog import AddFilesDialog +from saenopy.gui.common.AddFilesDialog import FileExistsDialog +from .result import ResultOrientation, get_orientation_files +from .Detection import DeformationDetector +from saenopy.gui.common.BatchEvaluateBase import BatchEvaluateBase + +settings = QtCore.QSettings("FabryLab", "CompactionAnalyzer") + + +class BatchEvaluate(BatchEvaluateBase): + settings_key = "Seanopy_deformation" + file_extension = ".saenopyDeformation" + result: ResultOrientation = None + + result_params = ["piv_parameters", "force_parameters"] + + def add_modules(self): + layout0 = QtShortCuts.currentLayout() + self.sub_module_deformation = DeformationDetector(self, layout0) + self.modules = [self.sub_module_deformation] + + def add_measurement(self): + last_decision = None + def do_overwrite(filename): + nonlocal last_decision + + # if we are in demo mode always load the files + if os.environ.get("DEMO") == "true": # pragma: no cover + return "read" + + # if there is a last decistion stored use that + if last_decision is not None: + return last_decision + + # ask the user if they want to overwrite or read the existing file + dialog = FileExistsDialog(self, filename) + result = dialog.exec() + # if the user clicked cancel + if not result: + return 0 + # if the user wants to remember the last decision + if dialog.use_for_all.value(): + last_decision = dialog.mode + # return the decision + return dialog.mode + + # getStack + dialog = AddFilesDialog(self, self.settings) + if not dialog.exec(): + return + + # create a new measurement object + if dialog.mode == "new": + # if there was a bf stack selected + fiberText = dialog.fiberText.value() + + # the active selected stack + cellText = dialog.cellText.value() + + try: + results = get_orientation_files(dialog.outputText.value(), + fiberText, cellText, pixel_size=dialog.pixel_size.value(), + exist_overwrite_callback=do_overwrite, + ) + except Exception as err: + # notify the user if errors occured + QtWidgets.QMessageBox.critical(self, "Load Stacks", str(err)) + traceback.print_exc() + else: + # add the loaded measruement objects + for data in results: + self.add_data(data) + + # load existing files + elif dialog.mode == "existing": + self.load_from_path(dialog.outputText3.value()) + + # load from the examples database + elif dialog.mode == "example": + # get the date from the example referenced by name + example = get_examples_orientation()[dialog.mode_data] + + # generate a stack with the examples data + results = get_orientation_files( + fiber_list_string=str(example["input_fiber"]), + cell_list_string = str(example["input_cell"]), + output_path = str(example["output_path"]), + pixel_size = example["pixel_size"], + exist_overwrite_callback=do_overwrite, + ) + # load all the measurement objects + for data in results: + #if getattr(data, "is_read", False) is False: + # data.piv_parameters = example["piv_parameters"] + # data.force_parameters = example["force_parameters"] + self.add_data(data) + elif dialog.mode == "example_evaluated": + self.load_from_path(dialog.examples_output) + + # update the icons + self.update_icons() diff --git a/saenopy/gui/orientation/modules/Detection.py b/saenopy/gui/orientation/modules/Detection.py new file mode 100644 index 0000000..f435b71 --- /dev/null +++ b/saenopy/gui/orientation/modules/Detection.py @@ -0,0 +1,489 @@ +import numpy as np +from typing import Tuple +import matplotlib.pyplot as plt +import jointforces as jf +from pathlib import Path + +from saenopy.gui.orientation.modules.result import ResultOrientation +from qtpy import QtCore, QtWidgets, QtGui +from qimage2ndarray import array2qimage +from saenopy.gui.common import QtShortCuts, QExtendedGraphicsView + +from saenopy.gui.common.gui_classes import QHLine, CheckAbleGroup +from saenopy.gui.common.PipelineModule import PipelineModule +from saenopy.gui.common.QTimeSlider import QTimeSlider +from saenopy.gui.common.resources import resource_icon +from saenopy.gui.common.code_export import get_code +from saenopy.gui.common.ModuleScaleBar import ModuleScaleBar +from saenopy.gui.common.ModuleColorBar import ModuleColorBar + + +class DeformationDetector(PipelineModule): + pipeline_name = "find deformations" + use_thread = False + progress_signal = QtCore.Signal(int, str) + result: ResultOrientation = None + + def __init__(self, parent: "BatchEvaluate", layout): + super().__init__(parent, layout) + + with QtShortCuts.QVBoxLayout(self) as layout: + layout.setContentsMargins(0, 0, 0, 0) + with CheckAbleGroup(self, "find oriengations").addToLayout() as self.group: + with QtShortCuts.QVBoxLayout() as layout: + with QtShortCuts.QHBoxLayout(): + self.scale = QtShortCuts.QInputString(None, "scale", "1.0", type=float, settings=self.settings, + settings_key="orientation/scale") + QtWidgets.QLabel("um/px").addToLayout() + with QtShortCuts.QHBoxLayout(): + self.sigma_tensor = QtShortCuts.QInputString(None, "sigma_tensor", "7.0", type=float, + settings=self.settings, + settings_key="orientation/sigma_tensor") + self.sigma_tensor_type = QtShortCuts.QInputChoice(None, "", "um", ["um", "pixel"], + settings=self.settings, + settings_key="orientation/sigma_tensor_unit") + self.sigma_tensor_button = QtShortCuts.QPushButton(None, "detect", + self.sigma_tensor_button_clicked) + self.sigma_tensor_button.setDisabled(True) + with QtShortCuts.QHBoxLayout(): + self.edge = QtShortCuts.QInputString(None, "edge", "40", type=int, settings=self.settings, + settings_key="orientation/edge", + tooltip="How many pixels to cut at the edge of the image.") + QtWidgets.QLabel("px").addToLayout() + self.max_dist = QtShortCuts.QInputString(None, "max_dist", "None", type=int, + settings=self.settings, + settings_key="orientation/max_dist", + tooltip="Optional: specify the maximal distance around the cell center", + none_value=None) + QtWidgets.QLabel("px").addToLayout() + + with QtShortCuts.QHBoxLayout(): + self.sigma_first_blur = QtShortCuts.QInputString(None, "sigma_first_blur", "0.5", + type=float, settings=self.settings, + settings_key="orientation/sigma_first_blur") + QtWidgets.QLabel("px").addToLayout() + with QtShortCuts.QHBoxLayout(): + self.angle_sections = QtShortCuts.QInputString(None, "angle_sections", "5", type=int, + settings=self.settings, + settings_key="orientation/angle_sections") + QtWidgets.QLabel("deg").addToLayout() + + with QtShortCuts.QHBoxLayout(): + self.shell_width = QtShortCuts.QInputString(None, "shell_width", "5", type=float, + settings=self.settings, + settings_key="orientation/shell_width") + self.shell_width_type = QtShortCuts.QInputChoice(None, "", "um", ["um", "pixel"], + settings=self.settings, + settings_key="orientation/shell_width_type") + + with QtShortCuts.QGroupBox(None, "Segmentation Parameters"): + self.segmention_thres = QtShortCuts.QInputString(None, "segmention_thresh", "1.0", + type=float, + settings=self.settings, + settings_key="orientation/segmention_thres") + #self.segmention_thres.valueChanged.connect(self.listSelected) + with QtShortCuts.QHBoxLayout(): + self.seg_gaus1 = QtShortCuts.QInputString(None, "seg_gauss1", "0.5", type=float, + settings=self.settings, + settings_key="orientation/seg_gaus1") + #self.seg_gaus1.valueChanged.connect(self.listSelected) + self.seg_gaus2 = QtShortCuts.QInputString(None, "seg_gauss2", "100", type=float, + settings=self.settings, + settings_key="orientation/seg_gaus2") + #self.seg_gaus2.valueChanged.connect(self.listSelected) + + with CheckAbleGroup(self, "individual segmentation").addToLayout() as self.individual_data: + with QtShortCuts.QVBoxLayout() as layout2: + self.segmention_thres_indi = QtShortCuts.QInputString(None, "segmention_thresh", + None, type=float, + allow_none=True) + #self.segmention_thres_indi.valueChanged.connect(self.listSelected) + with QtShortCuts.QHBoxLayout(): + self.seg_gaus1_indi = QtShortCuts.QInputString(None, "seg_gauss1", None, + type=float, allow_none=True) + #self.seg_gaus1_indi.valueChanged.connect(self.listSelected) + self.seg_gaus2_indi = QtShortCuts.QInputString(None, "seg_gauss2", None, + type=float, allow_none=True) + #self.seg_gaus2_indi.valueChanged.connect(self.listSelected) + + self.input_button = QtShortCuts.QPushButton(None, "detect deformations", self.start_process) + + with self.parent.tabs.createTab("PIV Deformations") as self.tab: + with QtShortCuts.QVBoxLayout() as layout: + #self.label_tab = QtWidgets.QLabel( + # "The deformations from the piv algorithm at every window where the crosscorrelation was evaluated.").addToLayout() + + with QtShortCuts.QHBoxLayout() as layout: + #self.plotter = QtInteractor(self, auto_update=False) # , theme=pv.themes.DocumentTheme()) + #self.tab.parent().plotter = self.plotter + #self.plotter.set_background("black") + #layout.addWidget(self.plotter.interactor) + self.label = QExtendedGraphicsView.QExtendedGraphicsView().addToLayout() + self.scale1 = ModuleScaleBar(self, self.label) + self.color1 = ModuleColorBar(self, self.label) + #self.label.setMinimumWidth(300) + self.pixmap = QtWidgets.QGraphicsPixmapItem(self.label.origin) + self.contour = QtWidgets.QGraphicsPathItem(self.label.origin) + pen = QtGui.QPen(QtGui.QColor(255, 0, 0)) + pen.setCosmetic(True) + pen.setWidth(2) + self.contour.setPen(pen) + + #self.z_slider = QTimeSlider("z", self.z_slider_value_changed, "set z position", + # QtCore.Qt.Vertical).addToLayout() + #self.z_slider.t_slider.valueChanged.connect( + # lambda value: parent.shared_properties.change_property("z_slider", value, self)) + #parent.shared_properties.add_property("z_slider", self) + + #self.vtk_toolbar = VTK_Toolbar(self.plotter, self.update_display, "deformation", z_slider=self.z_slider, shared_properties=self.parent.shared_properties).addToLayout() + with QtShortCuts.QHBoxLayout() as layout0: + layout0.setContentsMargins(0, 0, 0, 0) + + self.auto_scale = QtShortCuts.QInputBool(None, "", icon=[ + resource_icon("autoscale0.ico"), + resource_icon("autoscale1.ico"), + ], group=False, tooltip="Automatically choose the maximum for the color scale.") + # self.auto_scale = QtShortCuts.QInputBool(None, "auto color", True, tooltip="Automatically choose the maximum for the color scale.") + self.auto_scale.setValue(True) + self.auto_scale.valueChanged.connect(self.scale_max_changed) + self.scale_max = QtShortCuts.QInputString(None, "", 10,# if self.is_force_plot else 10, + type=float, + tooltip="Set the maximum of the color scale.") + self.scale_max.valueChanged.connect(self.scale_max_changed) + self.scale_max.setDisabled(self.auto_scale.value()) + + self.window_scale = QtWidgets.QWidget() + self.window_scale.setWindowTitle("Saenopy - Arrow Scale") + with QtShortCuts.QVBoxLayout(self.window_scale): + self.arrow_scale = QtShortCuts.QInputNumber(None, "arrow scale", 1, 0.01, 100, use_slider=True, + log_slider=True) + self.arrow_scale.valueChanged.connect(self.update_display) + #self.arrow_scale.valueChanged.connect( + # lambda value: shared_properties.change_property("arrow_scale" + addition, value, self)) + #shared_properties.add_property("arrow_scale" + addition, self) + + QtWidgets.QLabel("Colormap for arrows").addToLayout() + self.colormap_chooser = QtShortCuts.QDragableColor("turbo").addToLayout() + self.colormap_chooser.valueChanged.connect(self.update_display) + + #self.colormap_chooser.valueChanged.connect( + # lambda value: shared_properties.change_property("colormap_chooser" + addition, value, self)) + + QtWidgets.QLabel("Colormap for image").addToLayout() + self.colormap_chooser2 = QtShortCuts.QDragableColor("gray").addToLayout() + self.colormap_chooser2.valueChanged.connect(self.update_display) + + #self.colormap_chooser2.valueChanged.connect( + # lambda value: shared_properties.change_property("colormap_chooser2" + addition, value, + # self)) + #shared_properties.add_property("colormap_chooser2" + addition, self) + self.button_arrow_scale = QtShortCuts.QPushButton(None, "", lambda x: self.window_scale.show()) + self.button_arrow_scale.setIcon(resource_icon("arrowscale.ico")) + + self.show_seg = QtShortCuts.QInputBool(None, "seg", value=True).addToLayout() + self.show_seg.valueChanged.connect(self.update_display) + + self.t_slider = QTimeSlider(connected=self.update_display).addToLayout() + self.tab.parent().t_slider = self.t_slider + + self.setParameterMapping("piv_parameters", { + "sigma_tensor": self.sigma_tensor, + "sigma_tensor_type": self.sigma_tensor_type, + "edge": self.edge, + "max_dist": self.max_dist, + "sigma_first_blur": self.sigma_first_blur, + "angle_sections": self.angle_sections, + "shell_width": self.shell_width, + "shell_width_type": self.shell_width_type, + "segmention_thres": self.segmention_thres, + "seg_gaus1": self.seg_gaus1, + "seg_gaus2": self.seg_gaus2, + }) + + self.progress_signal.connect(self.progress_callback) + + def scale_max_changed(self): + self.scale_max.setDisabled(self.auto_scale.value()) + scalebar_max = self.getScaleMax() + #print(scalebar_max, self.plotter.auto_value, type(self.plotter.auto_value)) + #if scalebar_max is None: + # self.plotter.update_scalar_bar_range([0, self.plotter.auto_value]) + #else: + # self.plotter.update_scalar_bar_range([0, scalebar_max]) + self.update_display() + + def getScaleMax(self): + if self.auto_scale.value(): + return None + return self.scale_max.value() + + def check_available(self, result: ResultOrientation) -> bool: + return True + + def check_evaluated(self, result: ResultOrientation) -> bool: + return True + + def setResult(self, result: ResultOrientation): + super().setResult(result) + self.update_display() + + def update_display(self, *, plotter=None): + if self.result is None: + return + #if self.current_tab_selected is False: + # return + + im = self.result.get_image(0) + print(im.dtype, im.max()) + self.pixmap.setPixmap(QtGui.QPixmap(array2qimage(im))) + self.label.setExtend(im.shape[1], im.shape[0]) + return None + + import imageio + t = self.t_slider.value() + im = self.result.get_image_data(t).astype(float) + #im = imageio.v2.imread(self.result.images[t]).astype(float) + im0 = im.copy() + im = im - im.min() + im = (im / im.max() * 255).astype(np.uint8) + im = np.squeeze(im) + + colormap2 = self.colormap_chooser2.value() + #print(im.shape) + if len(im.shape) == 2 and colormap2 is not None and colormap2 != "gray": + cmap = plt.get_cmap(colormap2) + im = (cmap(im)[:, :, :3] * 255).astype(np.uint8) + #print(im.shape, im.dtype, im[100, 100]) + + from saenopy.gui.solver.modules.exporter.ExporterRender2D import render_2d_arrows + im_scale = 1 + aa_scale = 1 + display_image = [im, [1, 1], 0] + from PIL import Image + pil_image = Image.fromarray(im).convert("RGB") + pil_image = pil_image.resize( + [int(pil_image.width * im_scale * aa_scale), int(pil_image.height * im_scale * aa_scale)]) + #print(self.auto_scale.value(), self.getScaleMax()) + pil_image, disp_params = render_2d_arrows({ + 'arrows': 'deformation', + 'deformation_arrows': { + "autoscale": self.auto_scale.value(), + "scale_max": self.getScaleMax(), + "colormap": self.colormap_chooser.value(), + "skip": 1, + "arrow_opacity": 1, + "arrow_scale": 10*self.arrow_scale.value(), + }, + "time": {"t": t}, + '2D_arrows': {'width': 2.0, 'headlength': 5.0, 'headheight': 5.0}, + }, self.result, pil_image, im_scale, aa_scale, display_image, return_scale=True) + + im = np.asarray(pil_image) + self.pixmap.setPixmap(QtGui.QPixmap(array2qimage(im))) + self.label.setExtend(im.shape[1], im.shape[0]) + self.scale1.setScale([self.result.pixel_size]) + self.color1.setScale(0, disp_params["scale_max"] if disp_params else None, self.colormap_chooser.value()) + + if self.show_seg.value(): + thresh_segmentation = self.thresh_segmentation.value() + if not self.continuous_segmentation.value(): + im0 = imageio.v2.imread(self.result.get_image_data(0, return_filename=True)).astype(float) + seg0 = jf.piv.segment_spheroid(im0, True, thresh_segmentation) + from skimage import measure + # Find contours at a constant value of 0.8 + contours = measure.find_contours(seg0["mask"], 0.5) + + path = QtGui.QPainterPath() + for c in contours: + path.moveTo(c[0][1], im.shape[0] - c[0][0]) + for cc in c: + path.lineTo(cc[1], im.shape[0] - cc[0]) + self.contour.setPath(path) + + def process(self, result: ResultOrientation, piv_parameters: dict): + sigma_tensor = piv_parameters["sigma_tensor"] + if piv_parameters["sigma_tensor_type"] == "um": + sigma_tensor /= result.pixel_size + shell_width = piv_parameters["shell_width"] + if piv_parameters["shell_width_type"] == "um": + shell_width /= result.pixel_size + + from CompactionAnalyzer.CompactionFunctions import StuctureAnalysisMain + StuctureAnalysisMain(fiber_list=[result.image_fiber], + cell_list=[result.image_cell], + out_list=[result.output[:-len(".saenopyOrientation")]], + scale=result.pixel_size, + sigma_tensor=sigma_tensor, + edge=piv_parameters["edge"], + max_dist=piv_parameters["max_dist"], + segmention_thres=piv_parameters["segmention_thres"], + seg_gaus1=piv_parameters["seg_gaus1"], + seg_gaus2=piv_parameters["seg_gaus2"], + sigma_first_blur=piv_parameters["sigma_first_blur"], + angle_sections=piv_parameters["angle_sections"], + shell_width=shell_width, + ) + + result.save() + + def get_code(self) -> Tuple[str, str]: + import_code = "from saenopy.gui.spheroid.modules.result import get_stacks_spheroid\nimport jointforces as jf\n" + + def code(filename, output1, pixel_size1, result_file, piv_parameters1): # pragma: no cover + # load the relaxed and the contracted stack + # {t} is the placeholder for the time points + # use * to load multiple stacks for batch processing + # load_existing=True allows to load an existing file of these stacks if it already exists + results = get_stacks_spheroid( + filename, + output_path=output1, + pixel_size=pixel_size1, + load_existing=True) + # or if you want to explicitly load existing results files + # use * to load multiple result files for batch processing + # results = saenopy.load_results(result_file) + + for result in results: + result.piv_parameters = piv_parameters1 + jf.piv.compute_displacement_series_gui(result, + n_max=result.piv_parameters["n_max"], + n_min=result.piv_parameters["n_min"], + continous_segmentation=result.piv_parameters["continuous_segmentation"], + thres_segmentation=result.piv_parameters["thresh_segmentation"], + window_size=result.piv_parameters["window_size"], + ) + result.save() + + data = dict( + filename=self.result.get_absolute_path(), + output1=str(Path(self.result.output).parent), + pixel_size1=self.result.pixel_size, + result_file=str(self.result.output), + piv_parameters1=self.result.piv_parameters, + ) + + code = get_code(code, data) + return import_code, code + + def sigma_tensor_button_clicked(self): + parent = self + + class SigmaRange(QtWidgets.QDialog): + def __init__(self, parent): + super().__init__(parent) + self.setWindowTitle("Determine Sigma Tensor") + with QtShortCuts.QVBoxLayout(self) as layout: + self.output_folder = QtShortCuts.QInputFolder(None, "output folder", settings=settings, + settings_key="orientation/sigma_tensor_range_output") + self.label_scale = QtWidgets.QLabel(f"Scale is {parent.scale.value()} px/um").addToLayout(layout) + with QtShortCuts.QHBoxLayout() as layout2: + self.sigma_tensor_min = QtShortCuts.QInputString(None, "min", "1.0", type=float, + settings=settings, + settings_key="orientation/sigma_tensor_min") + self.sigma_tensor_max = QtShortCuts.QInputString(None, "max", "15", type=float, + settings=settings, + settings_key="orientation/sigma_tensor_max") + self.sigma_tensor_step = QtShortCuts.QInputString(None, "step", "1", type=float, + settings=settings, + settings_key="orientation/sigma_tensor_step") + self.sigma_tensor_type = QtShortCuts.QInputChoice(None, "", "um", ["um", "pixel"], + settings=settings, + settings_key="orientation/sigma_tensor_unit") + + self.progresss = QtWidgets.QProgressBar().addToLayout(layout) + + self.canvas = MatplotlibWidget(self) + layout.addWidget(self.canvas) + layout.addWidget(NavigationToolbar(self.canvas, self)) + + with QtShortCuts.QHBoxLayout() as layout3: + # self.button_clear = QtShortCuts.QPushButton(None, "clear list", self.clear_files) + layout3.addStretch() + self.button_addList2 = QtShortCuts.QPushButton(None, "cancel", self.reject) + self.button_addList1 = QtShortCuts.QPushButton(None, "run", self.run) + + def run(self): + from natsort import natsorted + fiber, cell, output, attr = parent.data[parent.list.currentRow()][2] + + output_folder = self.output_folder.value() + + sigma_tensor_min = self.sigma_tensor_min.value() + sigma_tensor_max = self.sigma_tensor_max.value() + sigma_tensor_step = self.sigma_tensor_step.value() + if self.sigma_tensor_type.value() == "um": + sigma_tensor_min /= parent.scale.value() + sigma_tensor_max /= parent.scale.value() + sigma_tensor_step /= parent.scale.value() + shell_width = parent.shell_width.value() + if parent.shell_width_type.value() == "um": + shell_width /= parent.scale.value() + + sigma_list = np.arange(sigma_tensor_min, sigma_tensor_max + sigma_tensor_step, sigma_tensor_step) + self.progresss.setRange(0, len(sigma_list)) + + from CompactionAnalyzer.CompactionFunctions import StuctureAnalysisMain, generate_lists + for index, sigma in enumerate(sigma_list): + sigma = float(sigma) + self.progresss.setValue(index) + app.processEvents() + # Create outputfolder + output_sub = os.path.join(output_folder, + rf"Sigma{str(sigma * parent.scale.value()).zfill(3)}") # subpath to store results + fiber_list, cell_list, out_list = generate_lists(fiber, cell, + output_main=output_sub) + + StuctureAnalysisMain(fiber_list=fiber_list, + cell_list=cell_list, + out_list=out_list, + scale=parent.scale.value(), + sigma_tensor=sigma, + edge=parent.edge.value(), + max_dist=parent.max_dist.value(), + segmention_thres=parent.segmention_thres.value() if attr[ + "segmention_thres"] is None else + attr["segmention_thres"], + seg_gaus1=parent.seg_gaus1.value() if attr["seg_gaus1"] is None else attr[ + "seg_gaus1"], + seg_gaus2=parent.seg_gaus2.value() if attr["seg_gaus2"] is None else attr[ + "seg_gaus2"], + sigma_first_blur=parent.sigma_first_blur.value(), + angle_sections=parent.angle_sections.value(), + shell_width=shell_width, + regional_max_correction=True, + seg_iter=1, + SaveNumpy=False, + plotting=True, + dpi=100 + ) + self.progresss.setValue(index + 1) + + ### plot results + # read in all creates result folder + result_folders = natsorted( + glob.glob(os.path.join(output_folder, "Sigma*", "*", "results_total.xlsx"))) + + import yaml + sigmas = [] + orientation = [] + for folder in result_folders: + with (Path(folder).parent / "parameters.yml").open() as fp: + parameters = yaml.load(fp, Loader=yaml.SafeLoader)["Parameters"] + sigmas.append(parameters["sigma_tensor"][0] * parameters["scale"][0]) + orientation.append(pd.read_excel(folder)["Orientation (weighted by intensity and coherency)"]) + + self.canvas.setActive() + plt.cla() + plt.axis("auto") + + plt.plot(sigmas, orientation, "o-") + plt.ylabel("Orientation", fontsize=12) + plt.xlabel("Windowsize (μm)", fontsize=12) + plt.grid() + plt.tight_layout() + plt.savefig(os.path.join(output_folder, "Results.png"), dpi=500) + self.canvas.draw() + + dialog = SigmaRange(self) + if not dialog.exec(): + return \ No newline at end of file diff --git a/saenopy/gui/orientation/modules/result.py b/saenopy/gui/orientation/modules/result.py new file mode 100644 index 0000000..81a9b88 --- /dev/null +++ b/saenopy/gui/orientation/modules/result.py @@ -0,0 +1,203 @@ +import numpy as np +import io +from typing import List, TypedDict, Tuple +import traceback +from natsort import natsorted +import re + +from tifffile import imread +import matplotlib.pyplot as plt +from pathlib import Path +import glob +import os + +from saenopy.gui.tfm2d.modules.result import read_tiff +from saenopy.saveable import Saveable +from saenopy.result_file import make_path_absolute, make_path_relative + + + +class ResultOrientation(Saveable): + __save_parameters__ = ['image_cell', 'image_fiber', 'pixel_size', 'output', + '___save_name__', '___save_version__'] + ___save_name__ = "ResultOrientation" + ___save_version__ = "1.0" + + image_cell: str = None + image_fiber: str = None + output: str = None + pixel_size: float = None + + piv_parameters: dict = {} + + state: bool = False + + shape: Tuple[int, int] = None + + def __init__(self, output, image_cell, image_fiber, pixel_size, **kwargs): + self.image_cell = image_cell + self.image_fiber = image_fiber + self.pixel_size = pixel_size + self.output = output + + super().__init__(**kwargs) + + def save(self, file_name=None): + if file_name is None: + file_name = self.output + Path(self.output).parent.mkdir(exist_ok=True, parents=True) + super().save(file_name) + + def on_load(self, filename: str): + self.output = str(Path(filename)) + + def get_absolute_path_cell(self): + return make_path_absolute(self.image_cell, Path(self.output).parent) + + def get_absolute_path_fiber(self): + return make_path_absolute(self.image_fiber, Path(self.output).parent) + + def get_image(self, index, corrected=True): + try: + if index == 0: + im = read_tiff(self.get_absolute_path_cell()) + else: + im = read_tiff(self.get_absolute_path_fiber()) + except FileNotFoundError as err: + traceback.print_exception(err) + h = 255 + w = 255 + if self.shape is not None: + h, w = self.shape[:2] + im = np.zeros([h, w, 3], dtype=np.uint8) + im[:, :, 0] = 255 + im[:, :, 2] = 255 + if self.shape is None: + self.shape = im.shape + return im + + def get_data_structure(self): + if self.shape is None: + self.get_image(0) + return { + "dimensions": 2, + "z_slices_count": 1, + "im_shape": [self.shape[0], self.shape[1], 1], + "time_point_count": 1, + "has_reference": False, + "voxel_size": [self.pixel_size, self.pixel_size, 1], + "time_delta": None, + "channels": ["cells", "fibers"], + "fields": { + "deformation": { + "type": "vector", + "measure": "deformation", + "unit": "pixel", + "name": "displacements_measured", + }, + "forces": { + "type": "vector", + "measure": "force", + "unit": "pixel", + "name": "force", + } + } + } + + def get_image_data(self, time_point, channel="default", use_reference=False): + if channel == "cells": + im = self.get_image(0) + else: + im = self.get_image(1) + if len(im.shape) == 2: + return im[:, :, None, None] + return im[:, :, :, None] + + def get_field_data(self, name, time_point): + class Mesh2D: + pass + + return None, None + + vx = None + vy = None + vf = 1 + + if name == "deformation": + vx = self.u + vy = self.v + vf = 10 + if name == "forces": + print("do force") + vx = self.tx + vy = self.ty + vf = 0.1 + + if vx is not None: + mesh = Mesh2D() + mesh.units = "pixels" + f = self.shape[0] / vx.shape[0] + x, y = np.meshgrid(np.arange(vx.shape[1]), np.arange(vx.shape[0])) + x = x * f + y = y * f + y = self.shape[0] - y + 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 + return None, None + + +def get_orientation_files(output_path, fiber_list_string, cell_list_string, pixel_size, + exist_overwrite_callback=None, + load_existing=False): + output_base = Path(fiber_list_string).parent + while "*" in str(output_base): + output_base = Path(output_base).parent + + fiber_list_string = sorted(glob.glob(str(fiber_list_string))) + output_path = str(output_path) + cell_list_string = sorted(glob.glob(str(cell_list_string))) + + if len(fiber_list_string) == 0: + raise ValueError("no fiber image selected") + if len(cell_list_string) == 0: + raise ValueError("no cell image selected") + + if len(fiber_list_string) != len(cell_list_string): + raise ValueError(f"the number of fiber images ({len(fiber_list_string)}) does not match the number of cell images {len(cell_list_string)}") + + results = [] + for i in range(len(fiber_list_string)): + im0 = fiber_list_string[i] + im1 = cell_list_string[i] + + output = Path(output_path) / os.path.relpath(im0, output_base) + output = output.parent / output.stem + output = Path(str(output) + ".saenopyOrientation") + + if output.exists(): + if exist_overwrite_callback is not None: + mode = exist_overwrite_callback(output) + if mode == 0: + break + if mode == "read": + data = ResultOrientation.load(output) + data.is_read = True + results.append(data) + continue + elif load_existing is True: + data = ResultOrientation.load(output) + data.is_read = True + results.append(data) + continue + + data = ResultOrientation( + output=str(output), + image_fiber=str(im0), + image_cell=str(im1), + pixel_size=float(pixel_size), + ) + data.save() + results.append(data) + + return results diff --git a/saenopy/saveable.py b/saenopy/saveable.py index a81b6f8..1b6a978 100644 --- a/saenopy/saveable.py +++ b/saenopy/saveable.py @@ -90,14 +90,14 @@ def save(self, filename: str, file_format=None): if file_format == ".h5py" or file_format == ".h5": # pragma: no cover return dict_to_h5(filename, flatten_dict(data)) - elif file_format == ".npz" or file_format == ".saenopy" or file_format == ".saenopy2D" or file_format == ".saenopySpheroid": + elif file_format == ".npz" or file_format == ".saenopy" or file_format == ".saenopy2D" or file_format == ".saenopySpheroid" or file_format == ".saenopyOrientation": np.savez(filename, **data) try: # numpy 2.0 np.lib._npyio_impl._savez(filename, [], flatten_dict(data), True, allow_pickle=False) except AttributeError: np.lib.npyio._savez(filename, [], flatten_dict(data), True, allow_pickle=False) import shutil - if file_format == ".saenopy" or file_format == ".saenopy2D" or file_format == ".saenopySpheroid": + if file_format == ".saenopy" or file_format == ".saenopy2D" or file_format == ".saenopySpheroid" or file_format == ".saenopyOrientation": shutil.move(filename+".npz", filename) else: raise ValueError("format not supported") @@ -133,7 +133,7 @@ def load(cls, filename, file_format=None): import h5py data = h5py.File(filename, "a") result = cls.from_dict(unflatten_dict_h5(data)) - elif file_format == ".npz" or file_format == ".saenopy" or file_format == ".saenopy2D" or file_format == ".saenopySpheroid": + elif file_format == ".npz" or file_format == ".saenopy" or file_format == ".saenopy2D" or file_format == ".saenopySpheroid" or file_format == ".saenopyOrientation": data = np.load(filename, allow_pickle=False) result = cls.from_dict(unflatten_dict(data))