Source code for ectopylasm.visualize

"""Visualization of point cloud data and geometrical shapes."""

import logging

import numpy as np
import ipyvolume as ipv
import pptk

from ectopylasm import geometry


LOGGER = logging.getLogger('ectopylasm.visualize')
LOGGER.setLevel(logging.INFO)


[docs]def random_sample(xyz, total, sample_frac): """ Get a random sample from a point cloud. xyz: array of shape (3, N) representing N points in 3D space total: number of points in xyz sample_frac: fraction of the total set that you want to return """ sample_size = int(sample_frac * total) sample = np.random.choice(total, sample_size, replace=False) LOGGER.debug("sample size: %i out of total %i", sample_size, total) return dict(x=xyz['x'][sample], y=xyz['y'][sample], z=xyz['z'][sample])
[docs]def ipv_plot_plydata(plydata, sample_frac=1, marker='circle_2d', **kwargs): """Plot vertices in a plydata object using ipyvolume.""" if sample_frac < 1: xyz = random_sample(plydata['vertex'], plydata['vertex'].count, sample_frac) else: xyz = dict(x=plydata['vertex']['x'], y=plydata['vertex']['y'], z=plydata['vertex']['z']) fig = ipv.scatter(**xyz, marker=marker, **kwargs) return fig
[docs]def pptk_plot_plydata(plydata, **kwargs): """Plot vertices in a plydata object using pptk.""" pptk.viewer(np.array([plydata['vertex']['x'], plydata['vertex']['y'], plydata['vertex']['z']]).T, **kwargs)
[docs]def ipv_plot_df(points_df, sample_frac=1, marker='circle_2d', size=0.2, **kwargs): """Plot vertices in a dataframe using ipyvolume.""" if sample_frac < 1: xyz = random_sample(points_df, len(points_df), sample_frac) else: xyz = dict(x=points_df['x'].values, y=points_df['y'].values, z=points_df['z'].values) fig = ipv.scatter(**xyz, marker=marker, size=size, **kwargs) return fig
[docs]def pptk_plot_df(points_df, **kwargs): """Plot vertices in a dataframe using pptk.""" pptk.viewer(np.array([points_df['x'], points_df['y'], points_df['z']]).T, **kwargs)
[docs]def plot_plane(plane: geometry.Plane, x_lim=None, y_lim=None, z_lim=None, limit_all=True, **kwargs): """ Draw a plane. The limited coordinates are called x and z, corresponding to the first and third components of `p` and `n`. The final y coordinate is calculated based on the equation for a plane. plane: a Plane object x_lim [optional]: iterable of the two extrema in the x direction y_lim [optional]: same as x, but for y z_lim [optional]: same as x, but for z limit_all [optional]: make sure that the plane surface plot is bound within all given limits """ if x_lim is None: x_lim = ipv.pylab.gcf().xlim if y_lim is None: y_lim = ipv.pylab.gcf().ylim if z_lim is None: z_lim = ipv.pylab.gcf().zlim x, y, z = geometry.plane_surface(plane, x_lim=x_lim, y_lim=y_lim, z_lim=z_lim) fig = ipv.plot_surface(x, y, z, **kwargs) if limit_all: if np.any(x < x_lim[0]) or np.any(x > x_lim[1]): ipv.pylab.xlim(*x_lim) if np.any(y < y_lim[0]) or np.any(y > y_lim[1]): ipv.pylab.ylim(*y_lim) if np.any(z < z_lim[0]) or np.any(z > z_lim[1]): ipv.pylab.zlim(*z_lim) return fig
[docs]def plot_thick_plane(plane: geometry.Plane, thickness=0, **kwargs): """ Draw two co-planar planes, separated by a distance `thickness`. plane: a central Plane object thickness: the distance between the two co-planar planes x_lim [optional]: iterable of the two extrema in the x direction z_lim [optional]: same as x, but for z d [optional]: if d is known (in-product of p and n), then this can be supplied directly; p is disregarded in this case. """ if thickness <= 0: fig = plot_plane(plane, **kwargs) else: plane_1, plane_2 = geometry.thick_plane_planes(plane, thickness) plot_plane(plane_1, **kwargs) fig = plot_plane(plane_2, **kwargs) return fig
[docs]def plot_plane_fit(fit_result, **kwargs): """Plot the plane resulting from a plane fit to a point set.""" p_fit = fit_result.params plane = geometry.Plane(p_fit['a'], p_fit['b'], p_fit['c'], p_fit['d']) fig = plot_plane(plane, **kwargs) return fig
[docs]def plot_cone(cone: geometry.Cone, n_steps=20, **kwargs): """ Draw a cone surface. cone: a Cone object n_steps: number of steps in the parametric range used for drawing (more gives a smoother surface, but may render more slowly) """ fig = ipv.plot_surface(*geometry.cone_surface(cone, n_steps=n_steps), **kwargs) return fig
[docs]def plot_thick_cone(cone: geometry.Cone, thickness, **kwargs): """ Plot two cones separated by a distance `thickness`. Parameters: same as plot_cone, plus `thickness`. """ cone_1, cone_2 = geometry.thick_cone_cones(cone, thickness) plot_cone(cone_1, **kwargs) kwargs.pop('color', None) fig = plot_cone(cone_2, color='blue', **kwargs) return fig
[docs]def plot_cone_fit(fit_result, **kwargs): """Plot the cone resulting from a cone fit to a point set.""" cone = geometry.Cone(*fit_result['x'][:4], base_pos=geometry.Point(*fit_result['x'][4:])) fig = plot_cone(cone, **kwargs) return fig
[docs]def plot(data, *args, **kwargs): """ Wraps plotting functions for use in Jupyter notebooks. Based on the type of `data`, this function will in turn call the following: - `pandas.DataFrame` or `vaex.DataFrame`: `ipv_plot_df`. - `geometry.Plane`: `plot_plane`. - `geometry.Cone`: `plot_cone`. See the documentation of those functions for how to call `plot`. All arguments and keyword arguments are passed on to the wrapped functions. """ import pandas as pd import vaex if isinstance(data, (pd.DataFrame, vaex.dataframe.DataFrame)): ipv_plot_df(data, *args, **kwargs) elif isinstance(data, geometry.Plane): plot_plane(data, *args, **kwargs) elif isinstance(data, geometry.Cone): plot_cone(data, *args, **kwargs) else: raise TypeError("Type of `data` not supported.")
[docs]def clear(): """Call ipyvolume.clear().""" ipv.clear()
[docs]def show(): """Call `ipyvolume.show()`.""" ipv.show()