PythonStandalone_23_ray_fan_native_manual_comparison
Last update: 17.07.2025 1import clr, os, winreg
2from itertools import islice
3
4import time
5import matplotlib.pyplot as plt
6import numpy as np
7
8from System import Enum, Int32, Double
9
10class PythonStandaloneApplication(object):
11 class LicenseException(Exception):
12 pass
13 class ConnectionException(Exception):
14 pass
15 class InitializationException(Exception):
16 pass
17 class SystemNotPresentException(Exception):
18 pass
19
20 def __init__(self, path=None):
21 # determine location of ZOSAPI_NetHelper.dll & add as reference
22 aKey = winreg.OpenKey(winreg.ConnectRegistry(None, winreg.HKEY_CURRENT_USER), r"Software\Zemax", 0, winreg.KEY_READ)
23 zemaxData = winreg.QueryValueEx(aKey, 'ZemaxRoot')
24 NetHelper = os.path.join(os.sep, zemaxData[0], r'ZOS-API\Libraries\ZOSAPI_NetHelper.dll')
25 winreg.CloseKey(aKey)
26 clr.AddReference(NetHelper)
27 import ZOSAPI_NetHelper
28
29 # Find the installed version of OpticStudio
30 if path is None:
31 isInitialized = ZOSAPI_NetHelper.ZOSAPI_Initializer.Initialize()
32 else:
33 # Note -- uncomment the following line to use a custom initialization path
34 isInitialized = ZOSAPI_NetHelper.ZOSAPI_Initializer.Initialize(path)
35
36 # determine the ZOS root directory
37 if isInitialized:
38 dir = ZOSAPI_NetHelper.ZOSAPI_Initializer.GetZemaxDirectory()
39 else:
40 raise PythonStandaloneApplication.InitializationException("Unable to locate Zemax OpticStudio. Try using a hard-coded path.")
41
42 # add ZOS-API referencecs
43 clr.AddReference(os.path.join(os.sep, dir, "ZOSAPI.dll"))
44 clr.AddReference(os.path.join(os.sep, dir, "ZOSAPI_Interfaces.dll"))
45 import ZOSAPI
46
47 # create a reference to the API namespace
48 self.ZOSAPI = ZOSAPI
49
50 # create a reference to the API namespace
51 self.ZOSAPI = ZOSAPI
52
53 # Create the initial connection class
54 self.TheConnection = ZOSAPI.ZOSAPI_Connection()
55
56 if self.TheConnection is None:
57 raise PythonStandaloneApplication.ConnectionException("Unable to initialize .NET connection to ZOSAPI")
58
59 self.TheApplication = self.TheConnection.CreateNewApplication()
60 if self.TheApplication is None:
61 raise PythonStandaloneApplication.InitializationException("Unable to acquire ZOSAPI application")
62
63 if self.TheApplication.IsValidLicenseForAPI == False:
64 raise PythonStandaloneApplication.LicenseException("License is not valid for ZOSAPI use")
65
66 self.TheSystem = self.TheApplication.PrimarySystem
67 if self.TheSystem is None:
68 raise PythonStandaloneApplication.SystemNotPresentException("Unable to acquire Primary system")
69
70 def __del__(self):
71 if self.TheApplication is not None:
72 self.TheApplication.CloseApplication()
73 self.TheApplication = None
74
75 self.TheConnection = None
76
77 def OpenFile(self, filepath, saveIfNeeded):
78 if self.TheSystem is None:
79 raise PythonStandaloneApplication.SystemNotPresentException("Unable to acquire Primary system")
80 self.TheSystem.LoadFile(filepath, saveIfNeeded)
81
82 def CloseFile(self, save):
83 if self.TheSystem is None:
84 raise PythonStandaloneApplication.SystemNotPresentException("Unable to acquire Primary system")
85 self.TheSystem.Close(save)
86
87 def SamplesDir(self):
88 if self.TheApplication is None:
89 raise PythonStandaloneApplication.InitializationException("Unable to acquire ZOSAPI application")
90
91 return self.TheApplication.SamplesDir
92
93 def ExampleConstants(self):
94 if self.TheApplication.LicenseStatus == self.ZOSAPI.LicenseStatusType.PremiumEdition:
95 return "Premium"
96 elif self.TheApplication.LicenseStatus == self.ZOSAPI.LicenseStatusTypeProfessionalEdition:
97 return "Professional"
98 elif self.TheApplication.LicenseStatus == self.ZOSAPI.LicenseStatusTypeStandardEdition:
99 return "Standard"
100 else:
101 return "Invalid"
102
103 def reshape(self, data, x, y, transpose = False):
104 """Converts a System.Double[,] to a 2D list for plotting or post processing
105
106 Parameters
107 ----------
108 data : System.Double[,] data directly from ZOS-API
109 x : x width of new 2D list [use var.GetLength(0) for dimension]
110 y : y width of new 2D list [use var.GetLength(1) for dimension]
111 transpose : transposes data; needed for some multi-dimensional line series data
112
113 Returns
114 -------
115 res : 2D list; can be directly used with Matplotlib or converted to
116 a numpy array using numpy.asarray(res)
117 """
118 if type(data) is not list:
119 data = list(data)
120 var_lst = [y] * x;
121 it = iter(data)
122 res = [list(islice(it, i)) for i in var_lst]
123 if transpose:
124 return self.transpose(res);
125 return res
126
127 def transpose(self, data):
128 """Transposes a 2D list (Python3.x or greater).
129
130 Useful for converting mutli-dimensional line series (i.e. FFT PSF)
131
132 Parameters
133 ----------
134 data : Python native list (if using System.Data[,] object reshape first)
135
136 Returns
137 -------
138 res : transposed 2D list
139 """
140 if type(data) is not list:
141 data = list(data)
142 return list(map(list, zip(*data)))
143
144if __name__ == '__main__':
145 zos = PythonStandaloneApplication()
146
147 # load local variables
148 ZOSAPI = zos.ZOSAPI
149 TheApplication = zos.TheApplication
150 TheSystem = zos.TheSystem
151
152 # Set up primary optical system
153 sampleDir = TheApplication.SamplesDir
154 testFile = sampleDir + "\\Sequential\\Objectives\\Double Gauss 28 degree field.zos"
155 TheSystem.LoadFile(testFile, False)
156
157 max_rays = 150
158 #! [e23s01_py]
159 max_field = 0
160 for i in range(1,TheSystem.SystemData.Fields.NumberOfFields + 1):
161 if TheSystem.SystemData.Fields.GetField(i).Y > max_field:
162 max_field = TheSystem.SystemData.Fields.GetField(i).Y
163 #! [e23s01_py]
164
165 max_num_field = TheSystem.SystemData.Fields.NumberOfFields
166
167 # sets up plot
168 plt.close('all')
169 plt.rcParams["figure.figsize"] = (15, 8)
170 colors = ('b', 'g', 'r', 'c', 'm', 'y', 'k')
171
172 tic = time.time()
173
174 #! [e23s02_py]
175 # Set up Batch Ray Trace
176 raytrace = TheSystem.Tools.OpenBatchRayTrace()
177 nsur = TheSystem.LDE.NumberOfSurfaces
178 normUnPolData = raytrace.CreateNormUnpol(max_rays + 1, ZOSAPI.Tools.RayTrace.RaysType.Real, nsur)
179 #! [e23s02_py]
180
181 #! [e23s03_py]
182 # define batch ray trace constants
183 hx = 0
184 # since python doesn't include STOP number in range, need to use STOP value slightly more than 1
185 py_ary = np.arange(0, 1.0001, 1 / max_rays) * 2 - 1
186 px = 0
187 max_wave = TheSystem.SystemData.Wavelengths.NumberOfWavelengths
188 #! [e23s03_py]
189
190 # initilize x/y image plane arrays
191 y_ary = np.zeros((max_num_field + 1, max_wave + 1, max_rays + 1))
192
193 #! [e23s04_py]
194 # image surface number and primary wavelength
195 nsur = TheSystem.LDE.NumberOfSurfaces
196 pwav = 0
197 for a in range(1, TheSystem.SystemData.Wavelengths.NumberOfWavelengths + 1):
198 if TheSystem.SystemData.Wavelengths.GetWavelength(a).IsPrimary == 1:
199 pwav = a
200
201 # creates array of Y coordinate chief ray values
202 chief_ary = np.zeros(max_num_field)
203 for field in range(1, max_num_field + 1):
204 #hy = TheSystem.SystemData.Fields.GetField(field).Y / max_field
205 hy = 1 if max_field == 0 else TheSystem.SystemData.Fields.GetField(field).Y / max_field
206 # gets single value without using MFE (see ZPL OPEV)
207 chief_ary[field - 1] = TheSystem.MFE.GetOperandValue(ZOSAPI.Editors.MFE.MeritOperandType.REAY, nsur, pwav, 0, hy , 0, 0, 0, 0)
208 #! [e23s04_py]
209
210 # setup plot
211 for field in range(1, max_num_field + 1):
212 if (field == 1):
213 ax1 = plt.subplot(2, max_num_field, field + max_num_field)
214 else:
215 plt.subplot(2, max_num_field, field + max_num_field)
216
217 hy = 1 if max_field == 0 else TheSystem.SystemData.Fields.GetField(field).Y / max_field
218
219 for wave in range(1, max_wave + 1):
220 #! [e23s05_py]
221 # Adding Rays to Batch, varying normalized object height hy
222 normUnPolData.ClearData()
223 for i in range(0, max_rays + 1):
224 py = py_ary[i]
225 normUnPolData.AddRay(Int32(wave), Double(hx), Double(hy), Double(px), Double(py), Enum.Parse(ZOSAPI.Tools.RayTrace.OPDMode, "None"))
226 #! [e23s05_py]
227
228 #! [e23s06_py]
229 # Run Batch Ray Trace
230 raytrace.RunAndWaitForCompletion()
231 #! [e23s06_py]
232
233 #! [e23s07_py]
234 # Read and display results
235 normUnPolData.StartReadingResults()
236
237 # Python NET requires all arguments to be passed in as reference, so need to have placeholders
238 sysInt = Int32(1)
239 sysDbl = Double(1.0)
240
241 output = normUnPolData.ReadNextResult(sysInt, sysInt, sysInt,
242 sysDbl, sysDbl, sysDbl, sysDbl, sysDbl, sysDbl, sysDbl, sysDbl, sysDbl, sysDbl, sysDbl);
243
244 while output[0]:
245 if (output[2] == 0 and output[3] == 0):
246 y_ary[field, wave, output[1] - 1] = output[5]
247
248 # Python NET requires all arguments to be passed in as reference, so need to have placeholders
249 output = normUnPolData.ReadNextResult(sysInt, sysInt, sysInt,
250 sysDbl, sysDbl, sysDbl, sysDbl, sysDbl, sysDbl, sysDbl, sysDbl, sysDbl, sysDbl, sysDbl);
251 plt.plot(py_ary[:], np.squeeze((y_ary[field, wave,:] - chief_ary[field - 1]) * 1000), '-', ms = 4)
252 #! [e23s07_py]
253 raytrace.Close();
254 manual = round(time.time() - tic, 3)
255
256 tic = time.time()
257
258 #! [e23s08_py]
259 ray = TheSystem.Analyses.New_Analysis(ZOSAPI.Analysis.AnalysisIDM.RayFan)
260 ray_settings = ray.GetSettings()
261 ray_settings.NumberOfRays = max_rays / 2
262 ray_settings.Field.UseAllFields()
263 ray_settings.Wavelength.UseAllWavelengths()
264
265 ray.ApplyAndWaitForCompletion()
266 ray_results = ray.GetResults()
267 #! [e23s08_py]
268
269 for field in range(1, max_num_field + 1):
270 #! [e23s09_py]
271 # Read and display results
272 if field == 1:
273 ax2 = plt.subplot(2, max_num_field, field)
274 else:
275 plt.subplot(2, max_num_field, field)
276
277 ds = ray_results.GetDataSeries(field *2 - 2)
278 # get raw .NET data into numpy array
279 xRaw = np.asarray(tuple(ds.XData.Data))
280 yRaw = np.asarray(tuple(ds.YData.Data))
281
282 x = xRaw
283 y = yRaw.reshape(ds.YData.Data.GetLength(0), ds.YData.Data.GetLength(1))
284
285 plt.plot(x, y)
286 hy = 1 if max_field == 0 else TheSystem.SystemData.Fields.GetField(field).Y / max_field
287 plt.title('Field: %4.3f' % (hy))
288 #! [e23s09_py]
289
290 native = round(time.time() - tic, 3)
291
292 print('Tracing ' + str(max_rays + 1) + ' number of rays')
293 print('Elapsed time is ' + str(native) + ' seconds with native code')
294 print('Elapsed time is ' + str(manual) + ' seconds with manual code')
295 print('The native code is ' + str(round((manual - native) / native * 100, 2)) + '% faster')
296
297 ax1.set_ylabel('Manual Code (%.3fs)' % (manual))
298 ax2.set_ylabel('Native Code (%.3fs)' % (native))
299
300 plt.subplots_adjust(wspace=0.5)
301
302 # This will clean up the connection to OpticStudio.
303 # Note that it closes down the server instance of OpticStudio, so you for maximum performance do not do
304 # this until you need to.
305 del zos
306 zos = None
307
308 # place plt.show() after clean up to release OpticStudio from memory
309 plt.show()
Definition: ZemaxService.cs:198