-
Notifications
You must be signed in to change notification settings - Fork 67
02. Using PyZDDE interactively in a Python shell
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
The following Youtube three and half minute video shows an example interactive session in APR 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()
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()
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()
...:
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()
This work by PyZDDE contributors is licensed under a Creative Commons Attribution 4.0 International License.
Based on a work at https://github.com/indranilsinharoy/PyZDDE.