X

NV5 Geospatial Blog

Each month, NV5 Geospatial posts new blog content across a variety of categories. Browse our latest posts below to learn about important geospatial information or use the search bar to find a specific topic or author. Stay informed of the latest blog posts, events, and technologies by joining our email list!



Mapping Earthquake Deformation in Taiwan With ENVI

Mapping Earthquake Deformation in Taiwan With ENVI

12/15/2025

Unlocking Critical Insights With ENVI® Tools Taiwan sits at the junction of major tectonic plates and regularly experiences powerful earthquakes. Understanding how the ground moves during these events is essential for disaster preparedness, public safety, and building community resilience. But traditional approaches like field... Read More >

Comparing Amplitude and Coherence Time Series With ICEYE US GTR Data and ENVI SARscape

Comparing Amplitude and Coherence Time Series With ICEYE US GTR Data and ENVI SARscape

12/3/2025

Large commercial SAR satellite constellations have opened a new era for persistent Earth monitoring, giving analysts the ability to move beyond simple two-image comparisons into robust time series analysis. By acquiring SAR data with near-identical geometry every 24 hours, Ground Track Repeat (GTR) missions minimize geometric decorrelation,... Read More >

Empowering D&I Analysts to Maximize the Value of SAR

Empowering D&I Analysts to Maximize the Value of SAR

12/1/2025

Defense and intelligence (D&I) analysts rely on high-resolution imagery with frequent revisit times to effectively monitor operational areas. While optical imagery is valuable, it faces limitations from cloud cover, smoke, and in some cases, infrequent revisit times. These challenges can hinder timely and accurate data collection and... Read More >

Easily Share Workflows With the Analytics Repository

Easily Share Workflows With the Analytics Repository

10/27/2025

With the recent release of ENVI® 6.2 and the Analytics Repository, it’s now easier than ever to create and share image processing workflows across your organization. With that in mind, we wrote this blog to: Introduce the Analytics Repository Describe how you can use ENVI’s interactive workflows to... Read More >

Deploy, Share, Repeat: AI Meets the Analytics Repository

Deploy, Share, Repeat: AI Meets the Analytics Repository

10/13/2025

The upcoming release of ENVI® Deep Learning 4.0 makes it easier than ever to import, deploy, and share AI models, including industry-standard ONNX models, using the integrated Analytics Repository. Whether you're building deep learning models in PyTorch, TensorFlow, or using ENVI’s native model creation tools, ENVI... Read More >

1345678910Last
17480 Rate this article:
3.3

Quickly Read and Display Data From a New File Format - Paleontology with IDL

Jim Pendleton

Over time, you may run across data sets that you'd like to read in IDL, file formats that lack any built-in utilities in the language.

Quite frequently I use the following technique for building custom data readers for IDL in my work for the Custom Solutions Group.

A recent article I ran across described a newly-minted repository of paleontological data, including 3D models of bones of long-lost species. MorphoSource.org, is a Duke University project funded in part by grants from the National Science Foundation. Commercial use of the provided media is strictly prohibited, according to their website.

My goal was to see how easy it would be to read some of this data and display it in an interactive IDL display. It was very simple, indeed!

 

Searching the database I found a number of interesting data types, including 3D models. One example, shown above and copyright the Florida Museum of Natural History, is the tooth of a Calippus elachistus, an early horse-like mammal that may have roamed the area in Florida during the Miocene epoch.

 

 

I requested a user account from MorphoSource.org and their robot sent me back the OK to grab some data, for instructional and non-commercial use only, of course.

Drilling down a little further I located some ASCII-format "Avizo PLY"-format files. I've not worked with this type of file before, and IDL doesn't have a built-in reader, but opening the file in a standard text editor shows that the format is straightforward.

Given this information, it's easy to build a utility in IDL that will both read the data and display the 3D object.

Though we won't attempt to parse all the data in the file, we can quickly locate the two major pieces of data we need for visualization, the list of vertex coordinates and the connectivity of those vertices.

The header begins with the following

ply
format ascii 1.0
obj_info Format: Avizo PLY
element vertex 249741
property float32 x
property float32 y
property float32 z
element face 495182
property list uint8 int32 vertex_indices

What this tells me is that somewhere in the file there are 249741 vertices with floating point (X,Y,Z) coordinates and 495182 "faces" or connectivity lists. A connectivity list tells us how the indices in the vertex list connect to one another.

(In other files in the MorphoSource data sets, you may also find data for vertex coloring, which adds greater interest!)

There are some additional lines in the header which we will ignore.  The header ends with an "end_header" line.

The lines immediately after the header are series of three floating point elements, for example

end_header
34.6223 25.3743 11.5595
34.5367 25.4449 11.5501
34.6183 25.4799 11.5998

Gosh, those look like vertex coordinates, don't they?  Now we can follow a hunch that if we look down the file another 249741 lines (the vertex count), we'll find data in a different format.  Sure enough, we find a transition.

23.9367 23.4851 43.5914
24.1271 23.4578 43.5783
24.0427 23.5348 43.5879
3 0 4 1 0
3 1 2 0 0
3 3 8 7 0
3 8 3 0 0

