Pointcloud Visualisation In Python – Edition 2, In A Series

Above: Rotating animation of Aerometrex’s 40.3GB/ 49 Billion point photogrammetry model of the Gold Coast, Australia made using udStream Development Kit and Python

What is a UDS 

The purpose of this article is to give a bit of background about the UDS format and the Unlimited Detail Renderer (the piece of software underlying Euclideon udStream). We will discuss the terminology of 3D rendering (just in case not all of us are experts in computer graphics), the UDS file format, and the characteristics of the UD engine, what it does well at, and what we don’t. 

UDS is the file format udStream uses to store point cloud data. Part of the efficiency of the unlimited detail engine comes the structure of how it stores your data. The format is optimized for exploration by the UD renderer as well as efficient streaming over network connection, which is what allows us to store terabyte+ sized point cloud files on a remote network drive and still view the file with a modest internet connection speed. We allow conversion from most major point cloud formats to UDS and support conversion back to those formats (we aren’t trying to lock you out of your data, we just want to make accessing it fast).  

An Example 

Like our previous post in this series, we will be using our Python Client to demonstrate our SDK functions. We will be taking a dive into some code and looking at how we can control our UDS using python programmatically so an IDE is a good idea. We recommend Pycharm Community (here)[ https://www.jetbrains.com/pycharm/download/] for editing python scripts (any will work though). 

We will start by loading our Client (see our previous post on how to do this): 

ipython pygletexample.py username password 

We can then load our sample file by running from the console: 

renderer.add_model(“./samplefiles/DirCube.uds”) 

TIP: ipython supports tab completion of objects in the name space of the main module, typing ren[tab] will bring up a list matching names in the program which can be selected using the arrow keys and enter 

Typing . and pressing tab again gives us a list of all the things we can do with that object; we want to select add_model here. 

This saves us having to remember the names of all our methods and object and typing them out 

Our viewing window will look something like this: 

We can move our camera using the mouse and WSAD keys. Alternatively we can set our view by typing in the console: 

mainCamera.position = (2,2,2) 
mainCamera.look_at((0,0,0)) 

Which results in the view looking like: 

These commands give us finer control of the exact position of the camera and can allow us to position the user of our program automatically by scripting. Later we will show how to script camera paths for creation of virtual tours or view extraction for further processing. 

Controlling the instance 

We refer to the entity being rendered as an instance. An instance consists of a reference to our UDS, information about where (position and rotation) and how (filters and shaders) to draw the model. In this post we will focus on the where and we will talk about the how later (for now the default RGB colours will suffice).  

We can control the position of the instance in a similar way to how control the camera above.  

First we will give our instance a convenient name: 

cube = renderer.renderInstances[-1] #we are making a reference to the last added instance here 
cube.position = (1,0,1) #move our cube right and up by one unit 

We see our cube has now moved: 

Using mainCamera.look_at we can automatically face the position of our cube 

mainCamera.look_at(cube.position) 

We can also rotate our cube: 

cube.rotation = (0,0,3.14/4) #rotate our cube by approximately 45 degrees 

We can also scale our cube in the same manner: 

cube.scale = 0.5 

Automation  

Controlling individual object properties using the console gives us fine control, but is tedious. We can automate these commands by placing them in a script and calling it from our console. Create a new file called my_script.py and paste the following: 

renderer.add_model("./samplefiles/DirCube.uds") 
cube = renderer.renderInstances[-1] 
mainCamera.position = (-2,2,2) 
cube.position = (1,0,1) 
mainCamera.look_at(cube.position) 

Save it in the same folder as our client and run the script by typing in the console: 

run_script("./my_script.py") 

We find a new cube has been created and our camera has been moved to the position (-2,2,2).  

Note that our scripts can modify the outer scope but will not create new variable within it. This means that while we have created a variable cube in the above script we would not be able to modify it in our console. We can redefine the name cube from console by running  

cube = renderer.renderInstances[-1]  

To name the most recently created renderInstance cube and modify it as in our previous examples. 

While this example was relatively simple our scripts need not be restricted to simply manipulating our camera and instances; we have the full flexibility of the  

Great Spinning Cities Batman! 

Hopefully the previous sections gave us an idea of how we can use scripting to create customizable workflows using UD in python. Now that we understand how our render instances can be manipulated we can look at how to do this over time. 

Let’s ditch the boring DirCube for the time being. Restart the program and run  

renderer.add_model("https://brisbane2019.oss-ap-southeast-.aliyuncs.com/CRR_LR_75mm_Aerometrex.uds") 

You will be greeted with Aerometrex’s lovely looking photogrammetry model of Brisbane City.  

The first thing to note is the default scaling: on initial load all models are scaled so that their smallest dimension is 1 unit long: that way we should always see something when a model loads. Let’s give the Brisbane model a friendly name and check this scale: 

brisbane = renderer.renderInstances[-1] 
brisbane.scale 

This should print out ~(243,243,243) indicating that the model is equally scaled in all directions. For our purposes we want a smaller scale (it will be apparent later why). 

Let’s reposition our brisbane model to (0,0,0) and scale it down: 

brisbane.scale=5 
brisbane.position=(0,0,0) 

We have to fly up a bit to find our model but here is our whole 120GB model: 

Now let’s try spinning it. 

We have provided an example class UDSAnimator that will repeatedly call functions using pyglets clock. We will use this to modify our instance’s rotation every 1/20 of a second to give an effect of the model gently turning about the z axis. 

From the console: 

from animator import UDSAnimator 

anim = UDSAnimator 

anim.spin_instance(brisbane) 

130 GB pointcloud/photogrammetry model rendering demonstration using python example 

We can stop the animation by calling  

anim.stop() 

And conversely to start again: 

anim.start() 

Adding your own rigid animations is as easy as modifying or subclassing UDSAnimator in animator.py. 

This has been a short introduction to rendering and manipulating the UDS models using python and udSDK. This is definitely not the limit of what is possible using these technologies and we will continue to cover the possibilities in the coming weeks.  

Blog article created by: 
Kisar Samsuya Euclideon Solutions Engineer 
Braden Wockner Euclideon Graduate Solutions Engineer