From 1210caf534a8fac2c8c5eea4f44238c42394d216 Mon Sep 17 00:00:00 2001 From: Richard Gerum <14153051+rgerum@users.noreply.github.com> Date: Thu, 28 Dec 2023 17:30:07 -0500 Subject: [PATCH] use BatchEvaluate common for pyTFM --- saenopy/gui/common/BatchEvaluate.py | 60 +-- saenopy/gui/solver/modules/BatchEvaluate.py | 2 +- saenopy/gui/spheroid/modules/BatchEvaluate.py | 26 +- saenopy/gui/tfm2d/modules/BatchEvaluate.py | 348 +++--------------- .../tfm2d/modules/CalculateForceGeneration.py | 2 +- saenopy/gui/tfm2d/modules/CalculateStress.py | 4 +- 6 files changed, 98 insertions(+), 344 deletions(-) diff --git a/saenopy/gui/common/BatchEvaluate.py b/saenopy/gui/common/BatchEvaluate.py index c942838c..0106c5fc 100644 --- a/saenopy/gui/common/BatchEvaluate.py +++ b/saenopy/gui/common/BatchEvaluate.py @@ -20,6 +20,7 @@ from saenopy.gui.common.AddFilesDialog import FileExistsDialog from saenopy.gui.spheroid.modules.result import ResultSpheroid +from saenopy.gui.tfm2d.modules.result import Result2D class SharedProperties: @@ -53,6 +54,28 @@ class BatchEvaluate(QtWidgets.QWidget): def add_modules(self): self.modules = [] + def add_tabs(self): + layout = QtShortCuts.currentLayout() + with QtShortCuts.QTabWidget(layout) as self.tabs: + self.tabs.setMinimumWidth(500) + old_tab = None + cam_pos = None + + def tab_changed(x): + nonlocal old_tab, cam_pos + tab = self.tabs.currentWidget() + if old_tab is not None and getattr(old_tab, "plotter", None): + cam_pos = old_tab.plotter.camera_position + if cam_pos is not None and getattr(tab, "plotter", None): + tab.plotter.camera_position = cam_pos + if old_tab is not None: + tab.t_slider.setValue(old_tab.t_slider.value()) + old_tab = tab + self.tab_changed.emit(tab) + + self.tabs.currentChanged.connect(tab_changed) + pass + def __init__(self, parent=None): super().__init__(parent) @@ -75,23 +98,7 @@ def __init__(self, parent=None): self.progressbar.setOrientation(QtCore.Qt.Horizontal) with QtShortCuts.QHBoxLayout() as layout: layout.setContentsMargins(0, 0, 0, 0) - with QtShortCuts.QTabWidget(layout) as self.tabs: - self.tabs.setMinimumWidth(500) - old_tab = None - cam_pos = None - def tab_changed(x): - nonlocal old_tab, cam_pos - tab = self.tabs.currentWidget() - if old_tab is not None and getattr(old_tab, "plotter", None): - cam_pos = old_tab.plotter.camera_position - if cam_pos is not None and getattr(tab, "plotter", None): - tab.plotter.camera_position = cam_pos - if old_tab is not None: - tab.t_slider.setValue(old_tab.t_slider.value()) - old_tab = tab - self.tab_changed.emit(tab) - self.tabs.currentChanged.connect(tab_changed) - pass + self.add_tabs() with QtShortCuts.QVBoxLayout() as layout0: layout0.parent().setMaximumWidth(420) layout0.setContentsMargins(0, 0, 0, 0) @@ -100,6 +107,8 @@ def tab_changed(x): self.button_start_all = QtShortCuts.QPushButton(None, "run all", self.run_all) with QtShortCuts.QHBoxLayout(): self.button_code = QtShortCuts.QPushButton(None, "export code", self.generate_code) + if getattr(self, "generate_data", None): + self.button_excel = QtShortCuts.QPushButton(None, "export data", self.generate_data) if getattr(self, "sub_module_export", None): self.button_export = QtShortCuts.QPushButton(None, "export images", lambda x: self.sub_module_export.export_window.show()) @@ -170,6 +179,12 @@ def progress(self, tup): self.progressbar.setValue(n) def generate_code(self): + try: + result = self.list.data[self.list.currentRow()][2] + if result is None: + return + except IndexError: + return new_path = QtWidgets.QFileDialog.getSaveFileName(None, "Save Session as Script", os.getcwd(), "Python File (*.py)") if new_path: # ensure filename ends in .py @@ -192,12 +207,9 @@ def run_all(self): if not self.data[i][1]: continue result = self.data[i][2] - if self.sub_module_deformation.group.value() is True: - self.sub_module_deformation.start_process(result=result) - if self.sub_module_mesh.group.value() is True: - self.sub_module_mesh.start_process(result=result) - if self.sub_module_regularize.group.value() is True: - self.sub_module_regularize.start_process(result=result) + for module in self.modules: + if getattr(module, "group", None) and module.group.value() is True: + module.start_process(result=result) def addTask(self, task, result, params, name): self.tasks.append([task, result, params, name]) @@ -265,6 +277,8 @@ def load_from_path(self, paths): if self.file_extension == ".saenopySpheroid": self.add_data(ResultSpheroid.load(p)) pass + elif self.file_extension == ".saenopy2D": + self.add_data(Result2D.load(p)) else: self.add_data(Result.load(p)) except Exception as err: diff --git a/saenopy/gui/solver/modules/BatchEvaluate.py b/saenopy/gui/solver/modules/BatchEvaluate.py index 32e2c68c..659af3fc 100644 --- a/saenopy/gui/solver/modules/BatchEvaluate.py +++ b/saenopy/gui/solver/modules/BatchEvaluate.py @@ -36,7 +36,7 @@ class BatchEvaluate(BatchEvaluate): settings_key = "Seanopy_deformation" file_extension = ".saenopy" - result_params = params = ["piv_parameters", "mesh_parameters", "material_parameters", "solve_parameters"] + result_params = ["piv_parameters", "mesh_parameters", "material_parameters", "solve_parameters"] def add_modules(self): layout0 = QtShortCuts.currentLayout() diff --git a/saenopy/gui/spheroid/modules/BatchEvaluate.py b/saenopy/gui/spheroid/modules/BatchEvaluate.py index f12bbdd0..01575900 100644 --- a/saenopy/gui/spheroid/modules/BatchEvaluate.py +++ b/saenopy/gui/spheroid/modules/BatchEvaluate.py @@ -1,34 +1,12 @@ -import json -import sys -import os - -import qtawesome as qta -from qtpy import QtCore, QtWidgets, QtGui -import numpy as np -import glob -import threading -from pathlib import Path -import matplotlib as mpl - -import traceback - -from saenopy import get_stacks -from saenopy import Result from saenopy.gui.common import QtShortCuts -from saenopy.gui.common.gui_classes import ListWidget -from saenopy.gui.common.stack_selector_tif import add_last_voxel_size, add_last_time_delta from .AddFilesDialog import AddFilesDialog from saenopy.gui.common.AddFilesDialog import do_overwrite from .DeformationDetector import DeformationDetector from saenopy.gui.solver.modules.exporter.Exporter import ExportViewer -#from .load_measurement_dialog import AddFilesDialog -from saenopy.gui.common.AddFilesDialog import FileExistsDialog -#from .path_editor import start_path_change -from saenopy.examples import get_examples from saenopy.gui.common.BatchEvaluate import BatchEvaluate from saenopy.examples import get_examples_spheroid -from saenopy.gui.spheroid.modules.result import ResultSpheroid, get_stacks_spheroid +from saenopy.gui.spheroid.modules.result import get_stacks_spheroid from .ForceCalculator import ForceCalculator @@ -36,7 +14,7 @@ class BatchEvaluate(BatchEvaluate): settings_key = "Spheroid" file_extension = ".saenopySpheroid" - result_params = params = ["piv_parameters", "force_parameters"] + result_params = ["piv_parameters", "force_parameters"] def add_modules(self): layout0 = QtShortCuts.currentLayout() diff --git a/saenopy/gui/tfm2d/modules/BatchEvaluate.py b/saenopy/gui/tfm2d/modules/BatchEvaluate.py index 9c018006..917461e4 100644 --- a/saenopy/gui/tfm2d/modules/BatchEvaluate.py +++ b/saenopy/gui/tfm2d/modules/BatchEvaluate.py @@ -39,118 +39,60 @@ #from .path_editor import start_path_change from saenopy.examples import get_examples_2D - -class SharedProperties: - properties = None - - def __init__(self): - self.properties = {} - - def add_property(self, name, target): - if name not in self.properties: - self.properties[name] = [] - self.properties[name].append(target) - - def change_property(self, name, value, target): - if name in self.properties: - for t in self.properties[name]: - if t != target: - t.property_changed(name, value) - - -class BatchEvaluate(QtWidgets.QWidget): - result_changed = QtCore.Signal(object) - tab_changed = QtCore.Signal(object) - set_current_result = QtCore.Signal(object) - - def __init__(self, parent=None): - super().__init__(parent) - - self.shared_properties = SharedProperties() - - self.settings = QtCore.QSettings("Saenopy", "Seanopy_deformation") - - with QtShortCuts.QHBoxLayout(self) as main_layout: - main_layout.setContentsMargins(0, 0, 0, 0) - 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", copy_params=True, allow_paste_callback=self.allow_paste) - self.list.addItemClicked.connect(self.add_measurement) - self.list.signal_act_copy_clicked.connect(self.copy_params) - self.list.signal_act_paste_clicked.connect(self.paste_params) - self.list.signal_act_paths_clicked.connect(self.path_editor) - self.list.itemSelectionChanged.connect(self.listSelected) - self.progressbar = QtWidgets.QProgressBar().addToLayout() - self.progressbar.setOrientation(QtCore.Qt.Horizontal) - with QtShortCuts.QHBoxLayout() as layout: - layout.setContentsMargins(0, 0, 0, 0) - with QtShortCuts.QVBoxLayout() as layout: - layout.setContentsMargins(0, 0, 0, 0) - with QtShortCuts.QTabBarWidget(layout) as self.tabs: - self.tabs.setMinimumWidth(500) - old_tab = None - cam_pos = None - def tab_changed(x): - nonlocal old_tab, cam_pos - tab = self.tabs.currentWidget() - self.tab_changed.emit(tab) - self.tabs.currentChanged.connect(tab_changed) - pass - self.draw = DrawWindow(self, QtShortCuts.currentLayout()) - self.draw.signal_mask_drawn.connect(self.on_mask_drawn) - with QtShortCuts.QVBoxLayout() as layout0: - layout0.parent().setMaximumWidth(420) - layout0.setContentsMargins(0, 0, 0, 0) - self.sub_bf = DisplayCellImage(self, layout0) - self.sub_draw = DeformationDetector(self, layout0) - self.sub_draw2 = DeformationDetector2(self, layout0) - self.sub_draw3 = DeformationDetector3(self, layout0) - self.sub_force = Force(self, layout0) - self.sub_force_gen = ForceGeneration(self, layout0) - self.sub_stress = CalculateStress(self, layout0) - #self.sub_module_stacks = StackDisplay(self, layout0) - #self.sub_module_deformation = DeformationDetector(self, layout0) - #self.sub_module_mesh = MeshCreator(self, layout0) - #self.sub_module_fitted_mesh = FittedMesh(self, layout0) - #self.sub_module_regularize = Regularizer(self, layout0) - #self.sub_module_view = ResultView(self, layout0) - #self.sub_module_fiber = FiberViewer(self, layout0) - #self.sub_module_export = ExportViewer(self, layout0) - layout0.addStretch() - - box = QtWidgets.QGroupBox("painting").addToLayout() - with QtShortCuts.QVBoxLayout(box) as layout: - self.slider_cursor_width = QtShortCuts.QInputNumber(None, "cursor width", 10, 1, 100, True, float=False) - self.slider_cursor_width.valueChanged.connect( lambda x: self.draw.setCursorSize(x)) - self.slider_cursor_opacity = QtShortCuts.QInputNumber(None, "mask opacity", 0.5, 0, 1, True, float=True) - self.slider_cursor_opacity.valueChanged.connect( lambda x: self.draw.setOpacity(x)) - with QtShortCuts.QHBoxLayout(): - self.button_red = QtShortCuts.QPushButton(None, "tractions", lambda x: self.draw.setColor(1), icon=qta.icon("fa5s.circle", color="red")) - self.button_green = QtShortCuts.QPushButton(None, "cell boundary", lambda x: self.draw.setColor(2), icon=qta.icon("fa5s.circle", color="green")) - #self.button_blue = QtShortCuts.QPushButton(None, "blue", lambda x: self.draw.setColor(3), icon=qta.icon("fa5s.circle", color="blue")) - self.button_start_all = QtShortCuts.QPushButton(None, "run all", self.run_all) - with QtShortCuts.QHBoxLayout(): - self.button_code = QtShortCuts.QPushButton(None, "export code", self.generate_code) - self.button_excel = QtShortCuts.QPushButton(None, "export data", self.generate_data) - #self.button_export = QtShortCuts.QPushButton(None, "export images", lambda x: self.sub_module_export.export_window.show()) - - self.data = [] - self.list.setData(self.data) - - self.setAcceptDrops(True) - - self.tasks = [] - self.current_task_id = 0 - self.thread = None - self.signal_task_finished.connect(self.run_finished) - - # load paths - self.load_from_path([arg for arg in sys.argv if arg.endswith(".saenopy2D")]) - - # disable all tabs - for i in range(self.tabs.count()-1, -1, -1): - self.tabs.setTabEnabled(i, False) +from saenopy.gui.common.BatchEvaluate import BatchEvaluate + + +class BatchEvaluate(BatchEvaluate): + settings_key = "Seanopy_deformation" + file_extension = ".saenopy2D" + + result_params = ["piv_parameters", "force_parameters"] + + def add_modules(self): + layout0 = QtShortCuts.currentLayout() + layout0.parent().setMaximumWidth(420) + layout0.setContentsMargins(0, 0, 0, 0) + self.sub_bf = DisplayCellImage(self, layout0) + self.sub_draw = DeformationDetector(self, layout0) + self.sub_draw2 = DeformationDetector2(self, layout0) + self.sub_draw3 = DeformationDetector3(self, layout0) + self.sub_force = Force(self, layout0) + self.sub_force_gen = ForceGeneration(self, layout0) + self.sub_stress = CalculateStress(self, layout0) + # self.sub_module_export = ExportViewer(self, layout0) + layout0.addStretch() + + box = QtWidgets.QGroupBox("painting").addToLayout() + with QtShortCuts.QVBoxLayout(box) as layout: + self.slider_cursor_width = QtShortCuts.QInputNumber(None, "cursor width", 10, 1, 100, True, float=False) + self.slider_cursor_width.valueChanged.connect(lambda x: self.draw.setCursorSize(x)) + self.slider_cursor_opacity = QtShortCuts.QInputNumber(None, "mask opacity", 0.5, 0, 1, True, float=True) + self.slider_cursor_opacity.valueChanged.connect(lambda x: self.draw.setOpacity(x)) + with QtShortCuts.QHBoxLayout(): + self.button_red = QtShortCuts.QPushButton(None, "tractions", lambda x: self.draw.setColor(1), + icon=qta.icon("fa5s.circle", color="red")) + self.button_green = QtShortCuts.QPushButton(None, "cell boundary", lambda x: self.draw.setColor(2), + icon=qta.icon("fa5s.circle", color="green")) + # self.button_blue = QtShortCuts.QPushButton(None, "blue", lambda x: self.draw.setColor(3), icon=qta.icon("fa5s.circle", color="blue")) + + self.modules = [self.sub_bf, self.sub_draw2, self.sub_draw3, self.sub_force, self.sub_force_gen, self.sub_stress] + + def add_tabs(self): + with QtShortCuts.QVBoxLayout() as layout: + with QtShortCuts.QTabBarWidget(layout) as self.tabs: + self.tabs.setMinimumWidth(500) + old_tab = None + cam_pos = None + + def tab_changed(x): + nonlocal old_tab, cam_pos + tab = self.tabs.currentWidget() + self.tab_changed.emit(tab) + + self.tabs.currentChanged.connect(tab_changed) + pass + self.draw = DrawWindow(self, QtShortCuts.currentLayout()) + self.draw.signal_mask_drawn.connect(self.on_mask_drawn) def generate_data(self): new_path = QtWidgets.QFileDialog.getSaveFileName(None, "Save Data CSV", os.getcwd(), @@ -217,174 +159,9 @@ def on_mask_drawn(self): if result: result.mask = self.draw.get_image() - def copy_params(self): - result = self.list.data[self.list.currentRow()][2] - params = { - "piv_parameters": result.piv_parameters_tmp, - "force_parameters": result.force_parameters_tmp, - "force_gen_parameters": result.force_gen_parameters_tmp, - "stress_parameters": result.stress_parameters_tmp, - } - print(params) - for group in params: - if params[group] is None: - continue - for g in params[group]: - if type(params[group][g]) == np.bool_: - params[group][g] = bool(params[group][g]) - if type(params[group][g]) == np.int64: - params[group][g] = int(params[group][g]) - text = json.dumps(params, indent=2) - cb = QtGui.QGuiApplication.clipboard() - cb.setText(text, mode=cb.Clipboard) - - def allow_paste(self): - cb = QtGui.QGuiApplication.clipboard() - text = cb.text(mode=cb.Clipboard) - try: - data = json.loads(text) - if "piv_parameters" in data and \ - "force_parameters" in data: - return True - except (ValueError, TypeError): - return False - return False - - def paste_params(self): - cb = QtGui.QGuiApplication.clipboard() - text = cb.text(mode=cb.Clipboard) - try: - data = json.loads(text) - except ValueError: - return False - result = self.list.data[self.list.currentRow()][2] - params = ["piv_parameters", "force_parameters"] - for par in params: - if par in data: - setattr(result, par+"_tmp", data[par]) - self.set_current_result.emit(result) - def path_editor(self): result = self.list.data[self.list.currentRow()][2] - start_path_change(self, result) - - def progress(self, tup): - n, total = tup - self.progressbar.setMaximum(total) - self.progressbar.setValue(n) - - def generate_code(self): - try: - result = self.list.data[self.list.currentRow()][2] - except IndexError: - return - new_path = QtWidgets.QFileDialog.getSaveFileName(None, "Save Session as Script", os.getcwd(), "Python File (*.py)") - if new_path: - # ensure filename ends in .py - if not new_path.endswith(".py"): - new_path += ".py" - - import_code = "import numpy as np\n" - run_code = "" - for module in [self.sub_bf, self.sub_draw2, self.sub_draw3, self.sub_force, self.sub_force_gen, self.sub_stress]: - code1, code2 = module.get_code() - import_code += code1 - run_code += code2 +"\n" - run_code = import_code + "\n\n" + run_code - #print(run_code) - with open(new_path, "w") as fp: - fp.write(run_code) - - def run_all(self): - for i in range(len(self.data)): - if not self.data[i][1]: - continue - result = self.data[i][2] - if self.sub_module_deformation.group.value() is True: - self.sub_module_deformation.start_process(result=result) - if self.sub_module_mesh.group.value() is True: - self.sub_module_mesh.start_process(result=result) - if self.sub_module_regularize.group.value() is True: - self.sub_module_regularize.start_process(result=result) - - def addTask(self, task, result, params, name): - self.tasks.append([task, result, params, name]) - if self.thread is None: - self.run_next() - - signal_task_finished = QtCore.Signal() - - def run_next(self): - task, result, params, name = self.tasks[self.current_task_id] - self.thread = threading.Thread(target=self.run_thread, args=(task, result, params, name), daemon=True) - self.thread.start() - - def run_thread(self, task, result, params, name): - result.state = True - self.update_icons() - task(result, params) - self.signal_task_finished.emit() - result.state = False - self.update_icons() - - def run_finished(self): - self.current_task_id += 1 - self.thread = None - if self.current_task_id < len(self.tasks): - self.run_next() - - 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() # path() - - if url[0] == "/" and url[2] == ":": - url = url[1:] - urls.append(url) - self.load_from_path(urls) - - def load_from_path(self, paths): - # make sure that paths is a list - if isinstance(paths, (str, Path)): - paths = [paths] - - # iterate over all paths - for path in paths: - # if it is a directory search all saenopy files in it - path = Path(path) - if path.is_dir(): - path = str(path) + "/**/*.saenopy2D" - # glob over the path (or just use the path if it does not contain a *) - for p in sorted(glob.glob(str(path), recursive=True)): - print(p) - try: - self.add_data(Result2D.load(p)) - except Exception as err: - QtWidgets.QMessageBox.critical(self, "Open Files", f"File {p} is not a valid Saenopy2D file.") - traceback.print_exc() - self.update_icons() - - def add_data(self, data): - self.list.addData(data.output, True, data, mpl.colors.to_hex(f"gray")) - - def update_icons(self): - for j in range(self.list.count( ) -1): - if self.data[j][2].state is True: - self.list.item(j).setIcon(qta.icon("fa5s.hourglass-half", options=[dict(color="orange")])) - else: - self.list.item(j).setIcon(qta.icon("fa5.circle", options=[dict(color="gray")])) + #start_path_change(self, result) def add_measurement(self): last_decision = None @@ -432,26 +209,11 @@ def do_overwrite(filename): bf_stack, active_stack, reference_stack, pixel_size=dialog.pixel_size.value(), exist_overwrite_callback=do_overwrite, ) - #results = [ - # Result2D(dialog.outputText.value(), bf=bf_stack, - # input=active_stack, reference_stack=reference_stack, pixel_size=dialog.pixel_size.value())] - ## load the stack - #results = get_stacks( - # active_stack, - # reference_stack=reference_stack, - # output_path=dialog.outputText.value(), - # voxel_size=dialog.stack_data.getVoxelSize(), - # time_delta=time_delta, - # crop=dialog.stack_data.get_crop(), - # 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: - # store the last voxel size - #add_last_voxel_size(dialog.stack_data.getVoxelSize()) # add the loaded measruement objects for data in results: self.add_data(data) diff --git a/saenopy/gui/tfm2d/modules/CalculateForceGeneration.py b/saenopy/gui/tfm2d/modules/CalculateForceGeneration.py index 96453ba9..1ba1bf39 100644 --- a/saenopy/gui/tfm2d/modules/CalculateForceGeneration.py +++ b/saenopy/gui/tfm2d/modules/CalculateForceGeneration.py @@ -33,7 +33,7 @@ def __init__(self, parent=None, layout=None): with QtShortCuts.QVBoxLayout(self) as layout: layout.setContentsMargins(0, 0, 0, 0) - with CheckAbleGroup(self, "force generation", url="https://saenopy.readthedocs.io/en/latest/interface_solver.html#detect-deformations").addToLayout() as self.group: + with CheckAbleGroup(self, "force generation").addToLayout() as self.group: with QtShortCuts.QVBoxLayout(): self.label = QtWidgets.QLabel("draw a mask with the red color to select the area where deformations and tractions that are generated by the colony.").addToLayout() self.label.setWordWrap(True) diff --git a/saenopy/gui/tfm2d/modules/CalculateStress.py b/saenopy/gui/tfm2d/modules/CalculateStress.py index 72d3a55b..faa57faa 100644 --- a/saenopy/gui/tfm2d/modules/CalculateStress.py +++ b/saenopy/gui/tfm2d/modules/CalculateStress.py @@ -40,7 +40,7 @@ def __init__(self, parent=None, layout=None): with QtShortCuts.QVBoxLayout(self) as layout: layout.setContentsMargins(0, 0, 0, 0) - with CheckAbleGroup(self, "stress", url="https://saenopy.readthedocs.io/en/latest/interface_solver.html#detect-deformations").addToLayout() as self.group: + with CheckAbleGroup(self, "stress").addToLayout() as self.group: with QtShortCuts.QVBoxLayout(): self.label = QtWidgets.QLabel( "draw a mask with the red color to select an area slightly larger then the colony. Draw a mask with the green color to circle every single cell and mark their boundaries.").addToLayout() @@ -204,7 +204,7 @@ def process(self, result: Result2D, stress_parameters: dict): # type: ignore result.save() def get_code(self) -> Tuple[str, str]: - import_code = "from pyTFM.grid_setup_solids_py import prepare_forces\nfrom pyTFM.grid_setup_solids_py import interpolation\nfrom pyTFM.grid_setup_solids_py import prepare_forces\nfrom pyTFM.grid_setup_solids_py import grid_setup, FEM_simulation\nfrom pyTFM.grid_setup_solids_py import find_borders\nfrom pyTFM.plotting import plot_continuous_boundary_stresses\nfrom pyTFM.stress_functions import all_stress_measures, coefficient_of_variation\nfrom pyTFM.stress_functions import mean_stress_vector_norm\nfrom pyTFM.stress_functions import lineTension\nimport matplotlib.pyplot as plt\n" + import_code = "import numpy as np\nfrom pyTFM.grid_setup_solids_py import prepare_forces\nfrom pyTFM.grid_setup_solids_py import interpolation\nfrom pyTFM.grid_setup_solids_py import prepare_forces\nfrom pyTFM.grid_setup_solids_py import grid_setup, FEM_simulation\nfrom pyTFM.grid_setup_solids_py import find_borders\nfrom pyTFM.plotting import plot_continuous_boundary_stresses\nfrom pyTFM.stress_functions import all_stress_measures, coefficient_of_variation\nfrom pyTFM.stress_functions import mean_stress_vector_norm\nfrom pyTFM.stress_functions import lineTension\nimport matplotlib.pyplot as plt\n" results: List[Result2D] = [] def code(): # pragma: no cover