-
Beta Was this translation helpful? Give feedback.
Replies: 2 comments 4 replies
-
Hello @GY1015,
I could take a look. My expectation is the ray trace should work up to 90 deg. Beyond that, the ray aiming scheme would need changes, I'd wager. Modeling wide angle systems is challenging and it wouldn't surprise me if this system has some combination of attributes that defeats the current ray trace scheme. |
Beta Was this translation helpful? Give feedback.
-
Hello @GY1015, It's taken a while, but I think ray-optics is much more likely to trace a fisheye or similar lens using v0.9.5. Below is some background about the source of the issues and how it was solved. The only input change for your original model would be to add the kwarg The original example was a fisheye lens from the CODE V example library. The then current version of ray-optics could trace that lens out to about 70 deg. Investigation led to the use of a separate While investigating different models, it became apparent that being able to specify a field point in terms of the real ray height in the image plane would substantially increase the number of models that were accessible to ray-optics. This can be done now using the 'real height' field value type in image space. The wideangle module is where most of the new capability has implemented. It was introduced in v0.9.3 and refined in v0.9.5. The goal is for the user not to have to know about this module, but several functions in the module are used to calculate spherical aberration of the pupil in the notebook below. %matplotlib inline isdark = False from scipy import interpolate
from rayoptics.environment import *
from rayoptics.raytr.wideangle import * Wide Angle modelUse new setting in FieldSpec, is_wide_angleThe is_wide_angle=True mode in FieldSpec uses a completely different way of aiming rays to go through the entrance pupil. A distinguishing feature of wide angle or fisheye lenses is that the location of the entrance pupil can change dramatically from the paraxial position. Aiming rays at the paraxial entrance pupil plane becomes less effective as the field angle increases until it fails completely at 90 degrees. When is_wide_angle=True, a search is conducted parameterized by z_enp, the distance along the z axis, measured from the first surface, to the real entrance pupil for that field angle. At extreme fields, ray failures during the search require a two phase search approach: the first is to find a range of z_enp values that trace to the actual stop surface without ray failure. Once this range is determined, a standard ray iteration procedure is applied to find the exact z_enp going through the center of the stop surface. Once the chief ray has been determined for a field angle, the entrance pupil is taken as a disk perpendicular to the chief ray, rather than the optical axis. This approximates the actual behavior of the entrance pupil in wide angle lenses and avoids the foreshortening that occurs when using a plane perpendicular to the optical axis. opm = OpticalModel()
sm = opm['seq_model']
osp = opm['optical_spec']
fov = osp['fov']
pm = opm['parax_model']
em = opm['ele_model']
pt = opm['part_tree']
ar = opm['analysis_results'] osp['pupil'] = PupilSpec(osp, key=['object', 'epd'], value=0.25)
fov = osp['fov'] = FieldSpec(osp, key=['object', 'angle'], value=90., flds=[0., 0.7071, 1.0],
is_relative=True, is_wide_angle=True)
osp['wvls'] = WvlSpec([(480., 1.0), (550, 1.0), (620., 1.0)], ref_wl=1)
obj_dist = 1.0e10
sm.gaps[0].thi = obj_dist
sm.do_apertures = False
opm.radius_mode = True
sm.add_surface([4.7745, .1299, 1.713, 53.9], sd=2.0)
sm.add_surface([0.8821, .5688, 'air'], sd=0.88)
sm.add_surface([1.6822, .0938, 1.618, 63.3], sd=0.95)
sm.add_surface([0.8064, .3313, 'air'], sd=0.725)
sm.add_surface([1.6265, .4838, 1.595, 39.2], sd=0.6)
sm.add_surface([-0.7284, .0938, 1.623, 56.9], sd=0.6)
sm.add_surface([8.3331, .05, 'air'], sd=0.6)
sm.add_surface([7.6334, .1875, 1.805, 40.9], sd=0.6)
sm.add_surface([0, .0806, 'air'], sd=0.6)
sm.add_surface([0, .0938, 1.581, 41.0], sd=0.6)
sm.add_surface([0, .1413, 'air'], sd=0.6)
sm.add_surface([0, .1000, 'air'], sd=0.284)
sm.set_stop()
sm.add_surface([-2.8724, .0500, 1.713, 53.2], sd=0.6)
sm.add_surface([2.8034, .2500, 1.640, 60.0], sd=0.6)
sm.add_surface([-1.1513, .0063, 'air'], sd=0.6)
sm.add_surface([3.2583, .2919, 1.488, 70.1], sd=0.6)
sm.add_surface([-0.8827, .0913, 1.805, 25.4], sd=0.6)
sm.add_surface([-1.7116, 2.305685559121, 'air'], sd=0.6)
opm.update_model() sm.gaps[-1].thi = pm.thi_ht_solve(pm.ax, -2, 0.)
opm.update_model() sm.list_model()
Set vignettingSimilar to the difficulties finding the chief ray at large field angles, determining the vignetting factors for large fields is also troubled by ray failures. A new option, use_bisection, was added to the set_vignetting function and is True by default if the model is_wide_angle=True. use_bisection will use a bisection algorithm to find the limiting apertures for the edge rays at each field. cmds.set_vignetting(opm) listobj(fov)
layout_pltw = plt.figure(FigureClass=InteractiveLayout, opt_model=opm, is_dark=False).plot() Calculate the position of the entrance pupil, z_enp, across the FOVThe function find_real_enp may be used to find z_enp at a field point. The following cells loop over the entire field of view and prints and plots the results. The entrance pupil moves about 75% of the distance from the paraxial pupil to the front surface (about 0.66 inches) at the 90 deg field angle. z_enps = []
flds = []
obj_angs = []
cwl = osp['wvls'].central_wvl
print(" py obj angle z_enp")
for i, fld_ht in enumerate(np.arange(-1., 1.1, .1)):
fld = Field(y=fld_ht)
z_enp, rr = find_real_enp(opm, sm.stop_surface, fld, cwl)
d0 = rr.pkg.ray[0][mc.d]
ang_x = np.rad2deg(math.atan2(d0[0], d0[2]))
ang_y = np.rad2deg(math.atan2(d0[1], d0[2]))
print(f"{i:2d} {fld_ht:6.3f}: {ang_y:7.2f} {z_enp:8.4f}")
flds.append(fld_ht)
obj_angs.append(ang_y)
z_enps.append(z_enp)
tck = interpolate.splrep(obj_angs, z_enps)
polyline = np.linspace(obj_angs[0], obj_angs[-1], 50)
z_enps_intrp = interpolate.splev(polyline, tck, der=0)
plt.scatter(z_enps, obj_angs)
plt.plot(z_enps_intrp, polyline)
plt.title("Pupil Aberration")
plt.ylabel("object angle")
plt.xlabel("z_enp")
plt.show() Switch to real image height specificationIt is often both desireable and convenient to specify field via (paraxial) height in the image plane. For wide angle systems, this is problematic because the paraxial and real image heights can be very different due to the enormous image distortion in fish-eye lenses. What is needed is a specification in terms of the real image height. A new field value type has been added for image space specification: 'real height'. Get the image height of the chief ray at full fieldcr_max, cr_max_exp_seg = fov.fields[-1].chief_ray
list_ray(cr_max)
Change the field specification to 'real height' in image space.listobj(fov.set_key_value(('image', 'real height'), cr_max.ray[-1][mc.p][1]))
Evaluate pupil spherical aberrationTo evaluate pupil spherical for real image height specifications, use the eval_real_image_ht to calculate z_enp across the field of view. z_enps = []
flds = []
obj_angs = []
cwl = osp['wvls'].central_wvl
print(" py obj angle z_enp")
for i, fld_ht in enumerate(np.arange(-1., 1.1, .1)):
fld = Field(y=fld_ht)
(p0, d0), z_enp = eval_real_image_ht(opm, fld, cwl)
ang_x = np.rad2deg(math.atan2(d0[0], d0[2]))
ang_y = np.rad2deg(math.atan2(d0[1], d0[2]))
print(f"{i:2d} {fld_ht:6.3f}: {ang_y:7.2f} {z_enp:8.4f}")
flds.append(fld_ht)
obj_angs.append(ang_y)
z_enps.append(z_enp)
tck = interpolate.splrep(obj_angs, z_enps)
polyline = np.linspace(obj_angs[0], obj_angs[-1], 50)
z_enps_intrp = interpolate.splev(polyline, tck, der=0)
plt.scatter(z_enps, obj_angs)
plt.plot(z_enps_intrp, polyline)
plt.title("Pupil Aberration")
plt.ylabel("object angle")
plt.xlabel("z_enp")
plt.show() 270 deg fisheyeModel from the OpticalBenchHub of the PhotonsToPhotos website.Many of the wide angle models on this website have real image height specifications. lens_url = "https://www.photonstophotos.net/GeneralTopics/Lenses/OpticalBench/OpticalBench.htm#Data/US003524697_Example02.txt"
opm, import_info = open_model(lens_url, info=True)
sm = opm['seq_model']
osp = opm['optical_spec']
fov = osp['fov']
pm = opm['parax_model']
em = opm['ele_model']
pt = opm['part_tree']
ar = opm['analysis_results'] layout_pltwb = plt.figure(FigureClass=InteractiveLayout, opt_model=opm, do_draw_beams=False, do_draw_ray_fans=False, is_dark=False).plot() Evaluate pupil spherical aberrationTo evaluate pupil spherical for real image height specifications, use the eval_real_image_ht to calculate z_enp across the field of view. The field of view of this lens is so wide that the real entrance pupil is outside of the first surface vertex for angles larger than 225 deg total field of view! z_enps = []
flds = []
obj_angs = []
cwl = osp['wvls'].central_wvl
print(" py obj angle z_enp")
for i, fld_ht in enumerate(np.arange(-1., 1.1, .1)):
fld = Field(y=fld_ht)
(p0, d0), z_enp = eval_real_image_ht(opm, fld, cwl)
ang_x = np.rad2deg(math.atan2(d0[0], d0[2]))
ang_y = np.rad2deg(math.atan2(d0[1], d0[2]))
print(f"{i:2d} {fld_ht:6.3f}: {ang_y:7.2f} {z_enp:8.4f}")
flds.append(fld_ht)
obj_angs.append(ang_y)
z_enps.append(z_enp)
tck = interpolate.splrep(obj_angs, z_enps)
polyline = np.linspace(obj_angs[0], obj_angs[-1], 50)
z_enps_intrp = interpolate.splev(polyline, tck, der=0)
plt.scatter(z_enps, obj_angs)
plt.plot(z_enps_intrp, polyline)
plt.title("Pupil Aberration")
plt.ylabel("object angle")
plt.xlabel("z_enp")
plt.show() |
Beta Was this translation helpful? Give feedback.
Quick update: there are several problems affecting this model: