stpyvista icon indicating copy to clipboard operation
stpyvista copied to clipboard

Problem with PolyData and add_point_labels

Open DomenicoGaudioso opened this issue 9 months ago • 6 comments

Hi everyone,

I'm attempting to integrate some three-dimensional visualization examples using PyVista into a Streamlit application. I'm encountering difficulties with two specific examples:

  1. Creating a truss (https://docs.pyvista.org/version/stable/examples/00-load/create-truss#sphx-glr-examples-00-load-create-truss-py) I'm trying to adapt this example to display a truss in a Streamlit application. I have problem with visualization with using PolyData.

  2. Calculating distance along a spline (https://docs.pyvista.org/version/stable/examples/02-plot/distance-along-spline#sphx-glr-examples-02-plot-distance-along-spline-py) I have problem with visualization when use the add_point_labels function to label points along a spline.

I would appreciate if someone could offer some advice on resolving these issues and making the examples work in Streamlit. Thank you in advance for your help!

Domenico

DomenicoGaudioso avatar May 06 '24 22:05 DomenicoGaudioso

Hi @DomenicoGaudioso, iirc, rendering lines as tubes and points as spheres are limitations from vtk-js, the library used to bring PyVista plotters to the browser: https://github.com/pyvista/pyvista/issues/5444

For example 1, my alternative would be to draw individual lines and points in the plotter:

Code:
import numpy as np
import pyvista as pv
import streamlit as st
from stpyvista import stpyvista
from matplotlib.cm import get_cmap


## Define geometry
nodes = [
    [0.0, 0.0, 0.0],
    [0.0, 1.0, 0.0],
    [4.0, 3.0, 0.0],
    [4.0, 0.0, 0.0],
    [0.0, 1.0, 2.0],
    [4.0, 1.0, 2.0],
    [4.0, 3.0, 2.0],
]

edges = [
    [0, 4],
    [1, 4],
    [3, 4],
    [5, 4],
    [6, 4],
    [3, 5],
    [2, 5],
    [5, 6],
    [2, 6],
]

## Generate colors for edges
cmap = get_cmap("hsv")
colors = map(cmap, np.linspace(0, 1, len(edges)))

## Create Plotter
plotter = pv.Plotter(window_size=(300, 300))
plotter.background_color = "salmon"

## Add nodes as points
plotter.add_points(np.array(nodes), point_size=20, color="purple")

## Add edges as lines
for edge, color in zip(edges, colors):
    i, j = edge
    line = np.array([nodes[i], nodes[j]])
    plotter.add_lines(line, color=color, width=10)

plotter.view_isometric()


## Send to streamlit
st.title("Truss")

stpyvista(
    plotter,
    panel_kwargs=dict(
        orientation_widget=True,
    ),
)

edsaac avatar May 10 '24 15:05 edsaac

Thanks for the reply, I am also using this alternative but I noticed that it is heavier (slowness in image processing). Thank you for the feedback

DomenicoGaudioso avatar May 10 '24 16:05 DomenicoGaudioso

Perhaps it's because I was looping through each edge element and adding it to the pyvista Plotter. We could try a couple things to improve performance:

  • Decorate with st.cache_resource a function in charge of generating the Plotter
  • Assembling a single array of edges/coordinates to pass to Plotter.add_lines
  • Pass a key to stpyvista to avoid re-rendering of the 3D view at each interaction with the web-app
Code:
import numpy as np
import pyvista as pv
import streamlit as st
from stpyvista import stpyvista


@st.cache_resource
def mesh_to_plotter(nodes: list[list[float]], edges: list[list[int]]):
    ## Create Plotter
    plotter = pv.Plotter(window_size=(300, 300))
    plotter.background_color = "salmon"

    ## Add nodes as points
    plotter.add_points(np.array(nodes), point_size=20, color="purple")

    ## Assemble edges as single array
    coord_edges = (
        np.array([[nodes[i], nodes[j]] for (i, j) in edges])
        .flatten()
        .reshape((2 * len(edges), 3))
    )

    plotter.add_lines(coord_edges, color="k", width=10)
    plotter.view_isometric()

    return plotter


def main():
    ## Define geometry
    nodes = [
        [0.0, 0.0, 0.0],
        [0.0, 1.0, 0.0],
        [4.0, 3.0, 0.0],
        [4.0, 0.0, 0.0],
        [0.0, 1.0, 2.0],
        [4.0, 1.0, 2.0],
        [4.0, 3.0, 2.0],
    ]

    edges = [
        [0, 4],
        [1, 4],
        [3, 4],
        [5, 4],
        [6, 4],
        [3, 5],
        [2, 5],
        [5, 6],
        [2, 6],
    ]

    ## Create plotter
    plotter = mesh_to_plotter(nodes, edges)

    ## Send to streamlit
    st.title("Truss")

    stpyvista(
        plotter,
        panel_kwargs=dict(
            orientation_widget=True,
        ),
        key="stpv_truss",
    )


if __name__ == "__main__":
    main()

edsaac avatar May 10 '24 20:05 edsaac

Great idea. One question: How can I set the color of the line based on, say, the 'Z' values of the points? In this example, they use 'cmap'. https://docs.pyvista.org/version/stable/examples/00-load/create-truss#sphx-glr-examples-00-load-create-truss-py

DomenicoGaudioso avatar May 11 '24 11:05 DomenicoGaudioso

Is it possible to add a list of colors in color? I tried but with no success.

  Error:  Must be a string, rgb(a) sequence, or hex color string.  For example:
            color='white'
            color='w'
            color=[1.0, 1.0, 1.0]
            color=[255, 255, 255]
            color='#FFFFFF'

DomenicoGaudioso avatar May 15 '24 23:05 DomenicoGaudioso

I don't think that will be possible because a single lines actor is generated from a single pv.Plotter.add_lines call.

edsaac avatar May 17 '24 11:05 edsaac