Skip to content

Commit

Permalink
added image exporter dialog for Spheroid evaluation
Browse files Browse the repository at this point in the history
  • Loading branch information
rgerum committed Dec 27, 2023
1 parent d95d9f0 commit deb5344
Show file tree
Hide file tree
Showing 13 changed files with 217 additions and 78 deletions.
1 change: 1 addition & 0 deletions saenopy/examples.py
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@ def get_examples_spheroid():
"desc": "Test data for the spheroid model.",
"img": image_path / "MCF7-time-lapse.png",
"pixel_size": 1.29,
"time_delta": 120,
"input": example_path / 'MCF7-time-lapse/20160912-122130_Mic3_rep?_pos*_x0_y0_modeBF_zMinProj.tif',
"output_path": example_path / 'MCF7-time-lapse/example_output',
"url": "https://github.com/rgerum/saenopy/releases/download/v0.7.4/MCF7-time-lapse.zip",
Expand Down
12 changes: 9 additions & 3 deletions saenopy/gui/common/BatchEvaluate.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
from saenopy.gui.common.stack_selector_tif import add_last_voxel_size, add_last_time_delta

from saenopy.gui.common.AddFilesDialog import FileExistsDialog
from saenopy.gui.spheroid.modules.result import ResultSpheroid


class SharedProperties:
Expand Down Expand Up @@ -99,7 +100,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)
self.button_export = QtShortCuts.QPushButton(None, "export images", lambda x: self.sub_module_export.export_window.show())
if getattr(self, "sub_module_export", None):
self.button_export = QtShortCuts.QPushButton(None, "export images", lambda x: self.sub_module_export.export_window.show())

self.data = []
self.list.setData(self.data)
Expand Down Expand Up @@ -260,11 +262,15 @@ def load_from_path(self, paths):
for p in sorted(glob.glob(str(path), recursive=True)):
print(p)
try:
self.add_data(Result.load(p))
if self.file_extension == ".saenopySpheroid":
self.add_data(ResultSpheroid.load(p))
pass
else:
self.add_data(Result.load(p))
except Exception as err:
QtWidgets.QMessageBox.critical(self, "Open Files", f"File {p} is not a valid Saenopy file.")
traceback.print_exc()
self.update_icons()
#self.update_icons()

def add_data(self, data):
self.list.addData(data.output, True, data, mpl.colors.to_hex(f"gray"))
Expand Down
3 changes: 2 additions & 1 deletion saenopy/gui/common/PipelineModule.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,8 @@ def __init__(self, parent: "BatchEvaluate", layout):
self.processing_progress.connect(self.parent.progress)

def setParameterMapping(self, params_name: str = None, parameter_dict: dict=None):
self.input_button.setEnabled(False)
if getattr(self, "input_button", None) is not None:
self.input_button.setEnabled(False)
self.params_name = params_name
if params_name is None:
return
Expand Down
10 changes: 5 additions & 5 deletions saenopy/gui/solver/modules/VTK_Toolbar.py
Original file line number Diff line number Diff line change
Expand Up @@ -235,11 +235,11 @@ def property_changed(self, name, value):
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])
if getattr(self.plotter, "auto_value", None) is not None:
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):
Expand Down
64 changes: 17 additions & 47 deletions saenopy/gui/solver/modules/exporter/ExportRenderCommon.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,36 +6,16 @@ class Mesh2D:
pass

def get_mesh_arrows(params, result):
if params["arrows"] == "piv":
if isinstance(result, ResultSpheroid):
if result.displacements is not None and params["time"]["t"] > 0:
try:
disp = result.displacements[params["time"]["t"]-1]
mesh = Mesh2D()
mesh.units = "pixels"
mesh.nodes = np.array([disp["x"].ravel(), disp["y"].ravel()]).T
mesh.displacements_measured = np.array([disp["u"].ravel(), disp["v"].ravel()]).T

