How to convert any sequential surface to a Grid Sag Surface using the ZOS-API

This article provides Mathematica and Matlab code to replace any sequential surface with a Grid Sag surface using the ZOS-API. The Grid Sag surface is highly-customizable, and may be used to represent errors and artifacts that are not easily represented by OpticStudio's native tolerancing algorithm.

Authored By Alexandra Culler & Erin Elliott


Article Attachments


In "How to use the Grid Sag surface type" we walked through how to import a Grid Sag surface manually. In "How to write a Grid Sag DAT file programmatically" we walked through how to build the corresponding DAT file programmatically. Now, we will use the ZOS-API to update a surface in an already-built system and compare the system performance before and after the change. In particular, the spot diagram and wavefront error will be used for comparison.

Once the process has proven successful, several modules will be provided to help automate the process.

Lens Data Editor and System Explorer modules

This tutorial includes some snippets of code that allow us to pull the LDE and System Explorer data into the API. This will allow us to keep track of the updates we're making to the model.

In Matlab, these snippets – known as functions – are defined below the end of TheApplication and may be called at any time by typing the name of the function. Currently, the output is limited to the Command Window, but the table output may be modified to show as a pop-up.

In both of these functions, the following is assumed:

TheLDE = TheSystem.LDE

SystemData = TheSystem.SystemData

If these variable names are altered, then the update will need to be made to the modules/functions as well!

Open and investigate the "Cooke 40 degree field.zmx" sample file

To begin, the attached API programs will check for the existence of an API folder in the native {Zemax}\Samples folder. If the folder doesn't exist, then one will be created.

Once complete, the API will open the "Cooke 40 degree field.zmx" file found in {Zemax}\Samples\Sequential\Objectives and will save it as a new file in the appropriate API folder. The new file name will be "Cooke 40 degree field_Surf1_GridSag.zmx".

To confirm that we have successfully pulled the file into the API, we will use the Lens Data Editor and System Explorer modules to look at the current system. These modules will present the initial LDE and System Explorer settings, as shown below:

This information will help to keep track of the surface properties as we update Surface 1. We should find that nothing else is changed when we replace the Standard Surface with a Grid Sag Surface.

To begin, we will remove the variables and solves in the system. This will ensure that all distances and radii remain fixed while we replace Surface 1.

surf6 = TheLDE.GetSurfaceAt(6);

surf6Radius = surf6.Radius;


solver = surf6.RadiusCell.CreateSolveType(ZOSAPI.Editors.SolveType.Fixed);


surf6.Radius = surf6Radius;

Now, we will need to extract the relevant information from Surface 1. As is discussed in the Knowledgebase article "How to write a Grid Sag DAT file programmatically," we will need the Semi-Diameter, Radius, and Conic constant of the surface to generate the DAT file. We can extract that information with the following commands:


Surface1 = TheLDE.GetSurfaceAt(1);


SemiDiaSurf1 = Surface1.SemiDiameter;

RadiusSurf1 = Surface1.Radius;

CurvatureSurf1 = 1/RadiusSurf1;

ConicSurf1 = Surface1.Conic;

We can quickly compare the extracted values to the LDE to confirm that we have the correct information.

Identify the current system performance

Our goal is to replace Surface 1 with an identical copy. To confirm if we have completed that task, we will need to compare the system performance before and after the replacement. Let's grab the Spot Diagram and Wavefront Error information from the current system. We may do so in the API, which is discussed in the Knowledgebase aritlce "How to add radial ripple to any sequential surface using the ZOS-API." In this article, we will simply observe the performance by manually opening the file in OpticStudio.

Access the Spot Diagram by navigating to Analyze…Rays & Spots…Standard Spot Diagram. Update the ray density to 25.

Access the Wavefront Map by navigating to Analyze…Wavefront…Wavefront Map. Update the resolution to 512x512. Set Wavelength = 2 and Field = 1.

We will see the following system specifications:

Specification Value
Field 1 RMS Spot Size 5.054
Field 2 RMS Spot Size 15.361
Field 3 RMS Spot Size 11.900
Wavefront Error PV 0.6434
Wavefront Error RMS 0.1829

Extract the sag profile of surface 1

