trimesh: Possible bug in Pillow rasterization

Rasterization using PIL doesn’t create a clean image when sectioning a mesh

geometry = trimesh.load_mesh(path)
slice = geometry.section(place_origin=[0, 0, 0.15], plane_normal = [0,0,1])
planar, _ = slice.to_planar(normal=[0, 0, 1])
im_origin = planar.bounds[0]
im = planar.rasterize(resolution, im_origin)

the file used is here : https://cdn.thingiverse.com/assets/7d/fc/6e/33/fe/3DBenchy.stl

I believe the issue is inside Pillow. I copied the rastization implementation and I just used scikit-image to draw ,instead of Pillow, and it fixed the issue.


from skimage import draw


def rasterize(
    path: trimesh.path.Path2D, pitch: npt.NDArray, origin: npt.NDArray, resolution=None
) -> npt.NDArray:

    pitch = np.asanyarray(pitch, dtype=np.float64)
    origin = np.asanyarray(origin, dtype=np.float64)
    # if resolution is None make it larger than path
    if resolution is None:
        span = np.vstack((path.bounds, origin)).ptp(axis=0)
        resolution = np.ceil(span / pitch) + 2
    
    im = np.zeros(resolution.astype(int)).astype(bool)
    # get resolution as a (2,) int tuple
    discrete = [((i - origin) / pitch).round().astype(np.int64) for i in path.discrete]
    roots = path.root
    enclosure = path.enclosure_directed

    for root in roots:
        outer = draw.polygon(discrete[root][:,0],discrete[root][:,1], shape = im.shape)
        im[outer] = 1
        for child in enclosure[root]:
            hole = draw.polygon(discrete[child][:,0],discrete[child][:,1], shape = im.shape)
            im[hole] = 0 
    
    return im

I can make a pull request if interested

About this issue

  • Original URL
  • State: closed
  • Created 2 years ago
  • Comments: 17 (7 by maintainers)

Commits related to this issue

Most upvoted comments

Sounds good, I think I just need the planar slice exactly to reproduce, here’s what I was running:

import pickle
import trimesh
import numpy as np
from skimage import draw


def rasterize(path, pitch, origin, resolution=None):

    pitch = np.asanyarray(pitch, dtype=np.float64)
    origin = np.asanyarray(origin, dtype=np.float64)
    # if resolution is None make it larger than path
    if resolution is None:
        span = np.vstack((path.bounds, origin)).ptp(axis=0)
        resolution = np.ceil(span / pitch) + 2
    else:
        resolution = np.array(resolution).round().astype(int)
    im = np.zeros(resolution).astype(bool)
    # get resolution as a (2,) int tuple
    discrete = [((i - origin) / pitch).round().astype(np.int64) for i in path.discrete]
    roots = path.root
    enclosure = path.enclosure_directed

    for root in roots:
        outer = draw.polygon(discrete[root][:, 0], discrete[root][:, 1], shape=im.shape)
        im[outer] = 1
        for child in enclosure[root]:
            hole = draw.polygon(discrete[child][:, 0],
                                discrete[child][:, 1], shape=im.shape)
            im[hole] = 0

    return im


if __name__ == '__main__':
    geometry = trimesh.load_mesh('3DBenchy.stl')
    slice = geometry.section(plane_origin=[0, 0, .15],
                             plane_normal=[0, 0, 1])
    planar, _ = slice.to_planar(normal=[0, 0, 1])

    origin = planar.bounds[0]
    pitch = planar.extents.max() / 1000
    resolution = [1000, 1000]

    im = planar.rasterize(resolution=resolution, pitch=pitch, origin=origin)

    ii = rasterize(path=planar, pitch=pitch, origin=origin, resolution=resolution)

    with open('planar.pickle', 'wb') as f:
        pickle.dump(planar, f)