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!



NV5 at ESA’s Living Planet Symposium 2025

NV5 at ESA’s Living Planet Symposium 2025

9/16/2025

We recently presented three cutting-edge research posters at the ESA Living Planet Symposium 2025 in Vienna, showcasing how NV5 technology and the ENVI® Ecosystem support innovation across ocean monitoring, mineral exploration, and disaster management. Explore each topic below and access the full posters to learn... Read More >

Monitor, Measure & Mitigate: Integrated Solutions for Geohazard Risk

Monitor, Measure & Mitigate: Integrated Solutions for Geohazard Risk

9/8/2025

Geohazards such as slope instability, erosion, settlement, or seepage pose ongoing risks to critical infrastructure. Roads, railways, pipelines, and utility corridors are especially vulnerable to these natural and human-influenced processes, which can evolve silently until sudden failure occurs. Traditional ground surveys provide only periodic... Read More >

Geo Sessions 2025: Geospatial Vision Beyond the Map

Geo Sessions 2025: Geospatial Vision Beyond the Map

8/5/2025

Lidar, SAR, and Spectral: Geospatial Innovation on the Horizon Last year, Geo Sessions brought together over 5,300 registrants from 159 countries, with attendees representing education, government agencies, consulting, and top geospatial companies like Esri, NOAA, Airbus, Planet, and USGS. At this year's Geo Sessions, NV5 is... Read More >

Not All Supernovae Are Created Equal: Rethinking the Universe’s Measuring Tools

Not All Supernovae Are Created Equal: Rethinking the Universe’s Measuring Tools

6/3/2025

Rethinking the Reliability of Type 1a Supernovae   How do astronomers measure the universe? It all starts with distance. From gauging the size of a galaxy to calculating how fast the universe is expanding, measuring cosmic distances is essential to understanding everything in the sky. For nearby stars, astronomers use... Read More >

Using LLMs To Research Remote Sensing Software: Helpful, but Incomplete

Using LLMs To Research Remote Sensing Software: Helpful, but Incomplete

5/26/2025

Whether you’re new to remote sensing or a seasoned expert, there is no doubt that large language models (LLMs) like OpenAI’s ChatGPT or Google’s Gemini can be incredibly useful in many aspects of research. From exploring the electromagnetic spectrum to creating object detection models using the latest deep learning... Read More >

1345678910Last
«September 2025»
SunMonTueWedThuFriSat
31123456
78910111213
14151617181920
21222324252627
2829301234
567891011
15237 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.