The connectivity list of each line starts with the number of vertices in a polygon's face.In this case they're all triangles, therefore the value is "3". Following the vertex count are the three zero-based indices of the vertices that make up each triangular facet.The last number in each line may indicate whether the polygon is open or closed. They all have a value of 0, so we'll just ignore the fifth value.

Not coincidentally, these vertex and connectivity lists are precisely in the format that IDL's IDLgrPolygon object uses.

After downloading the file to my desktop, I'll read the contents of the file into a string array variable named "val".

file = 'UF-53577_M5794-5222_Calippus_elachistus.ply'
n = file_lines(file)
val = strarr(n)
openr, lun, file, /get_lun
readf, lun, val
free_lun, lun

Locate the line that starts with the text "element vertex". We'll split that line on spaces and pull out the third element.  That's the vertex count.

vertexline = where(val.startswith('element vertex'))
vertexcount = long((val[vertexline[0]].split(' '))[2])
 

Repeat for the polygon connectivity count, which starts with "element face".

polygonline = where(val.startswith('element face'))
polygoncount = long((val[polygonline[0]].split(' '))[2])

Create IDL arrays in which to store the vertices and polygons.

vertices = fltarr(3, vertexcount)
poly = lonarr(4, polygoncount)

Remove the header from the string array so we're positioned at the first line of vertices.

last = (where(val.startswith('end_header')))[0]
val = val[last + 1: *]

Loop over the lines containing the vertex data, and extract the values into the vertices array.

for i = 0L,  vertexcount - 1 do $
  vertices[0, i] = float(((val[i]).trim()).split(' '))

Remove the vertices from the start of the string array and repeat the procedure for the connectivity.

val = val[vertexcount:*]
for i = 0L, polygoncount - 1 do $
  poly[0, i] = (long(((val[i]).trim()).split(' ')))[0:3]

For the display, we only need two lines of code.  Populate an IDLgrPolygon object with our data.

opolygon = idlgrpolygon(vertices, poly = poly, style = 2, color = !color.gold)

Finally, display the data in one of IDL's interactive tools, XOBJVIEW.

xobjview, opolygon

Did this horse have a cavity, or is the hole in the crown a spot that's been drilled for sampling?

Below is an example routine that incorporates the code shown above, and generates an MP4 animation in your temporary directory.  You will need to request a MorphoSource account to download your own PLY-format file as input. Feel free to generalize the code for a more robust PLY-format solution.

pro read_ply, dir, vertices
file = filepath('UF-53577_M5794-5222_Calippus_elachistus.ply')
n = file_lines(file)
val = strarr(n)
openr, lun, file, /get_lun
readf, lun, val
free_lun, lun
vertexline = where(val.startswith('element vertex'))
vertexcount = long((val[vertexline[0]].split(' '))[2])
polygonline = where(val.startswith('element face'))
polygoncount = long((val[polygonline[0]].split(' '))[2])
vertices = fltarr(3, vertexcount)
poly = lonarr(4, polygoncount)
last = (where(val.startswith('end_header')))[0]
val = val[last + 1: *]
for i = 0L,  vertexcount - 1 do $
vertices[0, i] = float(((val[i]).trim()).split(' '))
val = val[vertexcount:*]
for i = 0L, polygoncount - 1 do $
poly[0, i] = (long(((val[i]).trim()).split(' ')))[0:3]
;poly = reform(poly, poly.length, /overwrite)
om = idlgrmodel()
opolygon = idlgrpolygon(vertices, style = 2, poly = poly, color = !color.gold)
om->add, opolygon
mx = mean(vertices[0, *])
my = mean(vertices[1, *])
mz = mean(vertices[2, *])
om->translate, -mx, -my, -mz
xobjview, om, tlb = tlb, xsize = 256, ysize = 256
frames = list()
temppng = filepath(/tmp, 'frame.png')
for i = 0, 360, 3 do begin ; rotate
om->rotate, [1, 1, 0], 3
xobjview, refresh = tlb
xobjview_write_image, temppng, 'png'
frames.add, read_png(temppng)
file_delete, temppng
endfor
for i = 1, 100, 3 do begin ; zoom in
om->scale, 1.03, 1.03, 1.03
xobjview, refresh = tlb
xobjview_write_image, temppng, 'png'
frames.add, read_png(temppng)
file_delete, temppng
endfor
for i = 1, 100, 3 do begin ; zoom out
om->scale, 1/1.03, 1/1.03, 1/1.03
xobjview, refresh = tlb
xobjview_write_image, temppng, 'png'
frames.add, read_png(temppng)
file_delete, temppng
endfor
tempvid = filepath(/tmp, 'horsetooth.mp4')
ovid = IDLffVideoWrite(tempvid)
fs = (frames[0]).dim
vidstream = ovid.AddVideoStream(fs[1], fs[2], 24)
foreach frame, frames do $
t = ovid.put(vidstream, frame)
obj_destroy, ovid
spawn, tempvid
end
Please login or register to post comments.