第11章 瞳孔函数:用复数表示波前
在上一章末尾,光程差(OPD)为我们提供了一种新的信息。
点列图告诉我们会聚点的位置。光线扇面图告诉我们实际光线与参考光线的偏离。OPD则告诉我们更细微的信息:对于瞳孔平面上的每个点,该处的波前与理想参考波前相比,超前或滞后了多少。
这已经是一大步了。但OPD本身仍不足以生成图像。
为了计算衍射点扩散函数(PSF),计算机需要知道整个瞳孔的光在像面上如何干涉。这意味着每个瞳孔点需要两种信息:
- 透过的光量。
- 该光线具有的相位。
这两方面正是瞳孔函数所存储的内容。
瞳孔函数是波前误差转化为复数数组的关键步骤。它是几何光学路径误差与傅里叶光学之间的桥梁。
本章在概念上篇幅不长,但在整个处理链条中地位重要:
OPD图
→ 瞳孔振幅
→ 瞳孔相位
→ 复瞳孔函数
→ PSF
下一章将对这个复瞳孔函数进行傅里叶变换,并将其转化为PSF。这里,我们将仔细准备输入数据。
不要跳过这一步。许多错误的PSF和MTF计算都源于瞳孔函数构建时使用了错误的单位、错误的掩膜或错误的符号约定。
1. 我们正在打开的黑箱
衍射计算在书中常以简洁的等式呈现:
PSF与瞳孔函数的傅里叶变换相关。
在通常的夫琅禾费近似下,这个说法是正确的,但它掩盖了一个实际问题:
代码中瞳孔函数究竟是什么?
它不只是孔径形状。
它不只是OPD图。
它不只是相位图。
它是瞳孔平面上一个复值函数:
其中:
- 是复瞳孔函数。
- 是瞳孔振幅。
- 是OPD或波前误差。
- 是波长。
- 是虚数单位。
- 是瞳孔坐标。
这个公式可以概括本章的全部内容。
但仅有这一行是不够的。我们需要知道每个部分的意义、如何采样,以及如何在不自欺欺人的情况下编写代码。
2. 孔径掩膜:光存在的地方
从最简单的部分开始:孔径。
对于一个理想的圆形瞳孔,光通过圆内各点,并在圆外被阻挡。在归一化瞳孔坐标中,我们可以写:
瞳孔内部:
瞳孔外部:
因此,最简单的孔径掩膜是:
这个掩膜还不是完整的瞳孔函数。它只说明了哪些地方允许光线通过。
在代码中,这变成一个布尔数组:
mask = rho <= 1.0
这行代码很重要。没有它,你的FFT将包含来自瞳孔外部的光线。结果可能看起来像个图案,但它不再是你打算建模的光学系统的PSF。
在计算光学中,这是一个有益的习惯:
错误的数组仍可能产生一幅美丽的图像。
计算机并不知道你的瞳孔是否物理上合理。它只执行你给出的数字。
3. 瞳孔振幅:并不总是只有1
对于一个清晰、理想、均匀照明的圆形孔径,振幅通常写为:
这意味着:
- 瞳孔内部的振幅为1;
- 瞳孔外部的振幅为0。
这是最简单的情况,也是正确的起点。
但在实际光学系统中,振幅可能更复杂。它可能包括:
- 渐晕;
- 中心遮挡;
- 望远镜中的蜘蛛支架;
- 透过率变化;
- 变迹;
- 偏振相关效应;
- 镀膜或材料的透过率差异。
例如,带有中心遮挡的望远镜,其掩膜如下:
带有变迹的系统,其孔径内部可能具有平滑的振幅,例如:
在本章中,我们将主要使用简单的圆形瞳孔。但重要的是要清楚地区分:
掩膜:允许光通过的区域
振幅:通过的场振幅大小
强度:振幅的平方
振幅不是强度。如果强度透过率为,那么场振幅通常与以下值成比例:
这个平方根是常见的错误来源。如果你将强度直接放入场振幅数组中,PSF的能量将是错误的。
目前,我们将保持:
amplitude = mask.astype(float)
这足以构建第一个瞳孔函数。
4. OPD变成相位
OPD图告诉我们每个瞳孔点具有多少光程差。
如果某一点的OPD为,那么对应的相位为:
这是关键的转换。
单位规则很简单,但很严格:
W和λ必须使用相同的长度单位。
如果以米为单位,也必须以米为单位。
如果以微米为单位,也必须以微米为单位。
如果以纳米为单位,而以米为单位,代码仍然会运行,但相位将因的因子而出错。不会有警告,只会有一个错误的结果。
让我们把物理意义具体化。
假设波长为:
如果OPD为一个整波长:
则:
该点完成了一个完整的相位周期。
如果OPD为半个波长:
则:
该点的相位差了半个周期。
这就是为什么很小的OPD会产生如此大的影响。几百纳米在机械尺度上很小,但在光学上,它可能是可见光波长中的很大一部分。
瞳孔函数将此相位以复数形式存储:
因此,完整的瞳孔函数变为:
或者,代入相位:
这是最简洁的记忆形式。
5. 为什么这里复数不是装饰品
人们很容易将复数视为数学上的装饰。但在这里,它们不是装饰。它们是我们存储相位的方式。
一个实值的OPD图说明:
这个瞳孔点超前或滞后了这么多光程
一个复瞳孔函数说明:
这个瞳孔点以这个振幅和这个相位作出贡献
当我们稍后计算PSF时,来自不同瞳孔点的光将发生干涉。干涉取决于相位。
瞳孔的两个部分可能具有相同的振幅但相反的相位。它们可能会部分抵消。两个部分也可能相位对齐。它们可能会相互加强。
这就是为什么点列图不能完全替代衍射计算的原因。点列图将光线视为会聚点。瞳孔函数则准备光的波动特性,以便进行相干求和。
这里有一个重要的转变:
几何光线追迹问的是:
这条光线射向哪里?
波动光学问的是:
所有瞳孔贡献如何作为复场叠加在一起?
瞳孔函数正是我们准备第二个问题答案的地方。
6. 最小的瞳孔网格
现在我们用Python编写第一个版本。
我们将使用归一化瞳孔坐标。这意味着瞳孔半径为1,坐标范围大致为:
x 从 -1 到 1
y 从 -1 到 1
这还不是以毫米为单位的物理长度。它是一个归一化坐标系。这对本章来说是可以的,因为我们关注的是转换:
OPD → 相位 → 复瞳孔
这是生成网格的代码:
import numpy as np
import matplotlib.pyplot as plt
def make_pupil_grid(n=256):
"""
创建带有归一化瞳孔坐标的正方形采样网格。
返回
-------
x, y : 二维数组
归一化瞳孔坐标。
rho : 二维数组
径向瞳孔坐标。
theta : 二维数组
角度瞳孔坐标。
mask : 二维布尔数组
在单位圆形瞳孔内为True。
"""
coord = np.linspace(-1.0, 1.0, n)
x, y = np.meshgrid(coord, coord)
rho = np.sqrt(x**2 + y**2)
theta = np.arctan2(y, x)
mask = rho <= 1.0
return x, y, rho, theta, mask
输出数组的形状都是:
(n, n)
正方形网格便于进行FFT,尽管物理瞳孔是圆形的。圆形孔径用掩膜表示。
这是数值光学中最早的小妥协之一:
计算用的数组是正方形的。
光学孔径可能不是。
掩膜告诉数组什么是物理上真实的。
这种区别对下一章非常重要。
7. 一个简单的OPD图
在实际的光学设计程序中,OPD是根据光学系统计算得到的。光线追迹穿过表面,累积光程,然后将波前与参考球面或参考波前进行比较。
为了学习,我们可以从一个合成的OPD图开始。这样我们可以在每个例子中测试瞳孔函数机制,而不需要完整的镜头模型。
我们将创建一个具有两个成分的简单波前:
- 类似离焦的变化。
- 类似像散的变化。
这并非完整的Zernike实现。它只是一种简洁的方式,生成一个可识别的OPD模式。
def synthetic_opd(rho, theta, mask, wavelength):
"""
创建一个以米为单位的合成OPD图。
这是一个教学用波前,而非完整的光学设计结果。
系数是波长的几分之一。
"""
opd = np.zeros_like(rho)
# 以米为单位的系数
defocus = 0.50 * wavelength
astigmatism = 0.25 * wavelength
# 简单的波前项
opd += defocus * (2.0 * rho**2 - 1.0)
opd += astigmatism * rho**2 * np.cos(2.0 * theta)
# 在瞳孔外部,OPD在物理上没有意义。
# 在数值上我们保留为零,但掩膜会阻挡它。
opd[~mask] = 0.0
return opd
重要的不是具体用了什么多项式。重要的是opd数组与瞳孔网格具有相同的形状。
每个采样的瞳孔点现在都有一个波前误差。
网格点 → OPD值 → 相位值 → 复瞳孔值
这就是我们想要明确表达的转换。
8. 构建复瞳孔函数
现在我们可以编写完成本章主要任务的函数。
def pupil_function_from_opd(opd, mask, wavelength, amplitude=None):
"""
将OPD图转换为复瞳孔函数。
参数
----------
opd : 二维数组
光程差,以米为单位。
mask : 二维布尔数组
在孔径内为True。
wavelength : 浮点数
以米为单位的波长。
amplitude : 二维数组或None
场振幅透过率。如果为None,则使用清晰孔径。
返回
-------
pupil : 二维复数组
复瞳孔函数。
phase : 二维数组
以弧度为单位的相位。
"""
if amplitude is None:
amplitude = mask.astype(float)
phase = 2.0 * np.pi * opd / wavelength
pupil = amplitude * np.exp(1j * phase)
# 确保孔径外部的场为零。
pupil[~mask] = 0.0 + 0.0j
return pupil, phase
这个函数很小,但它执行了一个非常重要的转换。
让我们运行它:
wavelength = 550e-9 # 550 nm,以米为单位
x, y, rho, theta, mask = make_pupil_grid(n=256)
opd = synthetic_opd(rho, theta, mask, wavelength)
pupil, phase = pupil_function_from_opd(
opd=opd,
mask=mask,
wavelength=wavelength
)
此时:
opd是以米为单位的实值数组。phase是以弧度为单位的实值数组。pupil是复值数组。
你可以检查:
print(opd.dtype)
print(phase.dtype)
print(pupil.dtype)
第三个应当是复数类型。
将这一点作为健全性检查。如果你的瞳孔函数不是复数,那么你还没有以能够发生干涉的形式存储相位。
一个非常常见的Python 3笔误看起来像这样:
print phase.dtype
这行在Python 3中无效。应该是:
print(phase.dtype)
这是一个小的提醒:在计算光学中,许多错误并不是重大的理论错误。有些仅仅是语法、形状、单位或约定上的错误。要尽早发现它们。
9. 可视化OPD、相位和振幅
在进行PSF计算之前,一定要先查看一下瞳孔数据。
一个好规则是:
永远不要对你没有检查过的瞳孔函数进行傅里叶变换。
我们可以绘制三样东西:
- 以波长为单位的OPD。
- 包裹相位。
- 振幅掩膜。
以波长为单位的OPD是:
这通常比米更容易理解。
def show_pupil_maps(opd, phase, pupil, mask, wavelength):
opd_waves = opd / wavelength
wrapped_phase = np.angle(pupil)
opd_plot = np.where(mask, opd_waves, np.nan)
phase_plot = np.where(mask, wrapped_phase, np.nan)
amp_plot = np.abs(pupil)
plt.figure(figsize=(5, 4))
plt.imshow(opd_plot, origin="lower", extent=[-1, 1, -1, 1])
plt.colorbar(label="OPD [waves]")
plt.title("OPD map")
plt.xlabel("Normalized pupil x")
plt.ylabel("Normalized pupil y")
plt.tight_layout()
plt.show()
plt.figure(figsize=(5, 4))
plt.imshow(phase_plot, origin="lower", extent=[-1, 1, -1, 1])
plt.colorbar(label="Wrapped phase [rad]")
plt.title("Pupil phase")
plt.xlabel("Normalized pupil x")
plt.ylabel("Normalized pupil y")
plt.tight_layout()
plt.show()
plt.figure(figsize=(5, 4))
plt.imshow(amp_plot, origin="lower", extent=[-1, 1, -1, 1])
plt.colorbar(label="Field amplitude")
plt.title("Pupil amplitude")
plt.xlabel("Normalized pupil x")
plt.ylabel("Normalized pupil y")
plt.tight_layout()
plt.show()
show_pupil_maps(opd, phase, pupil, mask, wavelength)
有几点需要注意。
OPD图可能看起来平滑而连续。
相位图可能会出现突然的跳跃。这并不一定意味着波前不连续。这可能只是相位在和之间发生了包裹。
这是一个常见的混淆来源。
复指数并不关心相位写为:
还是:
因为它们在单位圆上是同一点。
所以当你用np.angle绘制相位时,通常看到的是包裹相位。包裹相位很有用,但它可能让平滑的波前看起来像是含有尖锐的跳跃。
OPD图通常更适合理解物理波前。包裹相位图更适合检查即将进入FFT的复场。
10. 相位包裹并不自动意味着错误
让我们暂停一下,因为这种错误非常普遍。
假设OPD在整个瞳孔上从0逐渐增加到一个波长。相位从:
增加到:
但np.angle通常将复相位显示在如下范围:
因此,当相位经过时,绘图值会跳转到。
这个跳跃看起来很剧烈,但它可能只是一个显示约定。
复数本身在单位圆上是连续的:
以及:
所以显示的跳跃边缘可能几乎代表相同的复数值。
这就是为什么你应该同时检查:
以波长为单位的OPD
包裹相位
OPD告诉你物理波前误差。
包裹相位告诉你复瞳孔中存储的相位。
它们回答的是相关但不同的问题。
11. 无像差的清晰圆形孔径
在引入有像差的波前之前,先构建理想情况会有所帮助。
如果瞳孔内各处OPD为零:
那么:
对于一个清晰的圆形孔径:
因此瞳孔函数只是一个复数值为的平坦圆盘。
zero_opd = np.zeros_like(rho)
perfect_pupil, perfect_phase = pupil_function_from_opd(
opd=zero_opd,
mask=mask,
wavelength=wavelength
)
print(np.max(np.abs(perfect_pupil)))
print(np.min(np.abs(perfect_pupil[mask])))
在瞳孔内部,振幅应为1。
在瞳孔外部,场应为0。
这个完美的瞳孔将在下一章中产生熟悉的衍射受限艾里斑图样。
在这一步,我们还没有计算艾里斑。我们只是在准备产生它的场。
整个过程是这样的:
平坦的圆形瞳孔
→ 傅里叶变换
→ 类似艾里斑的PSF
像差会改变瞳孔相位。它们不一定会改变瞳孔振幅。
因此,一个有像差的瞳孔可能在孔径内部振幅仍为1,但其相位不再是平坦的。
核心思想是:
一个有像差的清晰系统可以具有均匀的振幅但非均匀的相位。
12. 添加中心遮挡
现在让我们只修改振幅。
这个例子很有用,因为它分离了两种效应:
- 孔径形状影响振幅;
- OPD影响相位。
中心遮挡可以通过阻挡瞳孔中间部分来表示。
def annular_amplitude(rho, inner_radius=0.3):
"""
环形瞳孔的场振幅。
"""
return ((rho >= inner_radius) & (rho <= 1.0)).astype(float)
amplitude_annular = annular_amplitude(rho, inner_radius=0.3)
annular_pupil, annular_phase = pupil_function_from_opd(
opd=zero_opd,
mask=mask,
wavelength=wavelength,
amplitude=amplitude_annular
)
这里OPD仍为零。相位仍为平坦。但瞳孔振幅改变了。
这意味着即使没有像差,PSF也会发生变化。
这是一个重要的教训:
并非所有的PSF变化都来自波前误差。
有些PSF变化来自孔径形状和振幅透过率。
如果你为一个望远镜建模,却忘记了中心遮挡,即使你的OPD是完美的,衍射图样也会是错误的。
如果你错误地模拟了渐晕,PSF和MTF可能会因为瞳孔振幅的改变而改变。
再次强调,漂亮的输出并不能证明输入是正确的。
13. Optiland在这一步的贡献
我们的最小代码使用了一个合成的OPD图。这对学习很有用,但它并不是真实镜头分析通常开始的方式。
在一个真实的光学系统中,OPD应该来自光学处方:
表面
→ 材料
→ 波长
→ 视场
→ 瞳孔采样
→ 光线追迹
→ 参考波前
→ OPD图
这就是像Optiland这样的工具变得有用的地方。教学的模式应该是:
使用Optiland或其他光学设计库从真实系统计算OPD。
然后使用相同的瞳孔函数公式来理解接下来会发生什么。
具体的API可能随版本而变化,所以在书籍项目中,最安全的方法是在配套代码中锁定库的版本。从概念上讲,工作流程如下所示:
# 版本检查工作流程。
# 在使用具体的类和方法的名称之前,
# 检查已安装的Optiland版本和文档。
# 1. 构建或加载一个光学系统。
# optic = ...
# 2. 选择视场和波长。
# field = ...
# wavelength = 550e-9
# 3. 运行波前 / OPD分析。
# wavefront = ...
# opd = wavefront.opd_map(...)
# 4. 获取或定义瞳孔掩膜。
# mask = wavefront.pupil_mask(...)
# 5. 将OPD转换为复瞳孔函数。
# pupil, phase = pupil_function_from_opd(opd, mask, wavelength)
这并非要取代Optiland内部的PSF工具。相反,它解释了背后的桥梁。
如果Optiland给你一个OPD图,你现在知道接下来的数学步骤是什么:
而如果Optiland直接给你一个PSF,现在你也知道了该PSF计算所依赖的隐藏输入之一。
这就是本书双轨方法的意义所在。
库执行完整的工程工作流程。最小代码让你看到计算的骨架。
14. 符号约定:不统一意见的静默源头
有一个细节我们应该小心处理:指数中的符号。
你可能会看到瞳孔函数被写成:
或者:
这两种约定都出现在光学和信号处理领域,通常取决于傅里叶变换的约定和OPD的定义。
这很烦人,但可以处理。
规则不是“某一符号总是正确的”。规则是:
在OPD、瞳孔函数、傅里叶变换和坐标定义中使用一致的符号约定。
仅对于强度PSF,改变一个实值相位函数的符号通常会产生一个相关或镜像的结果。但当你关心相位敏感的量、非对称像差、相干成像,或与某个软件包进行比较时,约定就会很重要。
对于本书的最小代码,我们将使用:
如果你的软件包使用相反的约定,不要慌张。检查其文档,然后在你的比较代码中调整符号。
一个实用的比较检查清单是:
- 使用相同的波长。
- 使用相同的瞳孔坐标方向。
- 使用相同的OPD参考。
- 使用相同的相位符号约定。
- 在转到PSF时使用相同的FFT约定。
当两个程序不一致时,首先怀疑的不应是“物理错了”。首先怀疑的应是单位、符号、采样和归一化。
15. 瞳孔坐标:物理的还是归一化的?
在本章中我们使用了归一化坐标:
圆形瞳孔定义为:
这对教学很方便。
在一个真实的光学系统中,瞳孔坐标也可以用物理单位(如毫米)表示。瞳孔半径可能是:
那么物理瞳孔坐标可以归一化为:
OPD本身并不会自动归一化。OPD仍然是一个长度。
所以你可能会遇到:
x, y: 归一化瞳孔坐标
W: 米
λ: 米
phase: 弧度
这种组合完全没问题。
坐标系描述的是你在瞳孔中的位置。OPD和波长描述的是该处存在多少相位延迟。
将这些角色分开:
瞳孔坐标:位置
OPD:光程差
波长:相位转换的尺度
相位:以弧度为单位的角度
当这种区分清晰了,代码的调试会变得容易得多。
16. 瞳孔函数不是PSF
这一点值得直截了当地说明。
瞳孔函数不是点光源的图像。
瞳孔函数存在于瞳孔平面。它描述的是孔径上的复场。
PSF存在于像平面。它描述的是衍射和像差之后的强度分布。
下一章将通过傅里叶变换将它们连接起来,但它们不是同一个对象。
一个粗略的图示是:
瞳孔平面:
孔径上的复场
像平面:
像点周围的强度分布
这很重要,因为一个相位图可能看起来很严重,而得到的PSF可能仍然很紧凑;或者一个看似很小的瞳孔遮挡就可能产生可见的衍射环。
你不能总是从一张瞳孔图直接读出最终的图像质量。瞳孔函数是输入。PSF是经过相干传播后的输出。
这就是为什么我们正在小心地构建整个链条,而不是直接跳到最终的图。
17. 一个完整的最小脚本
下面是本章整个最小实现的紧凑版本。
它创建:
- 一个瞳孔网格;
- 一个圆形掩膜;
- 一个合成的OPD图;
- 一个复瞳孔函数;
- OPD、相位和振幅的图。
import numpy as np
import matplotlib.pyplot as plt
def make_pupil_grid(n=256):
coord = np.linspace(-1.0, 1.0, n)
x, y = np.meshgrid(coord, coord)
rho = np.sqrt(x**2 + y**2)
theta = np.arctan2(y, x)
mask = rho <= 1.0
return x, y, rho, theta, mask
def synthetic_opd(rho, theta, mask, wavelength):
opd = np.zeros_like(rho)
defocus = 0.50 * wavelength
astigmatism = 0.25 * wavelength
opd += defocus * (2.0 * rho**2 - 1.0)
opd += astigmatism * rho**2 * np.cos(2.0 * theta)
opd[~mask] = 0.0
return opd
def pupil_function_from_opd(opd, mask, wavelength, amplitude=None):
if amplitude is None:
amplitude = mask.astype(float)
phase = 2.0 * np.pi * opd / wavelength
pupil = amplitude * np.exp(1j * phase)
pupil[~mask] = 0.0 + 0.0j
return pupil, phase
def plot_map(data, title, colorbar_label):
plt.figure(figsize=(5, 4))
plt.imshow(data, origin="lower", extent=[-1, 1, -1, 1])
plt.colorbar(label=colorbar_label)
plt.title(title)
plt.xlabel("Normalized pupil x")
plt.ylabel("Normalized pupil y")
plt.tight_layout()
plt.show()
wavelength = 550e-9
x, y, rho, theta, mask = make_pupil_grid(n=256)
opd = synthetic_opd(rho, theta, mask, wavelength)
pupil, phase = pupil_function_from_opd(
opd=opd,
mask=mask,
wavelength=wavelength
)
opd_waves = np.where(mask, opd / wavelength, np.nan)
wrapped_phase = np.where(mask, np.angle(pupil), np.nan)
amplitude = np.abs(pupil)
plot_map(opd_waves, "OPD map", "OPD [waves]")
plot_map(wrapped_phase, "Wrapped pupil phase", "Phase [rad]")
plot_map(amplitude, "Pupil amplitude", "Field amplitude")
这个脚本此时还没有计算PSF。这是故意的。
在这一点上,正确的问题不是:
图像在哪里?
正确的问题是:
我们是否正确地构建了将形成图像的复场?
一旦答案是肯定的,下一章的傅里叶变换就说得通了。
18. 常见错误
让我们在继续之前总结最常见的错误。
错误1:单位混用
这是最危险的一个。
错误:
opd = 200 # 纳米,但未注明
wavelength = 550e-9 # 米
phase = 2 * np.pi * opd / wavelength
正确:
opd = 200e-9
wavelength = 550e-9
phase = 2 * np.pi * opd / wavelength
始终确保OPD和波长使用相同的单位。
错误2:忘记孔径掩膜
如果你计算:
pupil = np.exp(1j * phase)
而没有应用掩膜,你就允许光通过整个正方形数组。
正确:
pupil = mask.astype(float) * np.exp(1j * phase)
pupil[~mask] = 0.0
FFT并不知道你的孔径应该是圆形的,除非你告诉它。
错误3:混淆振幅和强度
如果一个滤光片透过25%的强度,场振幅因子不是0.25。而是:
错误:
amplitude = intensity_transmission
通常正确:
amplitude = np.sqrt(intensity_transmission)
这很重要,因为PSF强度是根据复场幅值的平方计算的。
错误4:认为包裹相位中的跳跃总是物理上的跳跃
np.angle(pupil)中的突然跳跃可能只是相位包裹。
在断定波前不连续之前,先检查以波长为单位的OPD。
错误5:未检查约定就与软件比较
如果你的PSF与某个软件包不同,请检查:
- 波长;
- 瞳孔采样;
- OPD参考;
- 相位符号;
- FFT移位;
- 归一化;
- 坐标方向。
在做改变物理模型的决定之前,先做这些检查。
错误6:将瞳孔函数当作最终结果
瞳孔函数是一个中间表示。它极其重要,但它不是PSF,也不是MTF。
它的作用是将振幅和相位信息带入衍射计算中。
19. 本章为完整链条添加了什么
我们现在可以更新本书的主要计算链条了:
镜头处方
→ 表面和材料
→ 光线追迹
→ 光程
→ OPD图
→ 瞳孔振幅
→ 瞳孔相位
→ 复瞳孔函数
本章执行了一次精确的转换:
以长度单位表示的OPD
→ 以弧度表示的相位
→ 孔径上的复场
这个转换小到几行代码就能容纳。但从概念上讲,它是本书中最重要的步骤之一。
没有它,OPD仅仅是一个波前误差图。
有了它,波前变成了能够干涉、衍射并形成图像的东西。
这就是为什么瞳孔函数是进入PSF计算的大门。
在下一章中,我们将把这个复瞳孔函数送入傅里叶变换。结果将不再存在于瞳孔平面。它将成为一个点图像的强度分布:PSF。
而从那个PSF开始,我们最终将接近开启这段旅程的MTF曲线。
章节总结
瞳孔函数存储了孔径上的复场。
它结合了:
振幅
相位
孔径掩膜
核心公式是:
其中OPD 必须与波长使用相同的长度单位。
孔径掩膜告诉代码光在哪里。振幅告诉我们有多少场穿透。相位告诉我们OPD如何改变复场。
一个完美的清晰圆形瞳孔具有平坦的相位和均匀的振幅。一个有像差的瞳孔可能具有均匀的振幅但非均匀的相位。一个被遮挡或变迹的瞳孔即使OPD为零,也可能具有非均匀的振幅。
本章中的主要代码动作是:
phase = 2.0 * np.pi * opd / wavelength
pupil = amplitude * np.exp(1j * phase)
pupil[~mask] = 0.0
这就是从OPD到傅里叶光学的桥梁。
接下来,我们将使用这个瞳孔函数来计算PSF。
生成验证图
