简介
Optiland 是一个开源的python光学模拟软件包,可以用来部分替代Zemax。这个工具包正在快速更新中,目前我看还缺几个关键部分:衍射模拟、渐变折射率曲面。但其他的部分已经不错了,在眼视光领域应该是具有一定的实用性。
这个开源项目的地址是:https://github.com/HarrisonKramer/optiland
vibe coding 方法
现在很多程序可以通过AI来写,你完全可以将上述github地址导入进AI让它来帮你撰写,
比如,在豆包中

可以点击"</>编程"这个按钮,然后就会出现github的章鱼猫图标

然后在弹出的窗口中贴入optiland的地址,点选添加链接,就可以让豆包AI来帮你写光学模拟程序了。

然后你大概可以直接跟豆包AI说:“帮我写一个Gullstrand模型眼”。
古法编程
接下来为了熟悉这个工具包,我们使用古法手工编程。我得先假定你已经会用python建立虚拟环境之类,如果不知道如何做的话,最好学一下比如conda、uv之类的东西,或者能够用某个在线jupyter notebook来弄,不然可能会把你的python环境弄得很混乱。
- 安装:
pip install optiland - 打开你的编辑器,建立一个新文件
- 先导入一些库
from optiland.optic import Optic
import numpy as np
import matplotlib.pyplot as plt
from optiland.materials import AbbeMaterial
from optiland import analysis, optimization, solves
from optiland.mtf import FFTMTF
from optiland.solves.quick_focus import QuickFocusSolve
- 与zemax非常相似,在optiland中,要先对整个模拟的环境进行一些设定,比如光瞳、波长、视场角等。这里我们设定系统的光瞳是随瞳孔自动变化的,叫做
float_by_stop_size
eye = Optic()
eye.set_aperture(aperture_type="float_by_stop_size", value = 4 )
eye.add_wavelength(value = 0.550)
eye.set_field_type(field_type = 'angle')
eye.add_field(y=0)
- 在optiland的sample中,没有仔细讲如何自定义材料的折射率,但在眼视光领域,常用的“材料”是角膜、房水、晶状体、玻璃体等等,并非是常见的玻璃材料,所以自定义的方法还是要介绍一下:
- 与眼视光最相关的可能是AbbeMaterial和IdealMaterial,其中AbbeMaterial是可以定义折射率和阿贝数,IdealMaterial则是直接定义折射率和k(不知道是啥)。
# material
material_aqueous = AbbeMaterial(n=1.336981, abbe=52.658991)
material_cornea = AbbeMaterial(n=1.376981, abbe=56.279936)
material_vitreous = AbbeMaterial(n=1.335982, abbe=53.342173)
- 接下来就是把一个面一个面加入到这个模拟系统之中。在常见的光学软件中,是按光学面来操作的,而不是按“镜头”,通常一个镜头会带有两个面,比如角膜,就要描述角膜的前表面和角膜的后表面,而角膜的后表面其实就是房水的前表面。
- 在描述每一个面的时候,要说明这个界面的参数,比如这个面的曲率半径raidus,这个面的口径aperture,这个面之后介质的折射率(比如角膜后表面的折射率应当是房水的),还有这个面到下一个面的厚度。
- 特别的,如果一个面是瞳孔,需要用
is_stop=Ture来指明。
eye.add_surface(index = 0 , radius = np.inf, thickness =np.inf, comment = 'Object')
eye.add_surface(index = 1, comment = 'Phoropter',
surface_type = 'paraxial',
thickness = 12, f = np.inf
)
eye.add_surface(index = 2 , comment = 'Cornea',
surface_type='standard',
radius = 7.70, thickness = 0.55,
material=material_cornea,
aperture=8)
eye.add_surface(index = 3 , comment = 'Aqueous',
surface_type='standard',
radius = 6.80, thickness = 3.6,
material=material_aqueous,
aperture=8)
eye.add_surface(index = 4, comment = 'Pupil',
surface_type='standard',
radius = np.inf, thickness = 0,
material=material_aqueous,
aperture = pupil_size,
is_stop=True)
eye.add_surface(index = 5 , comment = 'Lens front',
surface_type='standard',
radius = 10.0, thickness = 0.546,
material=AbbeMaterial(n=1.386, abbe=50.23),
aperture=8)
eye.add_surface(index = 6 , comment = 'Lens front core',
surface_type='standard',
radius = 7.91, thickness = 2.419,
material=AbbeMaterial(n=1.406, abbe=50.23),
aperture=8)
eye.add_surface(index = 7 , comment = 'Lens back core',
surface_type='standard',
radius = -5.76, thickness = 0.635,
material=AbbeMaterial(n=1.386, abbe=50.23),
aperture=8)
eye.add_surface(index = 8 , comment = 'Vitreous',
surface_type='standard',
radius = -6.0,
thickness = 17.185,
material=material_vitreous,
aperture=8)
eye.add_surface(index = 9 , comment = 'Retina',
surface_type='standard',
radius = -12.0, thickness =0,
aperture=8)
如果运行eye.info(),会有如下的输出:

执行eye.draw(num_rays=10),则可以绘图:

- Gullstrand是一个正视眼的模型,而非一个近视眼模型,但正视眼在眼视光里没啥作用,所以为了后续简单调整眼轴,我加入了一个“综合验光仪” Phoropter,放入了一个理想的近轴近似面,这样我可以先在综合验光仪上设定一个近视度数,再调整玻璃体腔的厚度,使光线还能聚焦到视网膜上,不就得到了一个比较粗糙的近视眼模型了么。
- 首先,我们设定一个-10D的近视状态。当前还没有更改玻璃体腔厚度,所以现在看起来光线并未聚焦到视网膜上
eye.surface_group.surfaces[1].f = 1000/(-10.0)
eye.info()
eye.draw(num_rays=10)

我们在这一步练习优化
problem = optimization.OptimizationProblem()
problem.add_variable(eye, "thickness", surface_number = 8, min_val=10, max_val=20)
input_data = {
"optic": eye,
"surface_number": -1,
"Hx": 0,
"Hy": 0,
"num_rays":16,
"wavelength": 0.550,
"distribution": "uniform",
}
problem.add_operand(
operand_type = "rms_spot_size",
target=0.0,
weight =10,
input_data=input_data,
)
optimizer = optimization.OptimizerGeneric(problem)
optimizer.optimize()
optimized_thickness = problem.variables[0].value
可以看出玻璃体腔的厚度已经变成了20mm

计算出玻璃体厚度以后,就可以把综合验光仪设定回平光的状态了。可以看到平行光聚焦到视网膜前面了。
eye.surface_group.surfaces[1].f = np.inf
eye.info()
eye.draw(num_rays=10)

- 然后我们可以进行一些分析
spot = analysis.SpotDiagram(eye, num_rings=10, distribution="hexapolar")
spot.view()
fft_mtf = FFTMTF(eye, max_freq=100, wavelength=0.550,num_rays=512)
fft_mtf.view()

