.ply reader in nuke

As an aside to developing my nuke photogrammetry pipeline (to be documented shortly) I was annoyed at the lack of .ply file support in nuke. Since a lot of photogrammetry and LIDAR tools rely export point clouds in the .ply format, it seemed useful to allow that data to be brought into nuke somehow.

The obvious way of doing this would be to subclass the geoReader class that comes with nuke and write an actual node that could parse and display ply files…but getting involved with compiled plugins means sacrificing portability and as a freelancer, I’d rather take a pythonic solution that I can use anywhere.

I remembered that in nuke 7, dense point clouds can be baked into “BakedPointCloud” groups which are just a collection of points in space. Intrigued, I copy/pasted the node into a text editor, and low and behold, it contained the same information as my .ply file! Point locations, normal directions, and a color! If I could get this to work, it’d be huge for 2 reasons…First, LIDAR scans and any .ply data could be easily imported and manipulated in nuke, as stated. But more excitingly, since the input to a Poisson Mesh node can be a BakedPointCloud group isolated from a dense point cloud, perhaps the points could be meshed interactively in nuke. Turns out they can.

Screen Shot 2013-07-09 at 11.19.33 PM
This looks like a mesh, but is actually a point cloud viewed from a ways off.

Screen Shot 2013-07-09 at 11.20.22 PM

Screen Shot 2013-07-09 at 11.19.38 PM

Screen Shot 2013-07-09 at 11.25.08 PM
Meshed point cloud! Thanks PoissonMesh node! In truth, meshlab has more control over meshing and I’m not sure there’s any reason to do it in nuke itself (perhaps I need to write a BakedPointCloud –> .ply file exporter to have a better chance of making nice meshes from camera solves, but I digress) but it’s still cool in principle!

Simply copy/paste this code to your script editor and execute it with Command + Enter(Mac) or Ctrl + Enter(Linux). There’s a GUI! Select an ASCII .ply file with verts, normals, and color, and the maximum number of points you want created, and hit okay! I’ll try to post this on nukepedia when I get the chance (and they start accepting uploads again)!

UPDATE! Better code!

# TODO:
# detect .ply files written with a different order of pos / normals / colors
# more efficiently access the file in question!
# probably won't work on windows due to /tmp

import math
import time
import nuke  

import random
import math

# GLOBALS
singleLineInput = 300000
filenameSearch = None
output = "/tmp/plyConversion_%s.nk" % str(int(time.mktime(time.gmtime())))
points = ""         # long string representing all the point positions
normals = ""        # long string representing all the point normals
colors = ""         # long string representing all the point colors
numVerts = "" 
total = 0
data = False        # whether we're in the "data" section of the ply file
vertEntries = []    # lines of the file pertaining to verts

# randomly select n items from a list
# inspired by a discussion here: http://www/gregable.com/2007/10/reservoir-sampling.html
def randomSelection(numToPick,items):
    shuffledItems = items
    random.shuffle(shuffledItems)
    return shuffledItems[0:numToPick]

# GUI!
p = nuke.Panel(".ply importer")
p.addFilenameSearch(".ply file", filenameSearch)
p.addSingleLineInput("Maximum points to create", singleLineInput)    
p.addButton("Cancel")
p.addButton("OK")
result = p.show()

# Parse choices
path = p.value(".ply file")
MAX_POINTS = int(p.value("Maximum points to create"))

# Processing!
r = open(path,"r")
lines = r.readlines()
r.close

# parse .ply file line by line!
# this is hideously inefficient...we should bomb out when we hit the last vert line...
for i,line in enumerate(lines):

    # if we've hit the data section, add only the lines that contain vertex info
    if data and total < numVerts:
        vertEntries.append(line)
        total += 1
            
    # calculate the number of vertices we're going to be loading!
    if "element vertex" in line:
        numVerts = int(line.split()[2])
        print "%s points detected! " % numVerts        
        
    # if the header is over, toggle the data boolean to true: now we're in the payload of the file
    if "end_header" in line:
        data = True

# process every point, or a fixed, random selection
if MAX_POINTS > numVerts:
    lines = vertEntries
else:
    # Thanks Mike Lang for suggesting an optimization could be used here!
    print "selecting only %s points at random! " % MAX_POINTS
    lines = randomSelection(MAX_POINTS,vertEntries)

# now that we have our selection, build up our template strings!
for line in lines:
    datasplit = line.split()
    points += "%s " % " ".join(datasplit[0:3])
    normals += "%s " % " ".join(datasplit[3:6])
    colors += "%s " % " ".join([str(float(v)/255.0) for v in datasplit[6:9]]) # ignore alpha value

# Nuke node specifics:
name = path.split("/")[-1]
label = "%s points" % (len(lines))

template = """
set cut_paste_input [stack 0]
version 7.0 v4
BakedPointCloud {
inputs 0
pointSize 2
serializeKnob ""
serializePoints "%s %s "
serializeNormals "%s %s "
serializeColors "%s %s "
name %s
label "%s"
xpos 510
ypos 140
}""" % (total, points, total, normals, total, colors,name,label)

# Write out .nk file to output location (somewhere in /tmp)
w = open(output,"w")
w.write(template)
w.close()

nuke.nodePaste(output)

3 thoughts on “.ply reader in nuke

  1. That’s a pretty harsh skip factor. Seems like trying to evenly distribute the max desired verts across the number of verts found would yield better results for the user. I think something like below could work (though my late night math ain’t what it used to be). Just sayin, I would imagine seeing a 350K mesh end up w 175K verts may seem odd

    // relabeling skip var to be process since that’s what is going on

    // calculate process factor wo rounding
    process = float(NumVerts/MaxPoints)

    curIdx = 0 // var outside of loop

    // data section test
    if i == floor(curIdx*process) :
    // process vert here
    curIdx++
    if curIdx >= MaxPoints:
    break;

    • I’m not sure that your math is right either, but you’re right: using the nearest whole int seems overly harsh, and this seems better. Truthfully, the skip factor was added purely because I thought there was a limit to the number of points nuke could display…turns out I only thought I was hitting a limit because of a bug in an older version of my code. I’ve loaded in 1.6M points since, no problem.

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>