Skip to content

Commit

Permalink
improvements to applications.MorphPlotter
Browse files Browse the repository at this point in the history
  • Loading branch information
marcomusy committed Mar 11, 2024
1 parent 1529544 commit adf5c6b
Show file tree
Hide file tree
Showing 7 changed files with 111 additions and 39 deletions.
6 changes: 4 additions & 2 deletions docs/changes.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,16 @@
- add `utils.circle_from_3points()` function.
- add example `examples/other/iminuit2.py`
- add `rotation=..` to `Arrow2D()` class
- improvements to `applications.MorphPlotter`


## Soft Breaking Changes
## Soft-breaking Changes
Changes that will break existing code whose fixing is trivial:

- None


## Hard Breaking Changes
## Hard-breaking Changes
Changes that will break existing code and need active thinking and work to adapt

- None
Expand All @@ -27,6 +28,7 @@ Changes that will break existing code and need active thinking and work to adapt
```
examples/other/magic-class1.py
examples/other/iminuit2.py
examples/advanced/warp4b.py
```

### Broken Examples
Expand Down
8 changes: 8 additions & 0 deletions docs/examples_db.js
Original file line number Diff line number Diff line change
Expand Up @@ -1903,6 +1903,14 @@ vedo_example_db =
long : 'Visualize a vedo mesh in the '+insertLink('napari','napari.org/')+' image viewer. Check out also the '+insertLink('napari-vedo plugin','github.com/jo-mueller/napari-vedo-bridge')+'for napari.',
imgsrc: 'images/other/napari1.png',
},
{
pyname: 'magic-class1',
kbd : '',
categ : 'other',
short : 'magic-class library',
long : 'Visualize objects using the '+insertLink('magic-class','github.com/hanjinliu/magic-class')+' library.',
imgsrc: 'images/other/magic-class1.png',
},
{
pyname: 'dolfin/elasticity2',
kbd : '',
Expand Down
10 changes: 6 additions & 4 deletions examples/advanced/warp4b.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
# self.at(0).add(source, self.msg0, self.instructions).reset_camera()
# self.at(1).add(f"Reference {target.filename}", self.msg1, target)
# cam1 = self.camera # save camera at 1
# self.at(2).add("Morphing Output", target, Axes(target)).background("k9")
# self.at(2).add("Morphing Output", target).background("k9")
# self.camera = cam1 # use the same camera of renderer1
#
# self.callid1 = self.add_callback("on key press", self.on_keypress)
Expand Down Expand Up @@ -99,13 +99,15 @@
################################################################################

settings.default_font = "Calco"
settings.enable_default_mouse_callbacks = False

source = Mesh(dataurl+"limb_surface.vtk").color("k5")
source.rotate_y(90).rotate_z(-60).rotate_x(40)
target = Mesh(dataurl+"290.vtk").cut_with_plane(origin=(1,0,0)).color("yellow5")

plt = MorphPlotter(source, target, size=(2490, 850))
target = Mesh(dataurl+"290.vtk").color("yellow5")
target.rotate_y(-40)

plt = MorphPlotter(source, target, size=(2490, 850), axes=14)
plt.cmap_name = "RdYlBu_r"
plt.show()
plt.close()

14 changes: 7 additions & 7 deletions vedo/addons.py
Original file line number Diff line number Diff line change
Expand Up @@ -3687,7 +3687,7 @@ def Axes(
if xlabel_backface_color is None:
bfc = 1 - np.array(get_color(xlabel_color))
xlab.backcolor(bfc)
xlab.name = f"xNumericLabel{i}"
xlab.name = f"xNumericLabel {i}"
labels.append(xlab)

if ylabel_size and ytitle:
Expand Down Expand Up @@ -3740,7 +3740,7 @@ def Axes(
if ylabel_backface_color is None:
bfc = 1 - np.array(get_color(ylabel_color))
ylab.backcolor(bfc)
ylab.name = f"yNumericLabel{i}"
ylab.name = f"yNumericLabel {i}"
labels.append(ylab)

if zlabel_size and ztitle:
Expand Down Expand Up @@ -3792,7 +3792,7 @@ def Axes(
if zlabel_backface_color is None:
bfc = 1 - np.array(get_color(zlabel_color))
zlab.backcolor(bfc)
zlab.name = f"zNumericLabel{i}"
zlab.name = f"zNumericLabel {i}"
labels.append(zlab)

################################################ axes titles
Expand Down Expand Up @@ -3857,7 +3857,7 @@ def Axes(
xt.use_bounds(x_use_bounds)
if xtitle == " ":
xt.use_bounds(False)
xt.name = f"xtitle {xtitle}"
xt.name = "xtitle"
titles.append(xt)
if xtitle_box:
titles.append(xt.box(scale=1.1).use_bounds(x_use_bounds))
Expand Down Expand Up @@ -3923,7 +3923,7 @@ def Axes(
yt.use_bounds(y_use_bounds)
if ytitle == " ":
yt.use_bounds(False)
yt.name = f"ytitle {ytitle}"
yt.name = "ytitle"
titles.append(yt)
if ytitle_box:
titles.append(yt.box(scale=1.1).use_bounds(y_use_bounds))
Expand Down Expand Up @@ -3985,7 +3985,7 @@ def Axes(
zt.use_bounds(z_use_bounds)
if ztitle == " ":
zt.use_bounds(False)
zt.name = f"ztitle {ztitle}"
zt.name = "ztitle"
titles.append(zt)

################################################### header title
Expand All @@ -4009,7 +4009,7 @@ def Axes(
htit.rotate_x(htitle_rotation)
wpos = [htitle_offset[0]*dx, (1 + htitle_offset[1])*dy, htitle_offset[2]*dz]
htit.shift(np.array(wpos) + [0, 0, xyshift*dz])
htit.name = f"htitle {htitle}"
htit.name = "htitle"
titles.append(htit)

######
Expand Down
105 changes: 81 additions & 24 deletions vedo/applications.py
Original file line number Diff line number Diff line change
Expand Up @@ -483,7 +483,11 @@ class MorphPlotter(Plotter):

def __init__(self, source, target, **kwargs):

kwargs.update(dict(N=3, sharecam=0))
vedo.settings.enable_default_keyboard_callbacks = False
vedo.settings.enable_default_mouse_callbacks = False

kwargs.update({"N": 3})
kwargs.update({"sharecam": 0})
super().__init__(**kwargs)

self.source = source.pickable(True)
Expand All @@ -492,29 +496,34 @@ def __init__(self, source, target, **kwargs):
self.sources = []
self.targets = []
self.warped = None
self.source_labels = None
self.target_labels = None
self.automatic_picking_distance = 0.075
self.cmap_name = "coolwarm"
self.nbins = 25
self.msg0 = Text2D("Pick a point on the surface",
pos="bottom-center", c='white', bg="blue4", alpha=1, font="Calco")
self.msg1 = Text2D(pos="bottom-center", c='white', bg="blue4", alpha=1, font="Calco")
instructions = (
"Morphological alignment of 3D surfaces.\n"
self.instructions = Text2D(s=0.7, bg="blue4", alpha=0.1, font="Calco")
self.instructions.text(
" Morphological alignment of 3D surfaces\n\n"
"Pick a point on the source surface, then\n"
"pick the corresponding point on the target\n"
"pick the corresponding point on the target \n"
"Pick at least 4 point pairs. Press:\n"
"- c to clear all landmarks\n"
"- d to delete the last pair\n"
"- d to delete the last landmark pair\n"
"- a to auto-pick additional landmarks\n"
"- z to compute and show the residuals\n"
"- q to quit and proceed"
)
self.instructions = Text2D(instructions, s=0.7, bg="blue4", alpha=0.1, font="Calco")
self.at(0).add_renderer_frame().add(source, self.msg0, self.instructions).reset_camera()
self.at(0).add_renderer_frame()
self.add(source, self.msg0, self.instructions).reset_camera()
self.at(1).add_renderer_frame()
self.add(Text2D(f"Target: {target.filename[-35:]}", bg="blue4", alpha=0.1, font="Calco"))
self.add(self.msg1, target)
cam1 = self.camera # save camera at 1
self.at(2).background("k9")
self.add(Text2D("Morphing Output", font="Calco"))
self.add(target, vedo.Axes(target))
self.add(target, Text2D("Morphing Output", font="Calco"))
self.camera = cam1 # use the same camera of renderer1

self.add_renderer_frame()
Expand All @@ -529,12 +538,12 @@ def update(self):
target_pts = Points(self.targets).color("purple5").ps(12)
source_pts.name = "source_pts"
target_pts.name = "target_pts"
slabels = source_pts.labels2d("id", c="purple3")
tlabels = target_pts.labels2d("id", c="purple3")
slabels.name = "source_pts"
tlabels.name = "target_pts"
self.at(0).remove("source_pts").add(source_pts, slabels)
self.at(1).remove("target_pts").add(target_pts, tlabels)
self.source_labels = source_pts.labels2d("id", c="purple3")
self.target_labels = target_pts.labels2d("id", c="purple3")
self.source_labels.name = "source_pts"
self.target_labels.name = "target_pts"
self.at(0).remove("source_pts").add(source_pts, self.source_labels)
self.at(1).remove("target_pts").add(target_pts, self.target_labels)
self.render()

if len(self.sources) == len(self.targets) and len(self.sources) > 3:
Expand Down Expand Up @@ -571,7 +580,11 @@ def on_keypress(self, evt):
self.source.pickable(True)
self.target.pickable(False)
self.update()
elif evt.keypress == "d":
if evt.keypress == "w":
rep = (self.warped.properties.GetRepresentation() == 1)
self.warped.wireframe(not rep)
self.render()
if evt.keypress == "d":
n = min(len(self.sources), len(self.targets))
self.sources = self.sources[:n-1]
self.targets = self.targets[:n-1]
Expand All @@ -580,25 +593,69 @@ def on_keypress(self, evt):
self.source.pickable(True)
self.target.pickable(False)
self.update()
elif evt.keypress == "z":
if evt.keypress == "a":
# auto-pick points on the target surface
if not self.warped:
vedo.printc("At least 4 points are needed.", c="r")
return
pts = self.target.clone().subsample(self.automatic_picking_distance)
if len(self.sources) > len(self.targets):
self.sources.pop()
d = self.target.diagonal_size()
r = d * self.automatic_picking_distance
TI = self.warped.transform.compute_inverse()
for p in pts.coordinates:
pp = vedo.utils.closest(p, self.targets)[1]
if vedo.mag(pp - p) < r:
continue
q = self.warped.closest_point(p)
self.sources.append(TI(q))
self.targets.append(p)
self.source.pickable(True)
self.target.pickable(False)
self.update()
if evt.keypress == "z" or evt.keypress == "a":
self.nbins = 25
dists = self.warped.distance_to(self.target, signed=True)
mind, maxd = np.min(dists), np.max(dists)
v = min(abs(mind), abs(maxd))
v = np.std(dists) * 2
self.warped.cmap(self.cmap_name, dists, vmin=-v, vmax=+v)

h = vedo.pyplot.histogram(
dists,
bins=25,
title="Residuals",
bins=self.nbins,
title=" ",
xtitle=f"STD = {v/2:.2f}",
ytitle="",
c=self.cmap_name,
xlim=(-v, v),
aspect=16/9,
axes=dict(text_scale=1.9),
axes=dict(
number_of_divisions=5,
text_scale=2,
xtitle_offset=0.075,
xlabel_justify="top-center"),
)
h = h.clone2d(pos="bottom-left", size=0.55)

# try to fit a gaussian to the histogram
def gauss(x, A, B, sigma):
return A + B * np.exp(-x**2 / (2 * sigma**2))
try:
from scipy.optimize import curve_fit
inits = [0, len(dists)/self.nbins*2.5, v/2]
popt, _ = curve_fit(gauss, xdata=h.centers, ydata=h.frequencies, p0=inits)
print(inits, popt)
x = np.linspace(-v, v, 300)
h += vedo.pyplot.plot(x, gauss(x, *popt), like=h, lw=1, lc="k2")
h["Axes"]["xtitle"].text(f":sigma = {abs(popt[2]):.3f}", font="VictorMono")
except:
pass

h = h.clone2d(pos="bottom-left", size=0.575)
h.name = "warped"
self.at(2).add(h)
self.render()
elif evt.keypress == "q":

if evt.keypress == "q":
self.break_interaction()


Expand Down
5 changes: 4 additions & 1 deletion vedo/shapes.py
Original file line number Diff line number Diff line change
Expand Up @@ -4103,6 +4103,7 @@ def __init__(
self.init_scale = s
self.name = "Text3D"
self.txt = txt
self.justify = justify

def text(
self,
Expand All @@ -4113,7 +4114,7 @@ def text(
vspacing=2.15,
depth=0,
italic=False,
justify="bottom-left",
justify="",
literal=False,
) -> "Text3D":
"""
Expand All @@ -4123,6 +4124,8 @@ def text(
"""
if txt is None:
return self.txt
if not justify:
justify = self.justify

poly = self._get_text3d_poly(
txt, self.init_scale * s, font, hspacing, vspacing,
Expand Down
2 changes: 1 addition & 1 deletion vedo/version.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
_version = '2024.5.1+dev05'
_version = '2024.5.1+dev06'

0 comments on commit adf5c6b

Please sign in to comment.