Skip to content

Trimesh: document associating data with simplices #1110

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
Huite opened this issue Aug 22, 2022 · 4 comments
Open

Trimesh: document associating data with simplices #1110

Huite opened this issue Aug 22, 2022 · 4 comments
Milestone

Comments

@Huite
Copy link

Huite commented Aug 22, 2022

Is your feature request related to a problem? Please describe.

(I think this question applies to the Holoviews/Geoviews trimesh as well.)

Having the option to efficiently visualize large irregular meshes via datashader is quite useful. However, unless I'm mistaken, the trimesh currently only supports data on the nodes. This does the trick for e.g. finite element computation, but not for finite volume, where the data is associated with the simplex/face/cell (more similar to raster data).

(If my understanding is incorrect, I'd be happy to hear how to do it -- I think neither the docs/examples nor the source code show it in that case!)

Describe the solution you'd like

Ideally, I can just give some data that matches the length of the simplices, and get a triangle-by-triangle fill. For datashader, I suppose you could stick it in an additional column of the simplices dataframe.

With matplotlib, I can plot both data associated with the nodes via triplot or data on the simplices with PolyCollection (although the latter one seems a bit obscure).

Describe alternatives you've considered

Any alternative seems rather impractical (e.g. I could create a voronoi tesselation, then triangulate it to get data associated with the cell centroids to get something on the nodes).

Add any other context or screenshots about the feature request here.

I guess this relates somewhat to holoviz/holoviews#3812.

It's relatively straightforward to triangulate a mesh of (convex) cells in a vectorized manner, storing the indices to grab the values, e.g.:

IntArray = np.ndarray
IntDtype = np.int64


def _triangulate(i: IntArray, j: IntArray, n_triangle_per_row: IntArray) -> IntArray:
    n_triangle = n_triangle_per_row.sum()
    n_face = len(i)
    index_first = np.argwhere(np.diff(i, prepend=-1) != 0)
    index_second = index_first + 1
    index_last = np.argwhere(np.diff(i, append=-1) != 0)

    first = np.full(n_face, False)
    first[index_first] = True
    second = np.full(n_face, True) & ~first
    second[index_last] = False
    third = np.full(n_face, True) & ~first
    third[index_second] = False

    triangles = np.empty((n_triangle, 3), IntDType)
    triangles[:, 0] = np.repeat(j[first], n_triangle_per_row)
    triangles[:, 1] = j[second]
    triangles[:, 2] = j[third]
    return triangles


def triangulate(face_node_connectivity: IntArray, fill_value: int):
    n_face, n_max = face_node_connectivity.shape

    if n_max == 3:
        triangles = face_node_connectivity.copy()
        return triangles, np.arange(n_face)

    valid = face_node_connectivity != fill_value
    n_per_row = valid.sum(axis=1)
    n_triangle_per_row = n_per_row - 2
    i = np.repeat(np.arange(n_face), n_per_row)
    j = face_node_connectivity.ravel()[valid.ravel()]
    triangles = _triangulate(i, j, n_triangle_per_row)

    triangle_face_connectivity = np.repeat(
        np.arange(n_face), repeats=n_triangle_per_row
    )
    return triangles, triangle_face_connectivity

This would allow plotting of any mesh (triangles, quads, or even voronoi cells).

It should be easier than the node based visualization: there's no need to interpolate values inside of the triangles, just sampling suffices.

@ianthomas23
Copy link
Member

ianthomas23 commented Sep 9, 2022

Datashader already supports weights on the triangles as well as on the vertices. Here is an example showing both:

import datashader as ds
import pandas as pd

canvas = ds.Canvas(plot_width=200, plot_height=200)

# Vertex-based weights.
vertices = pd.DataFrame(
    [[0, 0, 1.0], [1, 0, 2.5], [0, 1, 1.5], [-1, 0, 0.5], [0, -1, 0.75]],
    columns=("x", "y", "vert_weight"))
triangles = pd.DataFrame(
    [[0, 1, 2], [0, 2, 3], [0, 3, 4], [0, 4, 1]],
    columns=["v0", "v1", "v2"])

agg = canvas.trimesh(vertices, triangles, agg=ds.sum("vert_weight"))
im = ds.transfer_functions.shade(agg, how="linear")
ds.utils.export_image(im, "vert_weight")

# Triangle-based weights.
vertices = pd.DataFrame(
    [[0, 0], [1, 0], [0, 1], [-1, 0], [0, -1]],
    columns=("x", "y"))
triangles = pd.DataFrame(
    [[0, 1, 2, 0.5], [0, 2, 3, 1.0], [0, 3, 4, 1.5], [0, 4, 1, 2.0]],
    columns=["v0", "v1", "v2", "tri_weight"])

agg = canvas.trimesh(vertices, triangles, agg=ds.sum("tri_weight"))
im = ds.transfer_functions.shade(agg, how="linear")
ds.utils.export_image(im, "tri_weight")

and the images produced:
vert_weight tri_weight

Weights are taken from the vertices DataFrame if there are more than 2 columns, otherwise they are taken from the triangles DataFrame.

@ianthomas23
Copy link
Member

Certainly the docs could be more explicit about this.

@jbednar
Copy link
Member

jbednar commented Sep 9, 2022

A PR adding the above example to the Datashader docs would be very welcome, @Huite !

@Huite
Copy link
Author

Huite commented Sep 10, 2022

Great! -- I'll put the PR on my (somewhat crowded) to do list!

@sophiamyang sophiamyang changed the title Trimesh: data associated with simplices ("faces") instead of nodes only? Trimesh: document associating data with simplices Sep 26, 2022
@sophiamyang sophiamyang added this to the v0.14.3 milestone Sep 26, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants