Skip to content

02. Using PyZDDE interactively in a Python shell

Reza Tafteh edited this page Nov 18, 2016 · 19 revisions

There are two modes of working interactively with Zemax from a Python shell using PyZDDE -- APR mode and standard mode. The lens that is accessible to any of the Zemax DDE extensions, including PyZDDE, resides in the Zemax DDE server. Any modification to the lens in the server is not propagated to the LDE. In order to move a lens back and forth between the DDE server and the LDE, the user needs to explicitly call zPushLens() and zGetRefresh(). This is the default behavior, so we call it the standard mode.
In the APR mode (currently experimental), the modifications to a lens by a specific set of commands in the server are instantaneously propagated to the LDE. These specific set of commands (currently) include all commands which start with zGet, zSet, zInsert and zDelete. This mode is enabled using a boolean member variable of a PyZDDE instance like so:

ln = pyz.createLink()
ln.apr = True

Example of an interactive session in the APR mode

The following Youtube three and half minute video shows an example interactive session in APR mode:

Automatic Push Refresh Interactive Session

Example of an interactive session in the standard mode

The following is simply a sample how PyZDDE can be used with Zemax from a Python shell, in particular, the IPython/Jupyter QtConsole. Please, note that this sample is not meant to be exhaustive in any way. It also assumes that a Zemax application window is already open (If Zemax is not running, you will see an error message like ERROR: Unable to establish a conversation with server (err=0x400aL). when you try to create a link.)

In [1]: import pyzdde.zdde as pyz

In [2]: # create a link object

In [3]: ln = pyz.createLink()

In [4]: # check to see that the communication is up and running

In [5]: ln.zGetVersion()
Out[5]: 150624

In [6]: # See the link object repr

In [7]: ln
Out[7]: PyZDDE(appName='ZEMAX', appNum=1, connection=True, macroPath=None)

In [8]: # load a lens file into the zemax DDE server (not in the LDE)

In [9]: ln.zLoadFile("C:\Users\Indranil\Documents\ZEMAX\Samples\Sequential\Objectives\Cooke 40 degree field.zmx")
Out[9]: 0

In [10]: # see the currently loaded file

In [11]: ln.zGetFile()
Out[11]: 'C:\\Users\\Indranil\\Documents\\ZEMAX\\Samples\\Sequential\\Objectives\\Cooke 40 degree field.zmx'
In [12]: # see surfaces in the LDE (this is an ipz helper function ... note "ipz" instead of just "z" 

In [13]: ln.ipzGetLDE()
SURFACE DATA SUMMARY:

Surf     Type         Radius      Thickness                Glass      Diameter          Conic   Comment
 OBJ STANDARD       Infinity       Infinity                                  0              0
   1 STANDARD       22.01359       3.258956                 SK16            19              0
   2 STANDARD      -435.7604       6.007551                                 19              0
   3 STANDARD      -22.21328      0.9999746                   F2            10              0
 STO STANDARD       20.29192       4.750409                                 10              0
   5 STANDARD        79.6836       2.952076                 SK16            15              0
   6 STANDARD      -18.39533       42.20778                                 15              0
 IMA STANDARD       Infinity                                          36.34532              0

In [14]: # see the first order properties of the system

In [15]: ln.ipzGetFirst()
Paraxial magnification : 0.0
Real working F/#       : 4.978219667
Effective focal length : 49.9999995
Paraxial working F/#   : 4.99999995
Paraxial image height  : 18.19851153

In [16]: # again, the above function was a duplicate special function of zGetFirst().

In [17]: ln.zGetFirst()
Out[17]: firstOrderData(EFL=49.9999995, paraWorkFNum=4.99999995, realWorkFNum=4.978219667, paraImgHeight=18.19851153, paraMag=0.0)

In [18]: # see the pupil definitions

In [19]: ln.ipzGetPupil()
Exit pupil position (from IMA)           : -50.96133885
Entrance pupil position (from surface 1) : 11.51215705
Aperture Type                            : EPD
Apodization factor                       : 0.0
Apodization type                         : None
Value (system aperture)                  : 10.0
Exit pupil diameter                      : 10.23372788
Entrance pupil diameter                  : 10.0
In [20]: # check out the fields defined

In [21]: ln.ipzGetFieldData()
Field Normalization : Radial
Type                : Angles in degrees
Number of Fields    : 3
Max Y               : 20.0
Max X               : 20.0
   X       Y     Weight   VDX     VDY     VCX     VCY     VAN   
 0.00    0.00   1.0000  0.0000  0.0000  0.0000  0.0000  0.0000  
 0.00    14.00  1.0000  0.0000  0.0000  0.0000  0.0000  0.0000  
 0.00    20.00  1.0000  0.0000  0.0000  0.0000  0.0000  0.0000  

In [22]: # let's generate a spiral spot 

In [23]: x, y, _, _ = ln.zSpiralSpot(hx=0, hy=0.4, waveNum=1, spirals=10, rays=1000) 

In [24]: # import the matplotlib library to plot 

In [25]: import matplotlib.pyplot as plt

In [27]: plt.scatter(x, y, s=15, c='m', lw=0.3)
Out[27]: <matplotlib.collections.PathCollection at 0x1edda668>

In [28]: plt.show()

InteractiveDemoImg01

In [29]: # the seidel aberration coefficients

In [30]: ln.zGetSeidelAberration()
Out[30]: 
{u'W020': -0.9125,
 u'W040': 1.6753,
 u'W111': -0.1793,
 u'W131': -1.2605,
 u'W220M': 4.2876,
 u'W220P': 13.2257,
 u'W220S': 8.7567,
 u'W220T': -0.1814,
 u'W222': -8.9381,
 u'W311': -2.1104}

In [31]: # distance of the image plane from the last surface

In [32]: ln.zGetSurfaceData(surfNum=6, code=ln.SDAT_THICK)
Out[32]: 42.2077801

In [33]: # let's change the distance and quick focus again (for no particular reason)

In [34]: ln.zSetSurfaceData(surfNum=6, code=ln.SDAT_THICK, value=10)
Out[34]: 10.0

In [35]: # now let's get and plot the FFT MTFs

In [36]: mtfdata = ln.zGetMTF(which='fft')

In [37]: len(mtfdata)
Out[37]: 1

In [38]: # the above mtfdata contains the mtf for only a single field (because of a preconfigured configuration file 
         # that is not shown in this interaction, see the function's docstring for more details on how to change settings)

In [39]: mtfdata
Out[39]: (MTF(SpatialFreq=[0.0, 2.0, 4.0, 6.0, 8.0, 10.0, 12.0, ...], 
              Tangential=[1.0, 0.327242, 0.0, 0.0, 0.02436, ...], 
              Sagittal=[1.0, 0.327242, 0.0, 0.0, 0.02436, ...]),)

In [40]: # the mtfdata structure is shown above.

In [42]: plt.plot(mtfdata[0].SpatialFreq, mtfdata[0].Tangential)
Out[42]: [<matplotlib.lines.Line2D at 0x1f1eae10>]

In [43]: plt.plot(mtfdata[0].SpatialFreq, mtfdata[0].Sagittal)
Out[43]: [<matplotlib.lines.Line2D at 0x1f37d6a0>]

In [44]: plt.xlabel('Spatial Frequency in cycles/mm')
Out[44]: <matplotlib.text.Text at 0x1f182278>

In [45]: plt.ylabel('Modulus of OTF')
Out[45]: <matplotlib.text.Text at 0x1f189550>

In [46]: plt.show()

InteractiveDemoImg02

In [47]: # Let's use quick focus to restore the thickness of the last lens surface

In [48]: ln.zQuickFocus()
Out[48]: 0

In [49]:

In [50]: # check the value of the surface thickness

In [51]: ln.zGetSurfaceData(surfNum=6, code=ln.SDAT_THICK)
Out[51]: 42.20776312123

In [52]: cfgfile = ln.zSetFFTMTFSettings(sample=4, wave=0, field=0)

In [53]: mtfdata = ln.zGetMTF(which='fft', settingsFile=cfgfile)

In [54]: len(mtfdata)
Out[54]: 3

In [55]: for field, mtf in enumerate(mtfdata):
    ...:     plt.plot(mtf.SpatialFreq, mtf.Tangential, label='F-{}, T'.format(field+1))
    ...:     plt.plot(mtf.SpatialFreq, mtf.Sagittal, label='F-{}, S'.format(field+1))
    ...:     plt.xlabel('Spatial Frequency in cycles/mm')
    ...:     plt.ylabel('Modulus of OTF')
    ...:     plt.grid('on')
    ...: plt.legend(frameon=False)
    ...: plt.show()
    ...: 

InteractiveDemoImg03

In [56]: # getting help on a PyZDDE method

In [57]: # if you are within an IPython shell, use the question mark (as we do here) 

In [58]: # if you are in a standard Python shell, use the help , e.g. help(ln.zOperandValue)

In [59]: ln.zOperandValue? 
Signature: ln.zOperandValue(operandType, *values)
Docstring:
Returns the value of any optimization operand, even if the
operand is not currently in the merit function.

Parameters
----------
operandType : string
    a valid optimization operand
*values : flattened sequence
    a sequence of arguments. Possible arguments include:
    ``int1`` (column 2, integer), ``int2`` (column 3, integer),
    ``data1`` (column 4, float), ``data2`` (column 5, float),
    ``data3`` (column 6, float), ``data4`` (column 7, float),
    ``data5`` (column 12, float), ``data6`` (column 13, float)

Returns
-------
operandValue : float
    the value of the operand

Examples
--------
The following example retrieves the total optical path length
of the marginal ray between surfaces 1 and 3

>>> ln.zOperandValue('PLEN', 1, 3, 0, 0, 0, 1)

See Also
--------
zOptimize():
    to update MFE prior to calling ``zOperandValue()``, call
    ``zOptimize(-1)``
zGetOperand(), zSetOperand()
File:      c:\...\pyzdde\zdde.py
Type:      instancemethod
In [60]:

In [60]: # so the function zGetOperand() can retrieve the value of any operand

In [61]: # Let's suppose we want to find the angular magnification of the system

In [62]: # and we don't remember the particular operand. PyZDDE provides a helper

In [63]: # function to find operands based on keywords

In [64]: 

In [65]: pyz.findZOperand('angular')
[CNAX] Centroid angular x direction. See also CNAY, CNPX, CNPY, CENX, CENY.
[CNAY] Centroid angular y direction. See CNAX.
[ANAR] Angular aberration radius measured in image space at the wavelength defined by Wave with respect to the primary wavelength chief ray.
[ANAX] Angular aberration x direction measured in image space at the wavelength defined by Wave with respect to the primary wavelength chief ray.
[ANAY] Angular aberration y direction measured in image space at the wavelength defined by Wave with respect to the primary wavelength chief ray.
[ANAC] Angular aberration radial direction measured in image space with respect to the centroid at the wavelength defined by Wave.
[AMAG] Angular magnification, defined as the ratio of the paraxial image space chief ray angle to the paraxial object space chief ray angle.
[ANCX] Angular aberration x direction measured in image space at the wavelength defined by Wave with respect to the centroid.
[ANCY] Angular aberration y direction measured in image space at the wavelength defined by Wave with respect to the centroid.
Found 9 Optimization operands

In [66]: # had we just searched "angular magnification" ...

In [67]: pyz.findZOperand('angular magnification')
[AMAG] Angular magnification, defined as the ratio of the paraxial image space chief ray angle to the paraxial object space chief ray angle.
Found 1 Optimization operands

In [68]:

In [69]:

In [70]: # Now, we call zOperandValue() to get the angular magnification

In [71]: ln.zOperandValue('AMAG', 2)
Out[71]: 0.977161033
In [72]: # all operations we performed upto this point was done on the LENS that present in the DDE server. There may or may not be a lens present in the main Zemax window/LDE. To push the lens from the DDE server to the LDE:

In [72]: 

In [73]: ln.zPushLens(1)
Out[73]: 0

In [74]: # conversly, to transfer a lens from the LDE to the DDE server use zGetRefresh()

In [75]:

In [76]: ln.zGetRefresh()
Out[76]: 0

In [77]: # close the link

In [78]: ln.close()