在上期文章中,我以不透明挡板上一定尺寸的矩形通孔为例,直观呈现了光线入射时其对光的衍射现象,仿真视频如下所示。

在本期文章中,我将直接进行实现该仿真的源代码介绍。

1. 总述

实现该仿真的代码共分为三部分,分别为mplwidget.py、UiMainApp.py和 rectangular aperture diffraction.py,放置在名为Python and Optics的文件夹中,运行主程序rectangular aperture diffraction.py即可调用mplwidget.py和UiMainApp.py。

python使用卡尔曼滤波检测物体(光学现象的Python实现)(1)

2.“画布”创建:mplwidget.py

我们将绘制大量的图形和图表,所有这些都将使用Python中的Matplotlib模块来绘制。我们需要创建一个“画布”来绘制这些Matplotlib图形。

为此,我们编写一个Python代码,将Matplotlib嵌入到PyQt5 GUI中。编写完成后,我们将此代码命名为mplwidget.py,并将其保存在文件夹Python and Optics中,后续相关代码也需保存在该文件夹中;或者直接将该代码放在Python搜索导入模块路径中的某个位置,以便后续调用。

编写mplwidget.py之初,需调用相关库函数:

from PyQt5.QtWidgets import QSizePolicy , QWidget , QVBoxLayout from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas from matplotlib.backends.backend_qt5 import NavigationToolbar2QT as NavigationToolbar from matplotlib.figure import Figure from matplotlib import rcParams

随后,设计“画布”的布局:

class MplCanvas(FigureCanvas): def __init__(self): # setup Matplotlib Figure and Axis self.fig = Figure() self.ax = self.fig.add_subplot(111) # initialization of the canvas FigureCanvas.__init__(self, self.fig) # we define the widget as expandable FigureCanvas.setSizePolicy(self, QSizePolicy.Expanding, QSizePolicy.Expanding) # notify the system of updated policy FigureCanvas.updateGeometry(self) class MPL_WIDGET(QWidget): def __init__(self, parent=None): # initialization of Qt MainWindow widget QWidget.__init__(self, parent) # set the canvas to the Matplotlib widget self.canvas = MplCanvas() # create a navigation toolbar for our plot canvas self.navi_toolbar = NavigationToolbar(self.canvas,self) # create a vertical box layout self.vbl = QVBoxLayout() # add mpl widget to vertical box self.vbl.addWidget(self.canvas) # add the navigation toolbar to vertical box self.vbl.addWidget(self.navi_toolbar) # set the layout to vertical box self.setLayout(self.vbl)

两部分结合得到的完整代码如下:

from PyQt5.QtWidgets import QSizePolicy , QWidget , QVBoxLayout from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas from matplotlib.backends.backend_qt5 import NavigationToolbar2QT as NavigationToolbar from matplotlib.figure import Figure from matplotlib import rcParams rcParams['font.size'] = 9 class MplCanvas(FigureCanvas): def __init__(self): # setup Matplotlib Figure and Axis self.fig = Figure() self.ax = self.fig.add_subplot(111) # initialization of the canvas FigureCanvas.__init__(self, self.fig) # we define the widget as expandable FigureCanvas.setSizePolicy(self, QSizePolicy.Expanding, QSizePolicy.Expanding) # notify the system of updated policy FigureCanvas.updateGeometry(self) class MPL_WIDGET(QWidget): def __init__(self, parent=None): # initialization of Qt MainWindow widget QWidget.__init__(self, parent) # set the canvas to the Matplotlib widget self.canvas = MplCanvas() # create a navigation toolbar for our plot canvas self.navi_toolbar = NavigationToolbar(self.canvas,self) # create a vertical box layout self.vbl = QVBoxLayout() # add mpl widget to vertical box self.vbl.addWidget(self.canvas) # add the navigation toolbar to vertical box self.vbl.addWidget(self.navi_toolbar) # set the layout to vertical box self.setLayout(self.vbl)

3. 控件设计:UiMainApp.py

作为一个矩孔衍射的例子,我们需要设计五种调节控件来描述衍射现象发生时的参数,具体包括:

(1) 入射光的波长λ,(2) 矩孔的宽度b,(3) 矩孔的高度h,(4) 观察屏的尺寸a,以及 (5) 透镜焦距f2 (后续会说明为什么会有这个量)。

这五个控件的布局如下图所示:

python使用卡尔曼滤波检测物体(光学现象的Python实现)(2)

实现代码包含在UiMainApp.py中,源代码为:

from PyQt5 import QtCore , QtGui , QtWidgets from mplwidget import MPL_WIDGET #calling the mplwidget.py class Ui_MainWindow(object): def setupUi(self, MainWindow): MainWindow.setObjectName("MainWindow") MainWindow.resize(691, 545) self.centralwidget = QtWidgets.QWidget(MainWindow) self.centralwidget.setObjectName("centralwidget") self.horizontalLayout = QtWidgets.QHBoxLayout(self.centralwidget) self.horizontalLayout.setObjectName("horizontalLayout") self.mplwidget = MPL_WIDGET(self.centralwidget) self.mplwidget.setObjectName("mplwidget") self.horizontalLayout.addWidget(self.mplwidget) MainWindow.setCentralWidget(self.centralwidget) self.menubar = QtWidgets.QMenuBar(MainWindow) self.menubar.setGeometry(QtCore.QRect(0, 0, 691, 21)) self.menubar.setObjectName("menubar") MainWindow.setMenuBar(self.menubar) self.statusbar = QtWidgets.QStatusBar(MainWindow) self.statusbar.setObjectName("statusbar") MainWindow.setStatusBar(self.statusbar) self.settings = QtWidgets.QDockWidget(MainWindow) self.settings.setObjectName("settings") self.dockWidgetContents = QtWidgets.QWidget() self.dockWidgetContents.setObjectName("dockWidgetContents") self.verticalLayout = QtWidgets.QVBoxLayout(self.dockWidgetContents) self.verticalLayout.setContentsMargins(0, 0, 0, 0) self.verticalLayout.setObjectName("verticalLayout") self.label_lamda = QtWidgets.QLabel(self.dockWidgetContents) self.label_lamda.setObjectName("label_lamda") self.verticalLayout.addWidget(self.label_lamda) self.SpinBox_lambda = QtWidgets.QdoubleSpinBox(self.dockWidgetContents) self.SpinBox_lambda.setDecimals(0) self.SpinBox_lambda.setMinimum(400.0) self.SpinBox_lambda.setMaximum(900.0) self.SpinBox_lambda.setSingleStep(10.0) self.SpinBox_lambda.setProperty("value", 560.0) self.SpinBox_lambda.setObjectName("SpinBox_lambda") self.verticalLayout.addWidget(self.SpinBox_lambda) self.slider_lambda = QtWidgets.QSlider(self.dockWidgetContents) self.slider_lambda.setMinimum(400) self.slider_lambda.setMaximum(900) self.slider_lambda.setSingleStep(10) self.slider_lambda.setProperty("value", 560) self.slider_lambda.setOrientation(QtCore.Qt.Horizontal) self.slider_lambda.setTickPosition(QtWidgets.QSlider.TicksBelow) self.slider_lambda.setTickInterval(50) self.slider_lambda.setObjectName("slider_lambda") self.verticalLayout.addWidget(self.slider_lambda) self.label_b = QtWidgets.QLabel(self.dockWidgetContents) self.label_b.setObjectName("label_b") self.verticalLayout.addWidget(self.label_b) self.SpinBox_b = QtWidgets.QDoubleSpinBox(self.dockWidgetContents) self.SpinBox_b.setDecimals(1) self.SpinBox_b.setMinimum(0.1) self.SpinBox_b.setSingleStep(1.0) self.SpinBox_b.setProperty("value", 4.0) self.SpinBox_b.setObjectName("SpinBox_b") self.verticalLayout.addWidget(self.SpinBox_b) self.slider_b = QtWidgets.QSlider(self.dockWidgetContents) self.slider_b.setMinimum(1) self.slider_b.setMaximum(100) self.slider_b.setProperty("value", 4) self.slider_b.setOrientation(QtCore.Qt.Horizontal) self.slider_b.setTickPosition(QtWidgets.QSlider.TicksBelow) self.slider_b.setTickInterval(10) self.slider_b.setObjectName("slider_b") self.verticalLayout.addWidget(self.slider_b) self.label_h = QtWidgets.QLabel(self.dockWidgetContents) self.label_h.setObjectName("label_h") self.verticalLayout.addWidget(self.label_h) self.SpinBox_h = QtWidgets.QDoubleSpinBox(self.dockWidgetContents) self.SpinBox_h.setDecimals(1) self.SpinBox_h.setMinimum(0.1) self.SpinBox_h.setProperty("value", 4.0) self.SpinBox_h.setObjectName("SpinBox_h") self.verticalLayout.addWidget(self.SpinBox_h) self.slider_h = QtWidgets.QSlider(self.dockWidgetContents) self.slider_h.setMinimum(1) self.slider_h.setMaximum(100) self.slider_h.setProperty("value", 4) self.slider_h.setOrientation(QtCore.Qt.Horizontal) self.slider_h.setTickPosition(QtWidgets.QSlider.TicksBelow) self.slider_h.setTickInterval(10) self.slider_h.setObjectName("slider_h") self.verticalLayout.addWidget(self.slider_h) self.label_a = QtWidgets.QLabel(self.dockWidgetContents) self.label_a.setObjectName("label_a") self.verticalLayout.addWidget(self.label_a) self.SpinBox_a = QtWidgets.QDoubleSpinBox(self.dockWidgetContents) self.SpinBox_a.setDecimals(1) self.SpinBox_a.setMinimum(0.1) self.SpinBox_a.setSingleStep(1.0) self.SpinBox_a.setProperty("value", 15.0) self.SpinBox_a.setObjectName("SpinBox_a") self.verticalLayout.addWidget(self.SpinBox_a) self.slider_a = QtWidgets.QSlider(self.dockWidgetContents) self.slider_a.setMinimum(1) self.slider_a.setMaximum(100) self.slider_a.setProperty("value", 15) self.slider_a.setOrientation(QtCore.Qt.Horizontal) self.slider_a.setTickPosition(QtWidgets.QSlider.TicksBelow) self.slider_a.setTickInterval(10) self.slider_a.setObjectName("slider_a") self.verticalLayout.addWidget(self.slider_a) self.label_f2 = QtWidgets.QLabel(self.dockWidgetContents) self.label_f2.setObjectName("label_f2") self.verticalLayout.addWidget(self.label_f2) self.SpinBox_f2 = QtWidgets.QDoubleSpinBox(self.dockWidgetContents) self.SpinBox_f2.setDecimals(1) self.SpinBox_f2.setMinimum(0.1) self.SpinBox_f2.setMaximum(50.0) self.SpinBox_f2.setProperty("value", 2.0) self.SpinBox_f2.setObjectName("SpinBox_f2") self.verticalLayout.addWidget(self.SpinBox_f2) self.slider_f2 = QtWidgets.QSlider(self.dockWidgetContents) self.slider_f2.setMinimum(1) self.slider_f2.setMaximum(50) self.slider_f2.setProperty("value", 2) self.slider_f2.setOrientation(QtCore.Qt.Horizontal) self.slider_f2.setTickPosition(QtWidgets.QSlider.TicksBelow) self.slider_f2.setTickInterval(10) self.slider_f2.setObjectName("slider_f2") self.verticalLayout.addWidget(self.slider_f2) self.settings.setWidget(self.dockWidgetContents) MainWindow.addDockWidget(QtCore.Qt.DockWidgetArea(1), self.settings) self.retranslateUi(MainWindow) QtCore.QMetaObject.connectSlotsByName(MainWindow) def retranslateUi(self, MainWindow): _translate = QtCore.QCoreApplication.translate MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow")) self.settings.setWindowTitle(_translate("MainWindow", "Settings")) self.label_lamda.setText(_translate("MainWindow", "<html ><head / > < body > < palign =\"center\">Wave length(10e−9 m)</p> < / body > < / html > ")) self.slider_lambda.setStatusTip(_translate("MainWindow", "Change the wavelength of monochromatic light")) self.label_b.setText(_translate("MainWindow", "<html ><head/ > < body > < palign =\"center\">b(10e−5 m)</p></body ></html> ")) self.SpinBox_b.setStatusTip(_translate("MainWindow", "Change the width of the rectangular aperture")) self.slider_b.setStatusTip(_translate("MainWindow", "Change the width of the rectangular aperture")) self.label_h.setText(_translate("MainWindow", "<html ><head/ > < body > < palign =\"center\">h(10e−5 m)</p></body ></html> ")) self.SpinBox_h.setStatusTip(_translate("MainWindow", "change the height of the recatangular aperture")) self.slider_h.setStatusTip(_translate("MainWindow", "change the height of the recatangular aperture")) self.label_a.setText(_translate("MainWindow", "<html ><head/ > < body > < palign =\"center\">a(10e−2 m)</p></body ></html> ")) self.SpinBox_a.setStatusTip(_translate("MainWindow", "Change the side of the square−shaped screen")) self.slider_a.setStatusTip(_translate("MainWindow", "Change the side of the square−shaped screen")) self.label_f2.setText(_translate("MainWindow", "<html ><head/ > < body > < palign =\"center\">f2(m)</p></body ></html >")) self.SpinBox_f2.setStatusTip(_translate("MainWindow", "Change the focal length f2 of the lens L2")) self.slider_f2.setStatusTip(_translate("MainWindow", "Change the focal length f2 of the lens L2")) if __name__ == "__main__": import sys app = QtWidgets.QApplication(sys.argv) MainWindow = QtWidgets.QMainWindow() ui = Ui_MainWindow() ui.setupUi(MainWindow) MainWindow.show() sys.exit(app.exec_())

