"""3D visualisation inside the notebook."""
import warnings
import numpy as np
import pandas as pd
from ipywidgets import interact
from matplotlib import colormaps
from ..config.draw import sheet_spec
from ..utils.utils import get_sub_eptm, spec_updater
try:
import ipyvolume as ipv
except ImportError:
print(
"""
This module needs ipyvolume to work.
You can install it with:
$ conda install -c conda-forge ipyvolume
"""
)
[docs]def browse_history(
history, coords=["x", "y", "z"], start=None, stop=None, size=None, **draw_specs_kw
):
times = history.slice(start, stop, size)
num_frames = times.size
draw_specs = sheet_spec()
spec_updater(draw_specs, draw_specs_kw)
sheet = history.retrieve(0)
ipv.clear()
fig, meshes = sheet_view(sheet, coords, **draw_specs_kw)
lim_inf = sheet.vert_df[sheet.coords].min().min()
lim_sup = sheet.vert_df[sheet.coords].max().max()
ipv.xyzlim(lim_inf, lim_sup)
def set_frame(i=0):
fig.animation = 0
t = times[i]
meshes = _get_meshes(history.retrieve(t), coords, draw_specs)
update_view(fig, meshes)
ipv.show()
interact(set_frame, i=(0, num_frames - 1))
[docs]def update_view(fig, meshes):
for old, new in zip(fig.meshes, meshes):
old.x = new.x
old.y = new.y
old.z = new.z
old.color = new.color
old.triangles = new.triangles
old.lines = new.lines
[docs]def sheet_view(sheet, coords=["x", "y", "z"], **draw_specs_kw):
"""
Creates a javascript renderer of the edge lines to be displayed
in Jupyter Notebooks
Returns
-------
fig: a :class:`ipyvolume.widgets.Figure` widget
mesh: a :class:`ipyvolume.widgets.Mesh` mesh widget
"""
# ipv.style.use(["dark", "minimal"])
draw_specs = sheet_spec()
spec_updater(draw_specs, draw_specs_kw)
fig = ipv.gcf()
fig.meshes = fig.meshes + _get_meshes(sheet, coords, draw_specs)
box_size = max(*(np.ptp(sheet.vert_df[u]) for u in sheet.coords))
border = 0.05 * box_size
lim_inf = sheet.vert_df[sheet.coords].min().min() - border
lim_sup = sheet.vert_df[sheet.coords].max().max() + border
ipv.xyzlim(lim_inf, lim_sup)
return fig, fig.meshes
[docs]def view_ipv(sheet, coords=["x", "y", "z"], **edge_specs):
"""
Creates a javascript renderer of the edge lines to be displayed
in Jupyter Notebooks
Returns
-------
fig: a :class:`ipyvolume.widgets.Figure` widget
mesh: a :class:`ipyvolume.widgets.Mesh` mesh widget
"""
warnings.warn("`view_ipv` is deprecated, use the more generic `sheet_view`")
mesh = edge_mesh(sheet, coords, **edge_specs)
fig = ipv.gcf()
fig.meshes = fig.meshes + [mesh]
box_size = max(*(np.ptp(sheet.vert_df[u]) for u in sheet.coords))
border = 0.05 * box_size
lim_inf = sheet.vert_df[sheet.coords].min().min() - border
lim_sup = sheet.vert_df[sheet.coords].max().max() + border
ipv.xyzlim(lim_inf, lim_sup)
return fig, mesh
[docs]def edge_mesh(sheet, coords, **edge_specs):
"""
Creates a ipyvolume Mesh of the edge lines to be displayed
in Jupyter Notebooks
Returns
-------
mesh: a :class:`ipyvolume.widgets.Mesh` mesh widget
"""
spec = sheet_spec()["edge"]
spec.update(**edge_specs)
if callable(spec["color"]):
spec["color"] = spec["color"](sheet)
if isinstance(spec["color"], str):
color = spec["color"]
elif hasattr(spec["color"], "__len__"):
color = _wire_color_from_sequence(spec, sheet)[:, :3]
u, v, w = coords
mesh = ipv.Mesh(
x=sheet.vert_df[u],
y=sheet.vert_df[v],
z=sheet.vert_df[w],
lines=sheet.edge_df[["srce", "trgt"]].astype(dtype=np.uint32),
color=color,
)
return mesh
[docs]def face_mesh(sheet, coords, **face_draw_specs):
"""
Creates a ipyvolume Mesh of the face polygons
"""
Ne, Nf = sheet.Ne, sheet.Nf
if callable(face_draw_specs["color"]):
face_draw_specs["color"] = face_draw_specs["color"](sheet)
if isinstance(face_draw_specs["color"], str):
color = face_draw_specs["color"]
elif hasattr(face_draw_specs["color"], "__len__"):
color = _face_color_from_sequence(face_draw_specs, sheet)[:, :3]
if "visible" in sheet.face_df.columns:
edges = sheet.edge_df[sheet.upcast_face(sheet.face_df["visible"])].index
_sheet = get_sub_eptm(sheet, edges)
if _sheet is not None:
sheet = _sheet
if isinstance(color, np.ndarray):
faces = sheet.face_df["face_o"].values.astype(np.uint32)
edges = edges.values.astype(np.uint32)
indexer = np.concatenate([faces, edges + Nf, edges + Ne + Nf])
color = color.take(indexer, axis=0)
epsilon = face_draw_specs.get("epsilon", 0)
up_srce = sheet.edge_df[["s" + c for c in coords]]
up_trgt = sheet.edge_df[["t" + c for c in coords]]
Ne, Nf = sheet.Ne, sheet.Nf
if epsilon > 0:
up_face = sheet.edge_df[["f" + c for c in coords]].values
up_srce = (up_srce - up_face) * (1 - epsilon) + up_face
up_trgt = (up_trgt - up_face) * (1 - epsilon) + up_face
mesh_ = np.concatenate(
[sheet.face_df[coords].values, up_srce.values, up_trgt.values]
)
triangles = np.vstack(
[sheet.edge_df["face"], np.arange(Ne) + Nf, np.arange(Ne) + Ne + Nf]
).T.astype(dtype=np.uint32)
mesh = ipv.Mesh(
x=mesh_[:, 0], y=mesh_[:, 1], z=mesh_[:, 2], triangles=triangles, color=color
)
return mesh
def _wire_color_from_sequence(edge_spec, sheet):
""" """
color_ = edge_spec["color"]
cmap = colormaps[edge_spec.get("colormap", "viridis")]
if color_.shape in [(sheet.Nv, 3), (sheet.Nv, 4)]:
return np.asarray(color_)
if color_.shape == (sheet.Nv,):
if np.ptp(color_) < 1e-10:
return np.ones((sheet.Nv, 3)) * 0.7
return cmap((color_ - color_.min()) / np.ptp(color_))
if color_.shape in [(sheet.Ne, 3), (sheet.Ne, 4)]:
color_ = pd.DataFrame(color_, index=sheet.edge_df.index)
color_["srce"] = sheet.edge_df["srce"]
color_ = color_.groupby("srce").mean().values
return color_
if color_.shape == (sheet.Ne,):
color_ = pd.DataFrame(color_, index=sheet.edge_df.index)
color_["srce"] = sheet.edge_df["srce"]
color_ = color_.groupby("srce").mean().values.ravel()
if np.ptp(color_) < 1e-10:
warnings.warn("Attempting to draw a colormap " "with a uniform value")
return np.ones((sheet.Nv, 3)) * 0.7
return cmap((color_ - color_.min()) / np.ptp(color_))
else:
raise ValueError("The 'color' value of the spec doesn't have a correct shape.")
def _face_color_from_sequence(face_spec, sheet):
color_ = face_spec["color"]
cmap = colormaps[face_spec.get("colormap", "viridis")]
Nf, Ne = sheet.Nf, sheet.Ne
color_min, color_max = face_spec.get("color_range", (color_.min(), color_.max()))
face_mesh_shape = Nf + 2 * Ne
if color_.shape in [(sheet.Nf, 3), (sheet.Nf, 4)]:
return np.concatenate([color_, color_, color_])
elif color_.shape == (sheet.Nf,):
if np.ptp(color_) < 1e-10:
# warnings.warn("Attempting to draw a colormap with a uniform value")
return np.ones((face_mesh_shape, 3)) * 0.5
normed = (color_ - color_min) / (color_max - color_min)
up_color = sheet.upcast_face(normed).values
return cmap(np.concatenate([normed, up_color, up_color]))
else:
raise ValueError(
"shape of `face_spec['color']` must be either (Nf, 3), (Nf, 4) or (Nf,)"
)
def _get_meshes(sheet, coords, draw_specs):
meshes = []
edge_spec = draw_specs["edge"]
if edge_spec["visible"]:
edges = edge_mesh(sheet, coords, **edge_spec)
meshes.append(edges)
else:
edges = None
face_spec = draw_specs["face"]
if face_spec["visible"]:
faces = face_mesh(sheet, coords, **face_spec)
meshes.append(faces)
else:
faces = None
return meshes