To keep track of the surface shape, we will draw the information from the Surface Sag tool into the API. In the UI, this tool is found under Analyze…Surface…Sag. In general, the most accurate representation of the surface will be provided at higher resolutions. OpticStudio provides several options here:

  • 33x33
  • 65x65
  • 129x129
  • 257x257
  • 513x513
  • 1025x1025
  • 2049x2049
  • 4097x4097
  • 8193x8193
  • 16385x16385

We will build the DAT file to have the same resolution as what we choose for the Surface Sag plot. The memory consumed by DAT files is proportional to their size (as discussed in "How to use the Grid Sag surface type") so we will pick a reasonably high resolution, but nothing so high as to decrease the performance of the lens file.

We may open the Surface Sag plot and its settings with the following commands:

% Open sag plot and update settings

SurfSag = TheSystem.Analyses.New_SurfaceSag();

SagSettings = SurfSag.GetSettings();

Now, we may update the settings to focus the analysis on Surface 1, and to increase the resolution to 513x513:

SagSettings.Sampling = ZOSAPI.Analysis.SampleSizes_Pow2Plus1_X.S_513x513;



Once the settings have been applied, we can extract the sag data with the following commands:

% Grab the data from the surface sag plot

Surf1 = SurfSag.GetResults();

Surf1Dat = Surf1.GetDataGrid(0);

Surf1Results = flipud(Surf1.DataGrids(1).Values.double);


npix = Surf1Dat.Nx;

dx = Surf1Dat.Dx;



In the above, we are pulling the data into a matrix "Surf1Results" of double values. We are also extracting the resolution and point spacing information for use in generating the DAT file. This is discussed in more detail in the Knowledgebase article "How to write a Grid Sag DAT file programmatically."

The data is pulled into the API as a rectangular matrix, yet the surface profile is circular. This means that there will be "throwaway" values outside of the surface sag information. Matlab will report "NaN" in these locations. We will replace these values with "0" using the following code:

Surf1Results(isnan(Surf1Results)) = 0;

To ensure we have pulled the appropriate data, we can generate a plot using this new matrix and compare it to the Surface Sag plot as given in the OpticStudio GUI. This is shown below. On the left is the Matlab output and on the right is the OpticStudio output with the same settings:

The plots will not be identical, but should show the same trends. By comparing these two plots, we are checking for unusual slope and edge artifacts. There are none shown here.

Save the data to an array

While we may use the Surface Sag plot as a visual aid, it may not be accurate when writing to our DAT plot. This is because we are making a big assumption that the values on the outside of the surface should be zero. In order to obtain the most accurate information for our DAT file, we will instead use the SSAG optimization operand to pull the sag profile data. The SSAG operand will extrapolate the values of the sag profile beyond the aperture. The parameters for this operand are below:

We will use SSAG to populate a matrix that has the same resolution as the Surface Sag plot. This matrix will have point spacing defined by:

This will be the same point spacing as in the Surface Sag plot. We have stored this value as dx.

In Matlab, we must first generate two arrays that represent the point spacing. This is discussed in the Knowledgebase article "How to write a Grid Sag DAT file programmatically." The full code for the matrix generation is below:

fullSurfSag = zeros(npix);

xvals = linspace(-surf1SemiDiameter, surf1SemiDiameter, npix);

yvals = linspace(-surf1SemiDiameter, surf1SemiDiameter, npix);


% Use the arrays to generate points to place within the sag matrix

for col = 1:length(fullSurfSag)

    for row = 1:length(fullSurfSag)

        fullSurfSag(col, row) = TheMFE.GetOperandValue(ZOSAPI.Editors.MFE.MeritOperandType.SSAG, 1, 0, xvals(col), yvals(row), 0, 0, 0, 0);



Once the matrix is generated, we can load it directly into a DAT file.

Replace Surface 1 with the Grid Sag representation

As discussed in "How to write a Grid Sag DAT file programmatically," the first step in converting the sag information to a DAT file is to "flatten" the matrix into a 263169x1 array. This will allow us to iterate through the data and place one number at a time in a new line of the DAT file.

To perform this operation in Matlab, we use the following:

Surf1DataClean = fullSurfSag(:);

The code for writing this information to the DAT file is provided and is discussed in detail in "How to write a Grid Sag DAT file programmatically."