if mesh is not None:
return mesh, mesh.displacements_measured, params["deformation_arrows"], "displacements_measured"
except IndexError:
pass
elif result is not None:
mesh = result.mesh_piv[params["time"]["t"]]
if mesh is not None and mesh.displacements_measured is not None:
return mesh, mesh.displacements_measured, params["deformation_arrows"], "displacements_measured"
elif params["arrows"] == "target deformations":
M = result.solvers[params["time"]["t"]]
if M is not None:
return M.mesh, M.mesh.displacements_target, params["deformation_arrows"], "displacements_target"
elif params["arrows"] == "fitted deformations":
M = result.solvers[params["time"]["t"]]
if M is not None:
return M.mesh, M.mesh.displacements, params["deformation_arrows"], "displacements"
elif params["arrows"] == "fitted forces":
M = result.solvers[params["time"]["t"]]
if M is not None:
return M.mesh, -M.mesh.forces * M.mesh.regularisation_mask[:, None], params["force_arrows"], "forces"
data = result.get_data_structure()
if params["arrows"] not in data["fields"]:
return None, None, {}, ""
mesh, field = result.get_field_data(params["arrows"], params["time"]["t"])
if data["fields"][params["arrows"]]["measure"] == "deformation":
if mesh is not None and field is not None:
return mesh, field, params["deformation_arrows"], data["fields"][params["arrows"]]["name"]
if data["fields"][params["arrows"]]["measure"] == "deformation":
if mesh is not None and field is not None:
return mesh, field, params["force_arrows"], data["fields"][params["arrows"]]["name"]
return None, None, {}, ""


Expand Down Expand Up @@ -68,31 +48,21 @@ def get_mesh_extent(params, result):


def getVectorFieldImage(result, params, use_fixed_contrast_if_available=False, use_2D=False, exporter=None):
data = result.get_data_structure()
try:
image = params["stack"]["image"]
if use_2D:
image = 1
if image and params["time"]["t"] < len(result.stacks):
if params["stack"]["use_reference_stack"] and result.stack_reference:
stack = result.stack_reference
else:
stack = result.stacks[params["time"]["t"]]
channel = params["stack"]["channel"]
if isinstance(channel, str):
try:
channel = result.stacks[0].channels.index(channel)
except ValueError:
channel = 0
if channel >= len(stack.channels):
im = stack[:, :, :, params["stack"]["z"], 0]
else:
im = stack[:, :, :, params["stack"]["z"], channel]
if image and params["time"]["t"] < data["time_point_count"]:
stack = result.get_image_data(params["time"]["t"], params["stack"]["channel"],params["stack"]["use_reference_stack"])
if params["stack"]["z_proj"]:
z_range = [0, 5, 10, 1000][params["stack"]["z_proj"]]
start = np.clip(params["stack"]["z"] - z_range, 0, stack.shape[2])
end = np.clip(params["stack"]["z"] + z_range, 0, stack.shape[2])
im = stack[:, :, :, start:end, channel]
im = stack[:, :, :, start:end]
im = np.max(im, axis=3)
else:
im = stack[:, :, :, params["stack"]["z"]]

