#!/usr/bin/env python3
"""Visualization stage for iMars3D."""
import holoviews as hv
import numpy as np
import panel as pn
import param
from holoviews import opts, streams
[docs]
class Visualization(param.Parameterized):
"""Visualization stage for iMars3D."""
recon = param.Array(
doc="reconstruction results as numpy array",
precedence=-1, # hide
)
idx_y = param.Integer(default=0, bounds=(0, None), doc="index of the y axis for recon stack")
# cmap
colormap = param.Selector(
default="gray",
objects=["gray", "viridis", "plasma"],
doc="colormap used for images",
)
colormap_scale = param.Selector(
default="linear",
objects=["linear", "log", "eq_hist"],
doc="colormap scale for displaying images",
)
# image size
frame_width = param.Integer(default=500, doc="viewer frame size")
[docs]
def cross_hair_view(self, x, y):
"""Cross hair view."""
return (hv.HLine(y) * hv.VLine(x)).opts(
opts.HLine(
color="red",
line_width=0.5,
),
opts.VLine(
color="red",
line_width=0.5,
),
)
[docs]
def xz_view(self):
"""XZ view."""
# main, top down
xzview = self.recon[self.idx_y, :, :]
return hv.Image(
(
np.arange(xzview.shape[1]),
np.arange(xzview.shape[0]),
xzview,
),
kdims=["x", "z"],
vdims=["intensity"],
).opts(
opts.Image(
frame_width=self.frame_width,
tools=["hover"],
cmap=self.colormap,
cnorm=self.colormap_scale,
data_aspect=1.0,
)
)
[docs]
def xy_view(self, x, y):
"""XY view."""
z = int(y)
# bottom
xyview = self.recon[:, int(z), :]
img = hv.Image(
(
np.arange(xyview.shape[1]),
np.arange(xyview.shape[0]),
xyview,
),
kdims=["x", "y"],
vdims=["intensity"],
).opts(
opts.Image(
frame_width=self.frame_width,
tools=["hover"],
cmap=self.colormap,
cnorm=self.colormap_scale,
data_aspect=1.0,
)
)
return (img * hv.VLine(x) * hv.HLine(self.idx_y)).opts(
opts.VLine(
color="red",
line_width=0.5,
),
opts.HLine(
color="red",
line_width=0.5,
),
)
[docs]
def yz_view(self, x, y):
"""YZ view."""
z = int(y)
# side
yzview = self.recon[:, :, int(x)]
# frame width
frame_width = int(self.frame_width * self.recon.shape[0] / self.recon.shape[1])
img = hv.Image(
(
np.arange(yzview.shape[0]),
np.arange(yzview.shape[1]),
yzview.T,
),
kdims=["y", "z"],
vdims=["intensity"],
).opts(
opts.Image(
frame_width=frame_width,
tools=["hover"],
cmap=self.colormap,
cnorm=self.colormap_scale,
data_aspect=1.0,
)
)
return (img * hv.VLine(self.idx_y) * hv.HLine(z)).opts(
opts.VLine(
color="red",
line_width=0.5,
),
opts.HLine(
color="red",
line_width=0.5,
),
)
[docs]
@param.depends(
"frame_width",
"idx_y",
"colormap",
"colormap_scale",
)
def recon_orthorgonal_view(self):
"""Orthogonal view of the reconstruction."""
if self.recon is None:
return pn.pane.Markdown("no reconstruction to show")
# control
y_slider = pn.widgets.IntSlider.from_param(
self.param.idx_y,
name="y",
start=0,
end=self.recon.shape[0] - 1,
step=1,
)
# -- main view
main = self.xz_view()
crosshair = streams.PointerXY(x=0, y=0, source=main)
crosshair_dmap = hv.DynamicMap(self.cross_hair_view, streams=[crosshair])
main_view = main * crosshair_dmap
# -- side view
side_view = hv.DynamicMap(self.yz_view, streams=[crosshair])
# -- bottom view
bottom_view = hv.DynamicMap(self.xy_view, streams=[crosshair])
# -- composite viewer
viewer = (main_view.hist() + side_view + bottom_view).cols(2)
return pn.Column(y_slider, viewer)
[docs]
def plot_control(self, width=80):
"""Plot control."""
# color map
cmap = pn.widgets.Select.from_param(
self.param.colormap,
name="colormap",
)
cmapscale = pn.widgets.Select.from_param(
self.param.colormap_scale,
name="colormap scale",
)
framewidth = pn.widgets.LiteralInput.from_param(
self.param.frame_width,
name="frame width",
)
plot_pn = pn.Card(
cmap,
cmapscale,
framewidth,
width=width,
header="Diaply",
collapsible=True,
)
return plot_pn
[docs]
def panel(self):
"""App panel view."""
width = self.frame_width // 2
# side panel
side_pn = pn.Column(
self.plot_control(width=width),
width=int(width * 1.1),
)
# main panel
main_pn = self.recon_orthorgonal_view
#
app = pn.Row(side_pn, main_pn)
return app