Once the DAT file is generated, we can use the API to replace Surface 1. First, we will access the surface type and update it to a Grid Sag Surface. Then, we will change the Radius and Conic values of Surface 1 to "0". The reason for this is discussed in detail in "How to use the Grid Sag surface type."

Finally, we will import that DAT file which should be a replication of the sag profile of the original surface.

The code to do this is shown below:

% Change the surface type to Grid Sag

Surf1TypeSetting = Surface1.GetSurfaceTypeSettings(ZOSAPI.Editors.LDE.SurfaceType.GridSag);


Surface1.Radius = 0;

Surface1.Conic = 0;

% Import the data file


To confirm that the changes have been made, we can make a call to the displayLDE() function once more. We should find that Surface 1 now has the type "Grid Sag" and that the "Comment" is populated with the name of the DAT file:

We will also call the Surface Sag plot once more to compare the output with the new surface to the output with the original surface. Qualitatively, the two surfaces seem to be identical.

Let's save these changes and check the new performance in the OpticStudio GUI:


Check the performance of the updated system

The resulting system is attached to this article. When opened, we see the following system performance:

Specification Value Difference from initial file
Field 1 RMS Spot Size 5.057 0.003
Field 2 RMS Spot Size 15.363 0.002
Field 3 RMS Spot Size 11.897 0.003
Wavefront Error PV 0.6441 0.0007
Wavefront Error RMS 0.1831 0.0002

With the above data, we see a minute difference between the system with the Standard surface and the system with the Grid Sag. This is likely due to the chosen resolution. Had we chosen a higher resolution, the difference would be even smaller.

It is up to you to determine if this variation is large enough to warrant concern. Remember that the higher the resolution of the DAT file, the more memory is consumed when it is imported into the system. Thus, it will be important to strike a balance between accuracy and speed.

Similarly, there will be a small difference (on the order of 10^-3) depending on the significant figures used in the point spacing. The default amount of significant digits is different between programs.  

Generate functions for future automation

Now that we know the process for converting a surface to a DAT file, we can generate functions and modules to carry through multiple API files. We began this tutorial with two of these: displayLDE and displaySystemExplorer. After this tutorial we can create three more:

  • getSag: Used to open a Surface Sag plot for surface visualization
  • getExtendedSag: Used to generate a full sag profile for writing to a DAT file
  • makeDatFile: Used to flatten the sag array and produce a DAT file within the {Zemax}\Objects\Grid Files folder.

The three new modules will be located at the end of the attached Mathematica file in section 8. The three new functions will be provided as separate Matlab files within the provided samples.

Notes about Mathematica's connection to the ZOS-API

Because Mathematica is not a native API language, we must manually build the boilerplate code before we connect to OpticStudio. The provided code will use Mathematica's NETLink capability to search for the location of OpticStudio. However, the code will not check for a valid license or for the amount of OpticStudio sessions already open. If the code does not run, please ensure that you do not already have the maximum number of OpticStudio sessions open.

Beyond this change, the commands for the API are very similar to those in Matlab. Any major differences will be listed here:

  • In Mathematica, the LDE and System Explorer functions – known as modules – are given in Section 2 of the provided code. They may be called at any point in the code by typing the module titles: displayLDE and displaySystemData.
  • Variable names begin with a lowercase letter in Mathematica to avoid confusion with built-in functions.
  • A NaN point in MATLAB is reported as "Indeterminate" in Mathematica. The operation to convert this data to "0" is built into the generation of a new matrix "surf1DataClean":

    surf1DataClean = Table[If[NumberQ[surf1Data@Z[j-1, k-1]] == True, surf1Data@Z[j-1, k-1], 0], {j, 1, npix}, {k, 1, npix}];
  • The code in Mathematica to explicitly declare the spacing between points is built directly into the matrix generation:

    fullSurfaceSag = Table[theMFE@GetOperandValue[MeritOperandType`SSAG, 1, 0, xcoord, ycoord, 0, 0, 0, 0], {xcoord, -surf1SemiDiameter, surf1SemiDiameter, dx}, {ycoord, -surf1SemiDiameter, surf1SemiDiameter, dx}]
  • In the DAT file generation within the Mathematica code, there is a check for a file with the same filename in the specified directory. Mathematica does not have overwrite privileges so there is a line of code that allows the user to add an iteration number to append to the filename if necessary.


Was this article helpful?
1 out of 1 found this helpful



Article is closed for comments.