if params["stack"]["use_contrast_enhance"]:
if use_fixed_contrast_if_available and params["stack"]["contrast_enhance"]:
Expand All @@ -105,7 +75,7 @@ def getVectorFieldImage(result, params, use_fixed_contrast_if_available=False, u
im = im.astype(np.float64) * 255 / (max - min)
im = np.clip(im, 0, 255).astype(np.uint8)

display_image = [im, stack.voxel_size, params["stack"]["z"] - stack.shape[2] / 2]
display_image = [im, data["voxel_size"], params["stack"]["z"] - data["z_slices_count"] / 2]
if params["stack"]["image"] == 2:
display_image[2] = -stack.shape[2] / 2
else:
Expand Down
56 changes: 43 additions & 13 deletions saenopy/gui/solver/modules/exporter/Exporter.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from pathlib import Path

from saenopy import Result
from saenopy.gui.spheroid.modules.result import ResultSpheroid
from saenopy.gui.common import QtShortCuts, QExtendedGraphicsView
from saenopy.gui.common.resources import resource_path
from saenopy.gui.solver.modules.exporter.FiberViewer import ChannelProperties
Expand Down Expand Up @@ -633,51 +634,80 @@ def do_export_zscan(self):
writer.write(self.im)

def check_evaluated(self, result: Result) -> bool:
return self.result is not None and result.stacks is not None and len(result.stacks) > 0
if result is None:
return False
if isinstance(result, ResultSpheroid):
return result.displacements is not None and len(result.displacements) > 0
return result.stacks is not None and len(result.stacks) > 0

def setResult(self, result: Result, no_update_display=False):
self.result = result
if result and result.stacks and result.stacks[0]:
self.z_slider.setRange(0, result.stacks[0].shape[2] - 1)
self.z_slider.setValue(result.stacks[0].shape[2] // 2)
if result:
data = result.get_data_structure()
self.input_arrows.setValues(["None"] + list(data["fields"].keys()))
self.input_arrows.setValue(next(iter(data["fields"].items()))[0])

#if len(data["channels"]) == 1:
# self.channel_selectB.setVisible(False)
# self.colormap_chooserB.setVisible(False)
#else:
# self.channel_selectB.setVisible(True)
# self.colormap_chooserB.setVisible(True)

if data["dimensions"] == 2:
self.input_use2D.setValue(True)
self.input_use2D.setVisible(False)
self.hide2D()
self.z_slider.setVisible(False)
self.input_average_range.setVisible(False)
self.vtk_toolbar.button_z_proj.setVisible(False)

self.button_export_zscan.setEnabled(False)
self.zscan_fps.setEnabled(False)
self.zscan_steps.setEnabled(False)
else:
self.z_slider.setRange(0, data["z_slices_count"] - 1)
self.z_slider.setValue( data["z_slices_count"] // 2)

if result.stacks[0].channels:
if len(data["channels"]) > 1:
value = self.vtk_toolbar.channel_select.value()
self.vtk_toolbar.channel_select.setValues(np.arange(len(result.stacks[0].channels)), result.stacks[0].channels)
self.vtk_toolbar.channel_select.setValues(np.arange(len(data["channels"])), data["channels"])
self.vtk_toolbar.channel_select.setValue(value)
self.vtk_toolbar.channel_select.setVisible(True)

value = self.channel_selectB.value()
self.channel_selectB.setValues([-1] + list(np.arange(len(result.stacks[0].channels))),
[""] + result.stacks[0].channels)
self.channel_selectB.setValues([-1] + list(np.arange(len(data["channels"]))),
[""] + data["channels"])
self.channel_selectB.setValue("")
self.channel_selectB.setVisible(True)
self.colormap_chooserB.setVisible(True)

value = self.channel1_properties.channel_select.value()
self.channel1_properties.channel_select.setValues(np.arange(len(result.stacks[0].channels))[1:],
result.stacks[0].channels[1:])
self.channel1_properties.channel_select.setValues(np.arange(len(data["channels"]))[1:],
data["channels"])
self.channel1_properties.channel_select.setValue(value)
self.channel1_properties.channel_select.setVisible(True)
else:
self.vtk_toolbar.channel_select.setValue(0)
self.vtk_toolbar.channel_select.setVisible(False)
self.channel_selectB.setVisible(False)
self.colormap_chooserB.setVisible(False)

shape = result.stacks[0].shape
shape = data["im_shape"]
self.input_cropx.setRange(0, shape[1])
self.input_cropx.setValue((shape[1] // 2 - 100, shape[1] // 2 + 100))
self.input_cropy.setRange(0, shape[0])
self.input_cropy.setValue((shape[0] // 2 - 100, shape[0] // 2 + 100))
self.input_cropz.setRange(0, shape[2])
self.input_cropz.setValue((shape[2] // 2 - 25, shape[2] // 2 + 25))

if result.stacks[0].shape[-1] == 1:
if data["channels"] == 1:
self.channel1_properties.input_show.setValue(False)
self.channel1_properties.setDisabled(True)
else:
self.channel1_properties.setDisabled(False)

self.input_average_range.setRange(0, shape[2] * result.stacks[0].voxel_size[2])
self.input_average_range.setRange(0, shape[2] * data["voxel_size"][2])
self.input_average_range.setValue(10)

super().setResult(result)
Expand Down
8 changes: 4 additions & 4 deletions saenopy/gui/solver/modules/exporter/ExporterRender2D.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ def project_data(R, field, skip=1):
if mesh is None:
return pil_image

scale_max = params_arrows["scale_max"] if params_arrows["autoscale"] else None
scale_max = params_arrows["scale_max"] if not params_arrows["autoscale"] else None
colormap = params_arrows["colormap"]
skip = params_arrows["skip"]
alpha = params_arrows["arrow_opacity"]
Expand All @@ -92,7 +92,7 @@ def project_data(R, field, skip=1):
R = mesh.nodes.copy()
is3D = R.shape[1] == 3
field = field.copy()
if getattr(mesh, "units", None) is "pixels":
if getattr(mesh, "units", None) == "pixels":
R = R[:, :2]
R[:, 1] = display_image[0].shape[0] - R[:, 1]
field = field[:, :2] * params_arrows["arrow_scale"]
Expand Down Expand Up @@ -146,7 +146,7 @@ def getBarParameters(pixtomu, scale=1):
return pixel, mu

if params["scalebar"]["length"] == 0:
pixel, mu = getBarParameters(result.stacks[0].voxel_size[0])
pixel, mu = getBarParameters(result.get_data_structure()["voxel_size"][0])
else:
mu = params["scalebar"]["length"]
pixel = mu / result.stacks[0].voxel_size[0]
Expand Down Expand Up @@ -223,7 +223,7 @@ def add_text(pil_image, text, position, fontsize=18):
except IOError:
font = ImageFont.truetype("times", font_size)

length_number = image.textsize(text, font=font)
length_number = image.textlength(text, font=font)
x, y = position

if x < 0:
Expand Down
2 changes: 0 additions & 2 deletions saenopy/gui/spheroid/gui_deformation_spheroid.py
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,5 @@ def load(self):
app = QtWidgets.QApplication(sys.argv)
print(sys.argv)
window = MainWindow()
if len(sys.argv) >= 2:
window.loadFile(sys.argv[1])
window.show()
sys.exit(app.exec_())
3 changes: 3 additions & 0 deletions saenopy/gui/spheroid/modules/AddFilesDialog.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ def add_new_measurement_tab(self):
self.pixel_size = QtShortCuts.QInputString(None, "pixel size", 1.29, settings=self.settings,
settings_key=f"{self.settings_group}/pixel_size",
allow_none=False, type=float)
self.time_delta = QtShortCuts.QInputString(None, "time delta", 120, settings=self.settings,
settings_key=f"{self.settings_group}/delta_t",
allow_none=False, type=float)
self.outputText = QtShortCuts.QInputFolder(None, "output", settings=self.settings,
settings_key=f"{self.settings_group}/wildcard2", allow_edit=True)

Expand Down
6 changes: 5 additions & 1 deletion saenopy/gui/spheroid/modules/BatchEvaluate.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ def add_modules(self):
layout0 = QtShortCuts.currentLayout()
self.sub_module_deformation = DeformationDetector(self, layout0)
self.sub_module_forces = ForceCalculator(self, layout0)
self.modules = [self.sub_module_deformation, self.sub_module_forces]
self.sub_module_export = ExportViewer(self, layout0)
self.modules = [self.sub_module_deformation, self.sub_module_forces, self.sub_module_export]

def path_editor(self):
return
Expand All @@ -61,17 +62,20 @@ def add_measurement(self):
input_path = dialog.inputText.value()
output_path = dialog.outputText.value()
pixel_size = dialog.pixel_size.value()
time_delta = dialog.time_delta.value()
elif dialog.mode == "example":
# get the date from the example referenced by name
example = get_examples_spheroid()[dialog.mode_data]
input_path = example["input"]
output_path = example["output_path"]
pixel_size = example["pixel_size"]
time_delta = example["time_delta"]

results = get_stacks_spheroid(
input_path,
output_path,
pixel_size=pixel_size,
time_delta=time_delta,
exist_overwrite_callback=lambda x: do_overwrite(x, self),
)

Expand Down
2 changes: 1 addition & 1 deletion saenopy/gui/spheroid/modules/DeformationDetector.py
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,7 @@ def update_display(self, *, plotter=None):
[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 = render_2d_arrows({
'arrows': 'piv',
'arrows': 'deformation',
'deformation_arrows': {
"autoscale": not self.auto_scale.value(),
"scale_max": self.getScaleMax(),
Expand Down
Loading

0 comments on commit deb5344

Please sign in to comment.