From U.S. Patent 7,535,658
%matplotlib inline
#%matplotlib widget
isdark = False
# use standard rayoptics environment
from rayoptics.environment import *
# util functions
from rayoptics.util.misc_math import normalize
opm = OpticalModel()
sm = opm['seq_model']
osp = opm['optical_spec']
pm = opm['parax_model']
em = opm['ele_model']
pt = opm['part_tree']
opm.system_spec.title = 'Cell Phone Lens - U.S. Patent 7,535,658'
opm.system_spec.dimensions = 'mm'
osp['pupil'] = PupilSpec(osp, key=['image', 'f/#'], value=3.5)
osp['fov'] = FieldSpec(osp, key=['image', 'height'], value=3.5, is_relative=True, flds=[0., .7071, 1])
osp['wvls'] = WvlSpec([('F', 0.5), ('d', 1.0), ('C', 0.5)], ref_wl=1)
The ~.seq.sequential.SequentialModel.add_surface
method is used to enter a sequential model in the form it's usually given:
- curvature/radius, thickness, glass/refractive index, clear aperture
Each ~.elem.surface.Surface
has a profile attribute that is initialized to ~.elem.profiles.Spherical
.
The ~.elem.profiles
module has a variety of non-spherical profiles. Create an instance of the desired profile type and assign it to the profile attribute of the current interface.
opm.radius_mode = True
sm.gaps[0].thi=1e10
sm.add_surface([0., 0.])
sm.set_stop()
sm.add_surface([1.962, 1.19, 1.471, 76.6])
sm.ifcs[sm.cur_surface].profile = RadialPolynomial(r=1.962, ec=2.153,
coefs=[0., 0., -1.895e-2, 2.426e-2, -5.123e-2, 8.371e-4, 7.850e-3, 4.091e-3, -7.732e-3, -4.265e-3])
sm.add_surface([33.398, .93])
sm.ifcs[sm.cur_surface].profile = RadialPolynomial(r=33.398, ec=40.18,
coefs=[0., 0., -4.966e-3, -1.434e-2, -6.139e-3, -9.284e-5, 6.438e-3, -5.72e-3, -2.385e-2, 1.108e-2])
sm.add_surface([-2.182, .75, 1.603, 27.5])
sm.ifcs[sm.cur_surface].profile = RadialPolynomial(r=-2.182, ec=2.105,
coefs=[0., 0., -4.388e-2, -2.555e-2, 5.16e-2, -4.307e-2, -2.831e-2, 3.162e-2, 4.630e-2, -4.877e-2])
sm.add_surface([-6.367, 0.1])
sm.ifcs[sm.cur_surface].profile = RadialPolynomial(r=-6.367, ec=3.382,
coefs=[0., 0., -1.131e-1, -7.863e-2, 1.094e-1, 6.228e-3, -2.216e-2, -5.89e-3, 4.123e-3, 1.041e-3])
sm.add_surface([5.694, .89, 1.510, 56.2])
sm.ifcs[sm.cur_surface].profile = RadialPolynomial(r=5.694, ec=-221.1,
coefs=[0., 0., -7.876e-2, 7.02e-2, 1.575e-3, -9.958e-3, -7.322e-3, 6.914e-4, 2.54e-3, -7.65e-4])
sm.add_surface([9.192, .16])
sm.ifcs[sm.cur_surface].profile = RadialPolynomial(r=9.192, ec=0.9331,
coefs=[0., 0., 9.694e-3, -2.516e-3, -3.606e-3, -2.497e-4, -6.84e-4, -1.414e-4, 2.932e-4, -7.284e-5])
sm.add_surface([1.674, .85, 1.510, 56.2])
sm.ifcs[sm.cur_surface].profile = RadialPolynomial(r=1.674, ec=-7.617,
coefs=[0., 0., 7.429e-2, -6.933e-2, -5.811e-3, 2.396e-3, 2.100e-3, -3.119e-4, -5.552e-5, 7.969e-6])
sm.add_surface([1.509, .70])
sm.ifcs[sm.cur_surface].profile = RadialPolynomial(r=1.509, ec=-2.707,
coefs=[0., 0., 1.767e-3, -4.652e-2, 1.625e-2, -3.522e-3, -7.106e-4, 3.825e-4, 6.271e-5, -2.631e-5])
sm.add_surface([0., .40, 1.516, 64.1])
sm.add_surface([0., .64])
opm.update_model()
Turn off automatically resizing apertures based on sequential model ray trace.
sm.do_apertures = False
sm.list_model()
r t medium mode zdr sd
Obj: 0.000000 1.00000e+10 air 1 6.3006e+09
- Stop: 0.000000 0.00000 air 1 0.79358
2: 1.962000 1.19000 471.766 1 0.93800 3: 33.398000 0.930000 air 1 1.0837 4: -2.182000 0.750000 603.275 1 1.1338 5: -6.367000 0.100000 air 1 1.5390 6: 5.694000 0.890000 510.562 1 1.8254 7: 9.192000 0.160000 air 1 2.3978 8: 1.674000 0.850000 510.562 1 2.4820 9: 1.509000 0.700000 air 1 2.9297
- 10: 0.000000 0.400000 516.641 1 3.3067
11: 0.000000 0.640000 air 1 3.4058
Img: 0.000000 0.00000 1 3.6910
pm.first_order_data()
efl 5.555 ffl -7.531 pp1 -1.976 bfl 0.5678 ppk 4.987 f/# 3.5 m -5.555e-10 red -1.8e+09 obj_dist 1e+10 obj_ang 32.21 enp_dist -0 enp_radius 0.7936 na obj 7.936e-11 n obj 1 img_dist 0.5678 img_ht 3.5 exp_dist -3.602 exp_radius 0.5854 na img -0.1414 n img 1 optical invariant 0.5
pt.list_model()
root ├── Object ├── Stop ├── E1 ├── E2 ├── E3 ├── E4 ├── E5 └── Image
layout_plt0 = plt.figure(FigureClass=InteractiveLayout, opt_model=opm,
do_draw_rays=True, do_paraxial_layout=False,
is_dark=isdark).plot()
Note that in the lens layout above, the very aspheric surface shapes lead to extreme lens element shapes. The default logic used by ray-optics to apply flat bevels to concave surfaces is defeated by the aspherics that switch concavity between vertex and edge. How ray-optics renders flats can be controlled on a surface by surface basis.
First, generate a list of lens elements from the part tree.
elmn = [node.id for node in pt.nodes_with_tag(tag='#element')]
Lens elements have two surfaces, each of which can be specified with or without a flat.
elmn[1].do_flat1 = 'always'
elmn[1].do_flat2 = 'always'
elmn[2].do_flat1 = 'always'
elmn[2].do_flat2 = 'always'
elmn[3].do_flat1 = 'always'
elmn[3].do_flat2 = 'always'
layout_plt1 = plt.figure(FigureClass=InteractiveLayout, opt_model=opm,
do_draw_rays=True, do_paraxial_layout=False,
is_dark=isdark).plot()
By default, the inside diameters of a flat are set to the clear aperture of the interface in the sequential model. This can be overriden for each surface. The semi-diameter ~.elem.elements.Element.sd
of the lens element may also be set explicitly.
elmn[0].sd = 1.25
elmn[1].sd = 1.75
elmn[1].flat1 = 1.25
elmn[1].flat2 = 1.645
elmn[2].sd = 2.5
elmn[2].flat1 = 2.1
elmn[3].sd = 3.0
elmn[3].flat1 = 2.6
elmn[4].sd = 3.5
layout_plt = plt.figure(FigureClass=InteractiveLayout, opt_model=opm,
do_draw_rays=True, do_paraxial_layout=False,
is_dark=isdark).plot()
spot_plt = plt.figure(FigureClass=SpotDiagramFigure, opt_model=opm,
scale_type=Fit.All_Same, dpi=200, is_dark=isdark).plot()
opm.save_model("cell_phone_camera")
pt0 = np.array([0., 1., 0.])
dir0 = np.array([0., 0., 1.])
wvl = sm.central_wavelength()
marg_ray = rt.trace(sm, pt0, dir0, wvl)
list_ray(marg_ray[0])
X Y Z L M N Len
0: 0.00000 1.00000 0 0.000000 0.000000 1.000000 1e+10 1: 0.00000 1.00000 0 0.000000 0.000000 1.000000 0.26119 2: 0.00000 1.00000 0.26119 0.000000 -0.163284 0.986579 0.93632 3: 0.00000 0.84711 -0.0050525 0.000000 -0.272278 0.962219 0.86687 4: 0.00000 0.61108 -0.10094 0.000000 -0.024063 0.999710 0.79796 5: 0.00000 0.59188 -0.053212 0.000000 -0.171810 0.985130 0.16841 6: 0.00000 0.56295 0.012694 0.000000 -0.122925 0.992416 0.89598 7: 0.00000 0.45281 0.01188 0.000000 -0.158261 0.987397 0.2017 8: 0.00000 0.42089 0.051033 0.000000 -0.178956 0.983857 0.83614 9: 0.00000 0.27126 0.023675 0.000000 -0.185004 0.982738 0.6882
10: 0.00000 0.14394 0 0.000000 -0.122034 0.992526 0.40301 11: 0.00000 0.09476 0 0.000000 -0.185004 0.982738 0.65124 12: 0.00000 -0.02573 0 0.000000 -0.185004 0.982738 0
Given a point and direction at the first (not object) interface
dir0 = normalize(np.array([0.086, 0.173, 0.981]))
pt1 = np.array(-dir0)
sm.gaps[1].thi = dir0[2]
pt1[2] = 0.
dir0, [0.086, 0.173, 0.981], pt1
- (array([0.08601351, 0.17302717, 0.98115405]),
[0.086, 0.173, 0.981], array([-0.08601351, -0.17302717, 0. ]))
Use the low level ~.raytr.raytrace.trace_raw
function to trace the ray.
wvl = sm.central_wavelength()
path = sm.path(wl=wvl, start=1)
skew_ray = rt.trace_raw(path, pt1, dir0, wvl)
list_ray(skew_ray[0])
X Y Z L M N Len
0: -0.08601 -0.17303 0 0.086014 0.173027 0.981154 0.009449 1: -0.08520 -0.17139 0.009271 0.072254 0.145349 0.986739 1.1966 2: 0.00126 0.00253 1.1955e-07 0.106304 0.213844 0.971066 0.94474 3: 0.10169 0.20456 -0.012595 0.085295 0.171581 0.981471 0.75899 4: 0.16643 0.33479 -0.017664 0.106581 0.214401 0.970913 0.12979 5: 0.18026 0.36261 0.0083478 0.066253 0.133277 0.988862 0.90879 6: 0.24047 0.48374 0.017019 0.115071 0.231480 0.966010 0.24881 7: 0.26910 0.54133 0.097372 0.032613 0.065605 0.997313 0.88059 8: 0.29782 0.59910 0.1256 0.126731 0.254936 0.958617 0.5992 9: 0.37376 0.75186 0 0.083596 0.168164 0.982208 0.40725
10: 0.40780 0.82034 0 0.126731 0.254936 0.958617 0.66763 11: 0.49241 0.99054 0 0.126731 0.254936 0.958617 0
(field point index = 1)
fld, wvl, foc = osp.lookup_fld_wvl_focus(1)
Use the ~.raytr.trace.trace_base
function to trace a ray in terms of pupil position, field point and wavelength.
ray_f1_r0 = trace_base(opm, [0., 0.], fld, wvl)
list_ray(ray_f1_r0[0])
X Y Z L M N Len
0: 0.00000 -4455119074.82455 0 0.000000 0.406953 0.913449 1.0948e+10 1: 0.00000 0.00000 0 0.000000 0.406953 0.913449 3.0134e-15 2: 0.00000 0.00000 2.6771e-15 0.000000 0.276650 0.960971 1.2397 3: 0.00000 0.34297 0.001336 0.000000 0.409866 0.912146 0.86869 4: 0.00000 0.69902 -0.13629 0.000000 0.407402 0.913249 0.76898 5: 0.00000 1.01230 -0.18402 0.000000 0.432712 0.901532 0.34554 6: 0.00000 1.16182 0.027492 0.000000 0.283196 0.959062 1.0047 7: 0.00000 1.44636 0.10111 0.000000 0.468716 0.883349 0.36927 8: 0.00000 1.61944 0.2673 0.000000 0.352162 0.935939 1.0087 9: 0.00000 1.97467 0.3614 0.000000 0.436135 0.899881 0.37628
10: 0.00000 2.13878 0 0.000000 0.287688 0.957724 0.41766 11: 0.00000 2.25894 0 0.000000 0.436135 0.899881 0.71121 12: 0.00000 2.56912 0 0.000000 0.436135 0.899881 0
ray_f1_py = trace_base(opm, [0., 1.], fld, wvl)
list_ray(ray_f1_py[0])
X Y Z L M N Len
0: 0.00000 -4455119074.82455 0 0.000000 0.406953 0.913449 1.0948e+10 1: 0.00000 0.79358 0 0.000000 0.406953 0.913449 0.22235 2: 0.00000 0.88407 0.2031 0.000000 0.101114 0.994875 0.97236 3: 0.00000 0.98239 -0.019515 0.000000 0.074330 0.997234 0.60425 4: 0.00000 1.02730 -0.34694 0.000000 0.342927 0.939362 0.8381 5: 0.00000 1.31471 -0.30965 0.000000 0.320198 0.947351 0.45073 6: 0.00000 1.45903 0.017345 0.000000 0.246733 0.969084 1.0139 7: 0.00000 1.70918 0.10987 0.000000 0.362970 0.931801 0.29718 8: 0.00000 1.81705 0.22678 0.000000 0.335417 0.942070 0.99575 9: 0.00000 2.15104 0.31485 0.000000 0.340775 0.940145 0.40967
10: 0.00000 2.29065 0 0.000000 0.224786 0.974408 0.41051 11: 0.00000 2.38292 0 0.000000 0.340775 0.940145 0.68075 12: 0.00000 2.61491 0 0.000000 0.340775 0.940145 0
ray_f1_my = trace_base(opm, [0., -1.], fld, wvl)
list_ray(ray_f1_my[0])
X Y Z L M N Len
0: 0.00000 -4455119074.82455 0 0.000000 0.406953 0.913449 1.0948e+10 1: 0.00000 -0.79358 0 0.000000 0.406953 0.913449 0.15124 2: 0.00000 -0.73203 0.13815 0.000000 0.391742 0.920075 1.1443 3: 0.00000 -0.28377 0.00098906 0.000000 0.573151 0.819450 1.0975 4: 0.00000 0.34526 -0.029681 0.000000 0.428272 0.903650 0.78087 5: 0.00000 0.67968 -0.074048 0.000000 0.531021 0.847359 0.22793 6: 0.00000 0.80071 0.019088 0.000000 0.341292 0.939957 1.0037 7: 0.00000 1.14325 0.072483 0.000000 0.579551 0.814936 0.44361 8: 0.00000 1.40035 0.27399 0.000000 0.363252 0.931691 1.0285 9: 0.00000 1.77395 0.38224 0.000000 0.534421 0.845218 0.37595
10: 0.00000 1.97487 1.1102e-16 0.000000 0.352521 0.935804 0.42744 11: 0.00000 2.12555 0 0.000000 0.534421 0.845218 0.7572 12: 0.00000 2.53021 0 0.000000 0.534421 0.845218 0