This article will create a User Analysis using the ZOS-API to measure the time-of-flight (TOF) of a LIDAR system. The analysis will read a ZRD file, extract the data and plot the time-of-flight of rays reaching the detector.
Authored By Sandrine Auriol
ZOS-API (Application Programming Interface) enables connections and customization of OpticStudio. There are 4 Program modes to connect the application program and OpticStudio, but they can be gathered into 2 main categories:
1) Full Control (Standalone and User Extensions modes), in which the user generally has full control over the lens design and user interface.
2) Limited Access (User Operands and User Analysis modes), in which the user is locked down to working with a copy of the existing lens file.
The User Analysis Mode is used to populate data for a custom analysis. The displayed data is using the modern graphics provided in OpticStudio for most analyses. This mode does not allow changes to the current lens system or to the user interface (i.e. in this mode only changes to a copy of the system are allowed). User analyses can be written using either C++ (COM) or C# (.NET). The User Analysis of this article is written in C#.
For more information about the User-Analysis, check the built-in Help file under The Programming Tab...About the ZOS-API...User Analysis.
Let’s create a User Analysis in C#:
A Windows Explorer opens with the solution folder ‘..\Documents\Zemax\ZOS-API Projects\CSharpUserAnalysisApplication1’. Visual Studio also opens with a new solution. The solution contains a boilerplate code, a template that can be used as the basis for any User Analysis. In Visual Studio, the user analysis is then compiled as an executable. The executable is then copied to the \Zemax\ZOS-API\User Analysis folder so that OpticStudio can use it.
The file “Flash_NSC_Final.zar” can be downloaded at the top of this article. It contains a system representing a Flash LIDAR. A LIDAR sits at the top of the van. The van is on the road and there are 2 pedestrians and a living green wall.
The LIDAR sends a laser pulse towards the scene:
Light is scattered by the surrounding objects. Part of that light is scattered back to the LIDAR detector.
For example, below, the light scattering back off from the red pedestrian reaches a pixel of the LIDAR detector.
The LIDAR records how long it takes to get the signal back; that is the time-of-flight. The time-of-flight is converted into a distance. The pixel indicates the direction of the incoming light.
With both values, it tells that the scattered light comes from a red pedestrian that stands at 10m from the van. OpticStudio does not actually measure the time but the path length of rays, so the distance between the object and the detector.
The Detector Viewer can show radiometric results on the detector, but it doesn’t show the path length of the rays from the LIDAR source back to the LIDAR detector. That’s when ZOS-API comes in handy! A user analysis can be built to show the Distance to Object Data and thus reflect the Time of Flight information.
The path length of rays can be read in ZRD files, which are Ray database files.
Let’s run a Ray Trace and Save Rays in a ZRD file.
Under Analyze...Ray Database viewer, the path length of the rays that hit Detector 17 can be displayed. The “Apply Filter” setting is set to H17 to filter the rays that hit Detector 17.
For example, Ray 1 Segment 8 has hit Detector 17 and the path of that ray is the sum of the paths of all segments, which is 4E4 mm, so 40m. The ray has travelled to the object and then back to the detector. The distance of the object is half that path. It is 20m.
So, the distance of objects to the detector can be found by reading the path length of each ray that hits the detector. This operation can be done automatically using the ZOS-API.
A good habit when starting a new project with the ZOS-API is to check the examples which are under Programming...ZOS-API Help...ZOS-API Syntax Help.
Example 05 called “Parsing a ZRD File” can be used here as a reference because it shows how to read a ZRD file.
The User analysis code contains the following commands:
- OpenRayDatabaseReader: to open the Ray Database Viewer, read the ZRD file and then get the result:
IZRDReader ZRDReader = TheSystem.Tools.OpenRayDatabaseReader();
ZRDReader.ZRDFile = ‘<path>\Flash_LIDAR_NSC.ZRD’;
var ZRDResult = ZRDReader.GetResults();
- ReadNextResult: to read each ray
success_NextResult = ZRDResult.ReadNextResult(out rayNumber, out waveIndex, out wlUM, out numSegments);
- ReadNextSegmentFull: to read each segment of each ray
success_NextSegmentFull = ZRDResult.ReadNextSegmentFull(out segmentLevel, out segmentParent, out hitObj, out hitFace, out insideOf,
out RayStatus, out x, out y, out z, out l, out m, out n, out exr, out exi, out eyr, out eyi, out ezr, out ezi, out intensity, out pathLength, out xybin, out lmbin, out xNorm, out yNorm, out zNorm, out index, out startingPhase, out phaseOf, out phaseAt);
For segments of each ray, the values read are:
out pathLength: the path length of each ray segment. Adding them gives the path length of the ray.
out x, out y, out z: the X, Y, Z positions of the last ray segment to find out on which pixel the ray has landed
- out intensity: the intensity of the last ray segment
The results are stored in arrays of 2 dimensions (the x and y pixels of the detector).
Array (pixel x, pixel y) = data
The data in the array can be the path length of rays, the intensity, etc…
To assign a ray to a pixel, the ray last segment coordinates on the detector (x_LastSegment, y_LastSegment) are converted from Global Coordinates to the Detector Local Coordinates using GetMatrix:
TheSystem.NCE.GetMatrix(detector, out double R11, out double R12, out double R13, out double R21, out double R22, out double R23, out double R31, out double R32, out double R33, out double Xo, out double Yo, out double Zo);
Then the ray last segment can be assigned to a pixel [i,j] on the detector.
There are 4 result arrays:
- array_OPL [i,j] contains the average of the path length of the rays that hit the detector pixel [i,j] divided by two. It is the distance from the objects to the detector.
- array_TOF [i,j] contains the timeof-flight data. It is the translation of the path length in time. The speed of light in air is 299792 km/s. The time of flight is 2 times the path length (array_OPL) divided by the speed of light.
This value is an approximation as the path read out, which is not the optical path length but the path length. But the assumption is valid as the distance values to the objects are much bigger than the path through the optical elements.
- array_nb_hit [i,j] contains the number of rays that hit the detector pixel [i,j].
- array_flux [i,j] contains the sum of the rays flux that hit the detector pixel [i,j].
If the flux on a detector pixel array_flux [i,j] is smaller than a limit defined by MinFlux, then the detector value is set to zero as it is considered as noise.
//Define (declare and initialize arrays)
double[,] array_OPL = new double[ypixels, xpixels];
double[,] array_TOF = new double[ypixels, xpixels];
double[,] array_nb_hit = new double[ypixels, xpixels];
double[,] array_flux = new double[ypixels, xpixels];
Light reaching a -X pixel on the detector comes from a +X field of view. To avoid having a flipped image on the User Analysis, the result arrays contain the data from +X to -X and from +Y to -Y.
The User Analysis creates a plot of the arrays.
There are currently four types of a data that can be calculated and displayed: 2D Line Plot, 2D Grid Plot, 2D RGB Grid Plot and Text Data.
The “Time-Of-Flight” analysis creates a 2D Grid Plot with
MakeGridPlot. The results are stored in arrays. The X and Y axes are the detector pixels and the Z axis can either be the Distance to the Object, the Time of Flight, the Number of Hits or the Flux. The SetDataSafe function of the
IUserGridData interface takes an array as an input.
IUserGridData gridPlot = TheAnalysisData.MakeGridPlot("Time of Flight");
gridPlot.XLabel = "Pixel X coordinate value";
gridPlot.YLabel = "Pixel Y coordinate value";
Note that only one format of data is allowed per User Analysis.
The User analysis will display results for a saved ZRD file and for a specific detector. The results can be any of the four arrays: the distance from the objects to the detector, the time of flight, the number of rays that hits a detector pixel or the flux on a detector pixel.
All of those options are going to be part of a setting dialogue including abovementioned form control, and display option.
The User Analysis can contain a settings dialogue that makes the User Analysis more universal. Settings are stored in a dictionary consisting of key-value pairs. It means that each setting is referenced via a user-defined string key which has a specific type associated with it. The dictionary is empty when the Analysis is first launched. Any entries added to the Settings Dictionary are saved between updates and can be stored with the session. The following types can be stored in the settings: Integer, Float (32-bit), Double (64-bit), Boolean, String, Byte
The declaration of the dictionary is done into the Program.cs file. The variables are then passed between the settings and the analysis. Here is the dictionary of keys in the TOF User Analysis:
//declare public variables called keys to pass between settings and analysis
public const string S_filter = "filter";
public const string S_ShowAs = "ShowAs";
public const string S_ZRDFile = "ZRDFile";
public const string S_FirstTime = "FirstTime";
public const string S_ShowData = "ShowData";
public const string S_MinFlux = "MinFlux";
All those keys are settings except for the S_FirstTime key. The S_FirstTime key is used to record if the User Analysis is launched for the first time. If false, then the settings haven’t been set. A message is displayed to ask the user to select the settings first.
// Basic checks
IUserTextData userText = TheAnalysisData.MakeText();
userText.Data = "Run the settings first";
The Settings are defined in a separate class called
AnalysisSettingsForm. The AnalysisSettingsForm class is part of the User-Analysis template. That class defines the default values as well as the design of the settings window:
The User-Analysis template will create an empty Settings window. Using the Toolbox, Windows Forms can be added manually to define the settings. For example, the ZRD File selection is done with a ComboBox that displays a Drop Down List of ZRD files. The Detector number is a TextBox.
The User-Analysis template contains a function called ShowUserAnalysisSettings in the Program.cs file. That function will display the form to modify the settings and will then retrieve the settings.
In the code, an object called SettingsForm is defined which is an instance of the AnalysisSettingsForm class. The form is then displayed. When the user has defined the settings and clicked OK, the keys are assigned a value.
For example, the “Show As” setting which is SettingsForm.ShowData in the code is read. Then the value is set to the S_ShowData key.
When the User-Analysis is run with the function RunUserAnalysis in the Program.cs file, the S_ShowData key is read and the value is assigned to a variable called ShowData.
// Read the public variables / keys
// Define new variables (out) and assign a value to those variables
TheSettings.GetBooleanValue(S_FirstTime, out bool FirstTime);
TheSettings.GetStringValue(S_filter, out string filter);
TheSettings.GetStringValue(S_ShowAs, out string ShowAs);
TheSettings.GetStringValue(S_ZRDFile, out string ZRDFile);
TheSettings.GetStringValue(S_ShowData, out string ShowData);
TheSettings.GetStringValue(S_MinFlux, out string MinFlux);
Depending on the settings and namely the value of ShowData, different outputs are displayed:
case "Distance to Object":
gridPlot.ValueLabel = "Distance to Object: Half Path length in m";
case "Number of Hits":
gridPlot.ValueLabel = "Number of Hits";
case "Time Of Flight":
gridPlot.ValueLabel = "Time of Flight in us";
gridPlot.ValueLabel = "Flux in W";
The solution can then be built in Release Mode. Using Debug mode is fine when debugging. But to distribute the User-Analysis as a plug-in, set it in Release mode since the Debug libraries are not redistributable.
The executable can then be copied from the solution folder (in ‘…Documents\Zemax\ZOS-API Projects\...’) to ‘‘...Documents\Zemax OpticStudio\ZOS-API\User Analysis’
To check the TOF User Analysis, start OpticStudio and open the lens file “Flash_LIDAR_NSC.zar”. Under the Programming tab > User Analyses, there should now be the newly created analysis.
First define the settings:
Click OK to generate the plot showing the distance of the surrounding objects:
In this example, the detector is reading the path length of light but there is no notion of time. This is something that could be done using ZOS-API.
The LIDAR source is a pulse laser. The analysis could:
- Send light as a pulse laser
- Record the ZRD file at a given frequency
- Display / Process the result
Previous article: How to create a User-Extension: Converting from Chebyshev to Extended Polynomial
Next article: How to create a User-Operand: Thickness control operand