Cell Phone Camera Lens¶
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
Create a new, empty, model¶
opm = OpticalModel()
sm = opm['seq_model']
osp = opm['optical_spec']
pm = opm['parax_model']
em = opm['ele_model']
pt = opm['part_tree']
Specify aperture, field, and wavelengths¶
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)
Define interface and gap data for the sequential model¶
The 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 Surface
has a profile attribute that is initialized to Spherical
.
The 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])
Update the model¶
opm.update_model()
Turn off automatically resizing apertures based on sequential model ray trace.
sm.do_apertures = False
List the sequential model and the first order properties¶
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()

Set semi-diameters and flats for manufacturing and mounting¶
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 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
Draw a lens layout to verify the model¶
layout_plt = plt.figure(FigureClass=InteractiveLayout, opt_model=opm,
do_draw_rays=True, do_paraxial_layout=False,
is_dark=isdark).plot()

Plot a Spot Diagram¶
spot_plt = plt.figure(FigureClass=SpotDiagramFigure, opt_model=opm,
scale_type=Fit.All_Same, dpi=200, is_dark=isdark).plot()

Save the model¶
opm.save_model("cell_phone_camera")
Trace axial marginal ray¶
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
Trace an arbitrary skew ray¶
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 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
Set up the ray trace for the second field point¶
(field point index = 1)
fld, wvl, foc = osp.lookup_fld_wvl_focus(1)
Trace central, upper and lower rays¶
Use the 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