Search code examples
pythonvtktexture-mappingwavefront

Vtk inserts incorrect color between nodes when mapping texture to mesh


Hi I am trying to map a texture to 3d mesh using Mayavi and Python bindings of vtk. I am visualising an .obj wavefront. This obj is 3D photograph of a face. The texture image is a composite of three 2D photographs.

enter image description here

Each node in the mesh has an (uv) co-ordinate in the image, which defines its color. Different regions of the mesh draw their colours from different sections of the image. To illustrate this I have replaced the actual texture image with this one:

enter image description here

And mapped this to the mesh instead.

enter image description here

The problem I am having is illustrated around the nose. At the border between red and green there is an outline of blue. Closer inspection of this region in wireframe mode shows that it is not a problem with the uv mapping, but with how vtk is interpolating colour between two nodes. For some reason it is adding a piece of blue in between two nodes where one is red and one is green.

enter image description here

This causes serious problems when visualising using the real texture

enter image description here

Is there a way to force vtk to choose the colour of one or the other neighbouring nodes for the colour between them? I tried turning "edge-clamping" on, but this did not achieve anything.

The code that I am using is below and you can access the files in question from here https://www.dropbox.com/sh/ipel0avsdiokr10/AADmUn1-qmsB3vX7BZObrASPa?dl=0 but I hope this is a simple solution.

from numpy import *
from mayavi import mlab
from tvtk.api import tvtk
import os
from vtk.util import numpy_support

def  obj2array(f):
    """function for reading a Wavefront obj"""
    if type(f)==str:
            if os.path.isfile(f)==False:
                    raise ValueError('obj2array: unable to locate file ' + str(f))
            f =open(f)

    vertices = list()
    connectivity = list()
    uv = list()
    vt = list()


    fcount = 0


    for l in f:
                line = l.rstrip('\n')

                data = line.split()
                if len(data)==0:
                        pass
                else:

                        if data[0] == 'v':
                            vertices.append(atleast_2d(array([float(item) for item in data[1:4]])))

                        elif data[0]=='vt':
                            uv.append(atleast_2d(array([float(item) for item in data[1:3]])))


                        elif data[0]=='f':

                            nverts = len(data)-1 # number of vertices comprising each face

                            if fcount == 0:    #on first face establish face format

                                fcount = fcount + 1
                                if data[1].find('/')==-1:  #Case 1
                                    case = 1

                                elif data[1].find('//')==True:
                                    case = 4
                                elif len(data[1].split('/'))==2:
                                    case = 2
                                elif len(data[1].split('/'))==3:
                                    case = 3



                            if case == 1:
                                f = atleast_2d([int(item) for item in data[1:len(data)]])
                                connectivity.append(f)

                            if case == 2:
                                splitdata = [item.split('/') for item in data[1:len(data)]]
                                f = atleast_2d([int(item[0]) for item in splitdata])

                                connectivity.append(f)

                            if case == 3:
                                splitdata = [item.split('/') for item in data[1:len(data)]]
                                f = atleast_2d([int(item[0]) for item in splitdata])
                                connectivity.append(f)


                            if case == 4:
                                splitdata = [item.split('//') for item in data[1:len(data)]]
                                f = atleast_2d([int(item[0]) for item in splitdata])

                                connectivity.append(f)


    vertices = concatenate(vertices, axis = 0)
    if len(uv)==0:
        uv=None
    else:
        uv = concatenate(uv, axis = 0)

    if len(connectivity) !=0:
            try:
                    conarray = concatenate(connectivity, axis=0)
            except ValueError:
                    if triangulate==True:
                            conarray=triangulate_mesh(connectivity,vertices)

                    else:
                            raise ValueError('obj2array: not all faces triangles?')
            if conarray.shape[1]==4:
                    if triangulate==True:
                            conarray=triangulate_mesh(connectivity,vertices)



    return vertices, conarray,uv



# load texture image
texture_img = tvtk.Texture(interpolate = 1,edge_clamp=1)
texture_img.input = tvtk.BMPReader(file_name='HM_1_repose.bmp').output

#load obj
verts, triangles, uv = obj2array('HM_1_repose.obj')

# make 0-indexed
triangles = triangles-1

surf = mlab.triangular_mesh(verts[:,0],verts[:,1],verts[:,2],triangles)

tc=numpy_support.numpy_to_vtk(uv)

pd = surf.mlab_source.dataset._vtk_obj.GetPointData()
pd.SetTCoords(tc)
surf.actor.actor.mapper.scalar_visibility=False
surf.actor.enable_texture = True
surf.actor.actor.texture = texture_img
mlab.show(stop=True)

Solution

  • You can turn off all interpolation (change interpolate = 1 to interpolate = 0 in your example), but there is not a way to turn off interpolation at just the places where it would interpolate across sub-images of the texture – at least not without writing your own fragment shader. This will likely look crude.

    Another solution would be to create 3 texture images with transparent texels at each location that is not part of the actor's face. Then render the same geometry with the same texture coordinates but a different image each time (i.e., have 3 actors each with the same polydata but a different texture image).