Reputation: 126
I want to create 3D objects consisting of 2D polygons, each of whcih has a texture consisting of a single jpeg-image. I have 3D coordinates of X, Y, and Z values for the polygons as well as texture coordinates in an [0, 1] interval. I can plot the 3D objects in matplotlib using Poly3DCollection
, but as I have read so far, matplotlib does not support texture mapping for polygons. I have found PyVista, which seems to be a good choice for texture mapping, but I don't understand how to create a PyVista-compatible dataset from my data. Here is my currently working matplotlib-example:
from mpl_toolkits.mplot3d import Axes3D
from mpl_toolkits.mplot3d.art3d import Poly3DCollection
import matplotlib.pyplot as plt
# six polygons consisting of points with X, Y, and Z coordinates
polygon_a = [
[
(371982, 5812893, 47),
(371987, 5812889, 47),
(371993, 5812896, 47),
(371988, 5812900, 47),
(371982, 5812893, 47),
]
]
polygon_b = [
[
(371987, 5812889, 44),
(371987, 5812889, 47),
(371982, 5812893, 47),
(371982, 5812893, 44),
(371987, 5812889, 44),
]
]
polygon_c = [
[
(371993, 5812896, 44),
(371993, 5812896, 47),
(371987, 5812889, 47),
(371987, 5812889, 44),
(371993, 5812896, 44),
]
]
polygon_d = [
[
(371982, 5812893, 44),
(371982, 5812893, 47),
(371988, 5812900, 47),
(371988, 5812900, 44),
(371982, 5812893, 44),
]
]
polygon_e = [
[
(371988, 5812900, 44),
(371988, 5812900, 47),
(371993, 5812896, 47),
(371993, 5812896, 44),
(371988, 5812900, 44),
]
]
polygon_f = [
[
(371987, 5812889, 44),
(371982, 5812893, 44),
(371988, 5812900, 44),
(371993, 5812896, 44),
(371987, 5812889, 44),
]
]
# texture coordinates of interval [0, 1]
texture_coords_a = [
0.993515,
0.590665,
0.583403,
0.995886,
0.001318,
0.409513,
0.411194,
0.00281,
0.993515,
0.590665,
]
texture_coords_b = [
0.814495,
0.004965,
0.986562,
0.175202,
0.172649,
0.994582,
0.004011,
0.820917,
0.814495,
0.004965,
]
texture_coords_c = [
0.992976,
0.869131,
0.867654,
0.99699,
0.009377,
0.134356,
0.138307,
0.010153,
0.992976,
0.869131,
]
texture_coords_d = [
0.007693,
0.148416,
0.15451,
0.00767,
0.994519,
0.86112,
0.844256,
0.998197,
0.007693,
0.148416,
]
texture_coords_e = [
0.997322,
0.660826,
0.89938,
0.990736,
0.006374,
0.337104,
0.106732,
0.00748,
0.997322,
0.660826,
]
# textures for some of the polygons as .jpg-files
img_a = "tex_2962910.jpg"
img_b = "tex_2962971.jpg"
img_c = "tex_2962990.jpg"
img_d = "tex_2962933.jpg"
img_e = "tex_2962915.jpg"
polygons = [polygon_a, polygon_b, polygon_c, polygon_d, polygon_e, polygon_f]
# 3D plot of the polygons using matplotlib, but without textures
fig = plt.figure()
ax = Axes3D(fig)
for polygon in polygons:
ax.add_collection3d(Poly3DCollection(polygon, alpha=0.5))
ax.set_xlim3d(371980, 371995)
ax.set_ylim3d(5812889, 5812902)
ax.set_zlim3d(44, 48)
plt.show()
I would appreciate any help in the right direction!
Upvotes: 1
Views: 1502
Reputation: 126
For anyone with a similar problem: I found two packages that are easy to use and provide the needed functionality of texture mapping to numpy polygons. The first is PyVista, the working code is the following:
import pyvista as pv
import numpy as np
from PIL import Image
# six polygons consisting of points with X, Y, and Z coordinates
polygon_a = [
(371982, 5812893, 47),
(371987, 5812889, 47),
(371993, 5812896, 47),
(371988, 5812900, 47),
(371982, 5812893, 47),
]
polygon_b = [
(371987, 5812889, 44),
(371987, 5812889, 47),
(371982, 5812893, 47),
(371982, 5812893, 44),
(371987, 5812889, 44),
]
polygon_c = [
(371993, 5812896, 44),
(371993, 5812896, 47),
(371987, 5812889, 47),
(371987, 5812889, 44),
(371993, 5812896, 44),
]
polygon_d = [
(371982, 5812893, 44),
(371982, 5812893, 47),
(371988, 5812900, 47),
(371988, 5812900, 44),
(371982, 5812893, 44),
]
polygon_e = [
(371988, 5812900, 44),
(371988, 5812900, 47),
(371993, 5812896, 47),
(371993, 5812896, 44),
(371988, 5812900, 44),
]
texture_coords_a = np.array(
[
[0.993515, 0.590665],
[0.583403, 0.995886],
[0.001318, 0.409513],
[0.411194, 0.00281],
[0.993515, 0.590665],
]
)
texture_coords_b = np.array(
[
[0.814495, 0.004965],
[0.986562, 0.175202],
[0.172649, 0.994582],
[0.004011, 0.820917],
[0.814495, 0.004965],
]
)
texture_coords_c = np.array(
[
[0.992976, 0.869131],
[0.867654, 0.99699],
[0.009377, 0.134356],
[0.138307, 0.010153],
[0.992976, 0.869131],
]
)
texture_coords_d = np.array(
[
[0.007693, 0.148416],
[0.15451, 0.00767],
[0.994519, 0.86112],
[0.844256, 0.998197],
[0.007693, 0.148416],
]
)
texture_coords_e = np.array(
[
[0.997322, 0.660826],
[0.89938, 0.990736],
[0.006374, 0.337104],
[0.106732, 0.00748],
[0.997322, 0.660826],
]
)
# define polygon faces for each polygon
faces_a = np.hstack([[4, 0, 1, 2, 3]])
faces_b = np.hstack([[4, 0, 1, 2, 3]])
faces_c = np.hstack([[4, 0, 1, 2, 3]])
faces_d = np.hstack([[4, 0, 1, 2, 3]])
faces_e = np.hstack([[4, 0, 1, 2, 3]])
# textures for some of the polygons as .jpg-files
img_a = "tex_2962910.jpg"
img_b = "tex_2962971.jpg"
img_c = "tex_2962990.jpg"
img_d = "tex_2962933.jpg"
img_e = "tex_2962915.jpg"
polygons = [polygon_a, polygon_b, polygon_c, polygon_d, polygon_e]
faces = [faces_a, faces_b, faces_c, faces_d, faces_e]
textures = [img_a, img_b, img_c, img_d, img_e]
texture_coords = [
texture_coords_a,
texture_coords_b,
texture_coords_c,
texture_coords_d,
texture_coords_e,
]
def create_meshes_with_textures(polygon, face, texture, texture_coords):
""" Opens each image with PIL, converts them to np.arrays and converts those to
VTK textures. Each textur gets mapped to a polygon according to the coordinates.
"""
img = Image.open(texture)
img.load()
img = np.asarray(img, dtype=np.uint8)
img = pv.numpy_to_texture(img)
polygon = np.array(polygon)
mesh = pv.PolyData(polygon, face)
mesh.t_coords = texture_coords
return mesh, img
# create Plotter object from PyVista
p = pv.Plotter()
for polygon, face, texture, texture_coords in zip(
polygons, faces, textures, texture_coords
):
# map the textures to the polygons
mesh, img = create_meshes_with_textures(polygon, face, texture, texture_coords)
# add the resulting meshes to the Plotter object
p.add_mesh(mesh, texture=img)
p.show()
The second option is to use vtkplotter, thanks to the developer who implemented the feature through this GitHub Issue. Overall both packages work quite well for this problem, with vtkplotter needing less conversion steps for the texture data.
from vtkplotter import *
import numpy as np
# six polygons consisting of points with X, Y, and Z coordinates
polygon_a = [
[
(371982, 5812893, 47),
(371987, 5812889, 47),
(371993, 5812896, 47),
(371988, 5812900, 47),
(371982, 5812893, 47),
],
[[0, 1, 2, 3, 4]],
]
polygon_b = [
[
(371987, 5812889, 44),
(371987, 5812889, 47),
(371982, 5812893, 47),
(371982, 5812893, 44),
(371987, 5812889, 44),
],
[[0, 1, 2, 3, 4]],
]
polygon_c = [
[
(371993, 5812896, 44),
(371993, 5812896, 47),
(371987, 5812889, 47),
(371987, 5812889, 44),
(371993, 5812896, 44),
],
[[0, 1, 2, 3, 4]],
]
polygon_d = [
[
(371982, 5812893, 44),
(371982, 5812893, 47),
(371988, 5812900, 47),
(371988, 5812900, 44),
(371982, 5812893, 44),
],
[[0, 1, 2, 3, 4]],
]
polygon_e = [
[
(371988, 5812900, 44),
(371988, 5812900, 47),
(371993, 5812896, 47),
(371993, 5812896, 44),
(371988, 5812900, 44),
],
[[0, 1, 2, 3, 4]],
]
polygon_f = [
[
(371987, 5812889, 44),
(371982, 5812893, 44),
(371988, 5812900, 44),
(371993, 5812896, 44),
(371987, 5812889, 44),
],
[[0, 1, 2, 3, 4]],
]
# texture coordinates of X, Y with interval [0, 1]
texture_coords_a = [
0.993515,
0.590665,
0.583403,
0.995886,
0.001318,
0.409513,
0.411194,
0.00281,
0.993515,
0.590665,
]
texture_coords_b = [
0.814495,
0.004965,
0.986562,
0.175202,
0.172649,
0.994582,
0.004011,
0.820917,
0.814495,
0.004965,
]
texture_coords_c = [
0.992976,
0.869131,
0.867654,
0.99699,
0.009377,
0.134356,
0.138307,
0.010153,
0.992976,
0.869131,
]
texture_coords_d = [
0.007693,
0.148416,
0.15451,
0.00767,
0.994519,
0.86112,
0.844256,
0.998197,
0.007693,
0.148416,
]
texture_coords_e = [
0.997322,
0.660826,
0.89938,
0.990736,
0.006374,
0.337104,
0.106732,
0.00748,
0.997322,
0.660826,
]
texture_coords = [
texture_coords_a,
texture_coords_b,
texture_coords_c,
texture_coords_d,
texture_coords_e,
]
# textures for some of the polygons as .jpg-files
img_a = "tex_2962910"
img_b = "tex_2962971"
img_c = "tex_2962990"
img_d = "tex_2962933"
img_e = "tex_2962915"
polygons = [polygon_a, polygon_b, polygon_c, polygon_d, polygon_e]
textures = [img_a, img_b, img_c, img_d, img_e]
meshes = []
# loop through all polygons and their textures
for polygon, texture, texture_coord in zip(polygons, textures, texture_coords):
# reformat texture coordinates as [(u,v), ...]
texture_coord = np.split(np.array(texture_coord), 5)
# create an Actor object for each polygon
polygon = Actor(polygon)
# map the textur with the according coordinates
polygon.texture(texture, tcoords=texture_coord)
meshes.append(polygon)
# assemble the objects
polygons = Assembly(meshes)
show(polygons, viewup="z", axes=8)
Upvotes: 2