4. 衍射公式的编写:rectangular aperture diffraction.py

此处的衍射公式采用的是夫琅禾费近似。

夫琅禾费衍射是一种使用惠更斯-菲涅耳原理,藉以把通过方孔、圆孔或狭缝的波动分成多个向外的波动,使用透镜来有目的地观察衍射光的观测实验。

具体的衍射公式在rectangular aperture diffraction.py中,源代码如下:

from PyQt5.QtWidgets import QApplication , QMainWindow # Import pyqtSlot to connect sliders and DoubleSpinBox signals from PyQt5.QtCore import pyqtSlot # Import Ui_MainWindow class from UiMainApp.py generated by uic module from UiMainApp import Ui_MainWindow # Import functions from numpy library for scientific simulation from numpy import pi, linspace ,meshgrid ,sin # Import matplotlib.cm for the color map in our image of diffraction import matplotlib.cm as cm class MainApp(QMainWindow , Ui_MainWindow): """ MainApp class inherit from QMainWindow and from Ui_MainWindow class in UiMainApp module. """ def __init__(self): """Constructor or the initializer""" QMainWindow.__init__(self) # It is imperative to call self.setupUi (self) for the interface to initialize. self.setupUi(self) # This is defined in design.py file automatically self.fig1() def fig1(self): # Fraunhofer diffraction lamda = self.slider_lambda.value()*1.E-9;k = (2.*pi) / lamda #wavelength of light in vaccuum b = self.slider_b.value()*1.E-5; h = self.slider_h.value()*1.E-5 # dimensions of diffracting rectangular aperture (m) # b is along (Ox) and h is along (Oy) f_2 = self.slider_f2.value() # f2 is the focal length of the lens L2(m) a = self.slider_a.value()*1.E-2 # Side of a square−shaped screen(m) X_Mmax = a / 2.;X_Mmin = -a / 2. Y_Mmax = X_Mmax;Y_Mmin = X_Mmin N = 400 X = linspace(X_Mmin, X_Mmax, N);Y = X # coordinates of screen B = (k*b*X) / (2.*f_2);H = (k*h*Y) / (2.*f_2) # intermediate variable # 2D representation BB, HH = meshgrid(B, H) I = ((sin(BB) / BB)**2)*((sin(HH) / HH)**2) # figure 2D mpl = self.mplwidget.canvas mpl.ax.clear() mpl.ax.imshow(I, cmap=cm.gray, interpolation='bilinear',origin ='lower', vmin = 0, vmax = .01) mpl.ax.set_xlabel(u'$X(m)$', fontsize = 12, fontweight ='bold') mpl.ax.set_ylabel(u'$Y(m)$', fontsize = 12, fontweight ='bold') mpl.ax.set_xticks(linspace(0, N, 5)) mpl.ax.set_xticklabels(linspace(X_Mmin, X_Mmax, 5), color='r') mpl.ax.set_yticks(linspace(0, N, 5)) mpl.ax.set_yticklabels(linspace(Y_Mmin, Y_Mmax, 5), color='r') mpl.figure.suptitle('Fraunhofer Diffraction by rectangular aperture | Zhihu ID: CHEN', fontsize = 14, fontweight ='bold') mpl.ax.set_title(r"$\lambda = %.3e \ m, \ b = %.2e \ m, \ h= % .2e \ m, \ f_2 = % .1f \ m$"% (lamda ,b, h, f_2),fontsize=10) mpl.draw() # DoubleSpinBox signals @ pyqtSlot("double") def on_SpinBox_lambda_valueChanged(self, value): self.slider_lambda.setValue(value) @ pyqtSlot("double") def on_SpinBox_b_valueChanged(self, value): self.slider_b.setValue(value) @ pyqtSlot("double") def on_SpinBox_h_valueChanged(self, value): self.slider_h.setValue(value) @ pyqtSlot("double") def on_SpinBox_a_valueChanged(self, value): self.slider_a.setValue(value) @ pyqtSlot("double") def on_SpinBox_f2_valueChanged(self, value): self.slider_f2.setValue(value) # Sliders signals @ pyqtSlot("int") def on_slider_lambda_valueChanged(self, value): self.SpinBox_lambda.setValue(value) self.fig1() @ pyqtSlot("int") def on_slider_b_valueChanged(self, value): self.SpinBox_b.setValue(value) self.fig1() @ pyqtSlot("int") def on_slider_h_valueChanged(self, value): self.SpinBox_h.setValue(value) self.fig1() @ pyqtSlot("int") def on_slider_a_valueChanged(self, value): self.SpinBox_a.setValue(value) self.fig1() @ pyqtSlot("int") def on_slider_f2_valueChanged(self, value): self.SpinBox_f2.setValue(value) self.fig1() if __name__ == "__main__": import sys app = QApplication(sys.argv) MyApplication = MainApp() MyApplication.show() # Show the form sys.exit(app.exec_()) # Execute the app


【1】注:本文的所有代码,大部分会包含注释,其余没包含注释的语句,可以留言咨询,第一次写文章专门介绍代码,没有相关经验,望大家海涵!欢迎读者在评论区友善讨论,我会热情回复。共勉!

【2】注:欢迎关注我的知乎账号,id:CHEN

,