用于验证光学系统设计(透镜位置和尺寸、焦距)的简单光线跟踪库。
raytracing的Python项目详细描述
光线跟踪
作者[丹尼尔•C_té](邮件地址:dccote@cervo.ulaval.ca?subject=raytracing python模块)
此代码旨在提供一个简单的光线跟踪模块,用于计算光路的各种特性(对象、图像、光圈停止、场停止)。它使用abcd矩阵,不考虑像差(球面或彩色)。因为它使用abcd形式(或光线矩阵,或高斯矩阵),所以它可以跟踪光线,也可以跟踪高斯激光束。
它不是一个要执行"使用光线跟踪进行三维渲染"的包。
该代码首先是为教学目的而开发的,并在我的"optique"学习笔记(仅限法语),也可在我的研究中实际使用。我没有尝试过编写高性能代码。可读性和使用的简单性是这里的关键。它是一个只有几个文件的模块,并且只将matplotlib作为依赖模块。
该模块将ray
、matrix
、matrixgroup
和imagingpath
定义为跟踪光线的主要元素。矩阵
和矩阵组
是一个矩阵或多个矩阵序列,其中射线
将传播到其中。imagingpath
也是一个元素序列,在前端有一个对象。矩阵的特定子类存在:空间
,镜头
,厚度
和光圈
。最后,光线扇是光线的集合,从具有一定角度范围的给定点发出。
如果要使用相干激光束进行计算,则使用gaussianbeam
和laserpath
。除了形式主义不允许高斯光束被"阻塞"外,所有东西本质上都是一样的,因此在laserpath
安装和升级
您需要matplotlib
,这是一个相当标准的python模块。如果您没有,安装蟒蛇是您的最佳选择。您应该选择Python3.7或更高版本。有几种安装模块的方法:
- 最简单的:
pip install raytracing
或pip install--升级raytracing
- 如果下载模块的源代码,则可以键入:
python setup.py install
- 从github中,您可以获得最新版本(包括bug),然后键入
python setup.py install
- 如果完全丢失,则可以将源文件中的文件夹
光线跟踪
(包含\uu init_uuuuuu.py
)复制到与您自己的脚本相同的目录中。
开始
安装软件包后,在您自己的脚本中导入该软件包的最简单方法:
fromraytracingimport*
这将导入ray
,gaussianbeam
,以及几个matrix
元素,例如space
,lens
,thicklens
,aperture
,medielectricinterface
,而且还导入matrix group
(将元素组合在一起)。imagingpath
(用物体在前边缘进行光线跟踪)、laserpath
(用高斯激光束从前边缘跟踪)和一些预定义的其他,如objective
(创建一个非常厚的镜头来模拟一个物体)。
创建imagingpath
或laserpath
,然后填充光学元件,例如space
、lens
或aperture
或供应商镜头。然后可以调整路径属性(imagingpath中的对象高度或实例或inputbeam,用于laserpath
)并显示在matplotlib中。您可以使用matrixgroup
创建一组元素,例如望远镜、后聚焦或任何一组您希望视为"组"的光学元素。例如,Thorlabs和Edmund光学镜头被定义为矩阵组
这将向您展示一些您可以做的事情的示例:
python -m raytracing
或请求帮助:
python -m raytracing -h
在您的代码中(例如源代码中包含的test.py
或demo.py
文件),您可以这样做:
fromraytracingimport*path=ImagingPath()path.append(Space(d=100))path.append(Lens(f=50,diameter=25))path.append(Space(d=120))path.append(Lens(f=70))path.append(Space(d=100))path.display()
还可以对元素调用display()来查看基点、主平面、bfl和ffl。您可以使用任何单个矩阵
元素,也可以使用矩阵组
from raytracing import *
thorlabs.AC254_050_A().display()
eo.PN_33_921().display()
文档
光学元件(带参数和默认值)的类层次结构为:
文档最多是稀疏的。您可以通过以下方式获得帮助:
- 从代码中读取自动生成的文档(不是很好看,但至少是一些文档):
-
LI>核心:
射线
:用于几何光学的射线,高度和角度分别为$y$和$\theta$。gaussianbeam
:具有复杂曲率半径$q$的高斯激光束。矩阵
:任何2x2矩阵。matrixgroup
:将一组矩阵视为一个单元(也将其绘制为一个单元)imagingpath
:amatrixgroup
前面有一个用于几何光学的对象laserpath
:amatrixgroup
在前端或谐振器处输入激光束。
- 光学元件:
光圈
,空间
,透镜
,电介质接口
,电介质板
,厚透镜
- 专用镜头:定义普通消色差和物镜
- Thorlabs镜头:来自Thorlabs的消色差双镜头。
- 埃德蒙光学镜头:埃德蒙光学的消色差双镜头
- 奥林巴斯的目标:奥林巴斯的一些目标。
- 眼镜:Thorlabs用来制作消色差双色眼镜的一些眼镜。它们都有一个函数n(波长),返回该波长的索引。从http://reflectiveindex.info>获取的所有数据。
帮助(矩阵)
,帮助(矩阵组)
帮助(射线)
,帮助(图像路径)
获取API,python>>>help(Matrix)HelponclassMatrixinmoduleraytracing.abcd:classMatrix(builtins.object)|Matrix(A,B,C,D,physicalLength=0,apertureDiameter=inf,label='')||Amatrixandanopticalelementthatcantransformarayoranother|matrix.||Thegeneralproperties(A,B,C,D)aredefinedhere.Theoperator"*"is|overloadedtoallowsimplestatementssuchas:||ray2=M1*ray|or|M3=M2*M1||Thephysicallengthisincludedinthematrixtoallowsimplemanagementof|theraytracing.IFtwomatricesaremultiplied,theresultingmatrice|willhaveaphysicallengththatisthesumofbothmatrices.||Inadditionfiniteaperturesareconsidered:iftheapertureDiameter|isnotinfinite(default),thentheobjectisassumedtolimitthe|rayheighttoplusorminusapertureDiameter/2fromthefrontedgetotheback|edgeoftheelement.||Methodsdefinedhere:||__init__(self,A,B,C,D,physicalLength=0,apertureDiameter=inf,label='')|Initializeself.Seehelp(type(self))foraccuratesignature.||__mul__(self,rightSide)|Operatoroverloadingallowingeasytoreadmatrixmultiplication||Forinstance,withM1=Matrix()andM2=Matrix(),onecanwrite|M3=M1*M2.Withr=Ray(),onecanapplytheM1transformtoaray|withr=M1*r||__str__(self)|Stringdescriptionthatallowstheuseofprint(Matrix())||backwardConjugate(self)|Withanimageatthebackedgeoftheelement,|whereistheobject?Distancebeforetheelementby|whicharaymusttraveltoreachtheconjugateplaneat|thebackoftheelement.Apositivedistancemeansthe|objectis"distance"infrontoftheelement(ortothe|left,orbefore).||M2=M1*Space(distance)|# M2.isImaging == True
示例
在示例目录中,可以运行demo.py
查看各种系统,illuminator.py
查看科勒照明器,以及invariant.py
查看镜头直径的作用示例以确定视野。但是,您也可以使用python-m raytracing直接运行模块,它将运行以下代码(\u main\uuu.py
),让您了解可能的情况:
from.imagingpathimport*from.laserpathimport*from.specialtylensesimport*from.axiconimport*importraytracing.thorlabsasthorlabsimportraytracing.eoaseoimportraytracing.olympusasolympusimportargparseap=argparse.ArgumentParser(prog='python -m raytracing')ap.add_argument("-e","--examples",required=False,default='all',help="Specific example numbers, separated by a comma")args=vars(ap.parse_args())examples=args['examples']ifexamples=='all':examples=range(1,30)else:examples=[int(y)foryinexamples.split(',')]if1inexamples:path=ImagingPath()path.label="Demo #1: lens f = 5cm, infinite diameter"path.append(Space(d=10))path.append(Lens(f=5))path.append(Space(d=10))path.display(comments="""Demo #1: lens with f=5 cm, infinite diameter An object at z=0 (front edge) is used. It is shown in blue. The image (or any intermediate images) are shown in red.\n\ This will use the default objectHeight and fanAngle but they can be changed with: path.objectHeight = 1.0 path.fanAngle = 0.5 path.fanNumber = 5 path.rayNumber = 3 Code: path = ImagingPath() path.label = "Demo #1: lens f = 5cm, infinite diameter" path.append(Space(d=10)) path.append(Lens(f=5)) path.append(Space(d=10)) path.display() """)if2inexamples:path=ImagingPath()path.label="Demo #2: Two lenses, infinite diameters"path.append(Space(d=10))path.append(Lens(f=5))path.append(Space(d=20))path.append(Lens(f=5))path.append(Space(d=10))path.display(comments="""Demo #2: Two lenses, infinite diameters An object at z=0 (front edge) is used with default properties (see Demo #1). Code: path = ImagingPath() path.label = "Demo #2: Two lenses, infinite diameters" path.append(Space(d=10)) path.append(Lens(f=5)) path.append(Space(d=20)) path.append(Lens(f=5)) path.append(Space(d=10)) path.display() """)# or#path.save("Figure 2.pdf")if3inexamples:path=ImagingPath()path.label="Demo #3: Finite lens"path.append(Space(d=10))path.append(Lens(f=5,diameter=2.5))path.append(Space(d=3))path.append(Space(d=17))path.display(comments="""Demo #3: A finite lens An object at z=0 (front edge) is used with default properties (see Demo #1). Notice the aperture stop (AS) identified at the lens which blocks the cone of light. There is no field stop to restrict the field of view, which is why we must use the default object and cannot restrict the field of view. Notice how the default rays are blocked. path = ImagingPath() path.objectHeight = 1.0 # object height (full). path.objectPosition = 0.0 # always at z=0 for now. path.fanAngle = 0.5 # full fan angle for rays path.fanNumber = 9 # number of rays in fan path.rayNumber = 3 # number of points on object path.label = "Demo #3: Finite lens" path.append(Space(d=10)) path.append(Lens(f=5, diameter=2.5)) path.append(Space(d=3)) path.append(Space(d=17)) path.display() """)if4inexamples:path=ImagingPath()path.label="Demo #4: Aperture behind lens"path.append(Space(d=10))path.append(Lens(f=5,diameter=3))path.append(Space(d=3))path.append(Aperture(diameter=3))path.append(Space(d=17))path.display(comments="""Demo #4: Aperture behind lens Notice the aperture stop (AS) identified after the lens, not at the lens. Again, since there is no field stop, we cannot restrict the object to the field of view because it is infinite. Code: path = ImagingPath() path.label = "Demo #4: Aperture behind lens" path.append(Space(d=10)) path.append(Lens(f=5, diameter=3)) path.append(Space(d=3)) path.append(Aperture(diameter=3)) path.append(Space(d=17)) path.display() """)if5inexamples:path=ImagingPath()path.label="Demo #5: Simple microscope system"path.fanAngle=0.1# full fan angle for rayspath.fanNumber=5# number of rays in fanpath.rayNumber=5# number of points on objectpath.append(Space(d=4))path.append(Lens(f=4,diameter=0.8,label='Obj'))path.append(Space(d=4+18))path.append(Lens(f=18,diameter=5.0,label='Tube Lens'))path.append(Space(d=18))path.display(limitObjectToFieldOfView=True,comments="""# Demo #5: Simple microscope system The aperture stop (AS) is at the entrance of the objective lens, and the tube lens, in this particular microscope, is the field stop (FS) and limits the field of view. Because the field stop exists, we can use limitObjectToFieldOfView=True when displaying, which will set the objectHeight to the field of view, but will still trace all the rays using our parameters. path = ImagingPath() path.label = "Demo #5: Simple microscope system" path.fanAngle = 0.1 # full fan angle for rays path.fanNumber = 5 # number of rays in fan path.rayNumber = 5 # number of points on object path.append(Space(d=4)) path.append(Lens(f=4, diameter=0.8, label='Obj')) path.append(Space(d=4 + 18)) path.append(Lens(f=18, diameter=5.0, label='Tube Lens')) path.append(Space(d=18)) path.display() """)if6inexamples:path=ImagingPath()path.label="Demo #6: Simple microscope system, only principal rays"path.append(Space(d=4))path.append(Lens(f=4,diameter=0.8,label='Obj'))path.append(Space(d=4+18))path.append(Lens(f=18,diameter=5.0,label='Tube Lens'))path.append(Space(d=18))path.display(limitObjectToFieldOfView=True,onlyChiefAndMarginalRays=True,comments="""# Demo #6: Simple microscope system, only principal rays The aperture stop (AS) is at the entrance of the objective lens, and the tube lens, in this particular microscope, is the field stop (FS) and limits the field of view. Because the field stop exists, we can use limitObjectToFieldOfView=True when displaying, which will set the objectHeight to the field of view. We can also require that only the principal rays are drawn: chief ray marginal ray (or axial ray). path = ImagingPath() path.label = "Demo #6: Simple microscope system, only principal rays" path.append(Space(d=4)) path.append(Lens(f=4, diameter=0.8, label='Obj')) path.append(Space(d=4 + 18)) path.append(Lens(f=18, diameter=5.0, label='Tube Lens')) path.append(Space(d=18)) path.display() """)if7inexamples:path=ImagingPath()path.label="Demo #7: Focussing through a dielectric slab"path.append(Space(d=10))path.append(Lens(f=5))path.append(Space(d=3))path.append(DielectricSlab(n=1.5,thickness=4))path.append(Space(d=10))path.display(comments=path.label+"""\n path = ImagingPath() path.label = "Demo #7: Focussing through a dielectric slab" path.append(Space(d=10)) path.append(Lens(f=5)) path.append(Space(d=3)) path.append(DielectricSlab(n=1.5, thickness=4)) path.append(Space(d=10))""")if8inexamples:# Demo #8: Virtual imagepath=ImagingPath()path.label="Demo #8: Virtual image at -2f with object at f/2"path.append(Space(d=2.5))path.append(Lens(f=5))path.append(Space(d=10))path.display(comments=path.label+"""\n path = ImagingPath() path.label = "Demo #8: Virtual image at -2f with object at f/2" path.append(Space(d=2.5)) path.append(Lens(f=5)) path.append(Space(d=10)) path.display()""")if9inexamples:# Demo #9: Infinite telecentric 4f telescopepath=ImagingPath()path.label="Demo #9: Infinite telecentric 4f telescope"path.append(Space(d=5))path.append(Lens(f=5))path.append(Space(d=10))path.append(Lens(f=5))path.append(Space(d=5))path.display(comments=path.label+"""\n path = ImagingPath() path.label = "Demo #9: Infinite telecentric 4f telescope" path.append(Space(d=5)) path.append(Lens(f=5)) path.append(Space(d=10)) path.append(Lens(f=5)) path.append(Space(d=5)) """)if10inexamples:path=ImagingPath()path.fanAngle=0.05path.append(Space(d=20))path.append(Lens(f=-10,label='Div'))path.append(Space(d=7))path.append(Lens(f=10,label='Foc'))path.append(Space(d=40))(focal,focal)=path.effectiveFocalLengths()bfl=path.backFocalLength()path.label="Demo #10: Retrofocus $f_e$={0:.1f} cm, and BFL={1:.1f}".format(focal,bfl)path.display(comments=path.label+"""\n A retrofocus has a back focal length longer than the effective focal length. It comes from a diverging lens followed by a converging lens. We can always obtain the effective focal lengths and the back focal length of a system. path = ImagingPath() path.fanAngle = 0.05 path.append(Space(d=20)) path.append(Lens(f=-10, label='Div')) path.append(Space(d=7)) path.append(Lens(f=10, label='Foc')) path.append(Space(d=40)) (focal,focal) = path.effectiveFocalLengths() bfl = path.backFocalLength() path.label = "Demo #10: Retrofocus $f_e$={0:.1f} cm, and BFL={1:.1f}".format(focal, bfl) path.display() """)if11inexamples:# Demo #11: Thick diverging lenspath=ImagingPath()path.label="Demo #11: Thick diverging lens"path.objectHeight=20path.append(Space(d=50))path.append(ThickLens(R1=-20,R2=20,n=1.55,thickness=10,diameter=25,label='Lens'))path.append(Space(d=50))path.display(onlyChiefAndMarginalRays=True,comments=path.label+"""\n path = ImagingPath() path.label = "Demo #11: Thick diverging lens" path.objectHeight = 20 path.append(Space(d=50)) path.append(ThickLens(R1=-20, R2=20, n=1.55, thickness=10, diameter=25, label='Lens')) path.append(Space(d=50)) path.display()""")if12inexamples:# Demo #12: Thick diverging lens built from individual elementspath=ImagingPath()path.label="Demo #12: Thick diverging lens built from individual elements"path.objectHeight=20path.append(Space(d=50))path.append(DielectricInterface(R=-20,n1=1.0,n2=1.55,diameter=25,label='Front'))path.append(Space(d=10,diameter=25,label='Lens'))path.append(DielectricInterface(R=20,n1=1.55,n2=1.0,diameter=25,label='Back'))path.append(Space(d=50))path.display(onlyChiefAndMarginalRays=True,comments=path.label+"""\n path = ImagingPath() path.label = "Demo #12: Thick diverging lens built from individual elements" path.objectHeight = 20 path.append(Space(d=50)) path.append(DielectricInterface(R=-20, n1=1.0, n2=1.55, diameter=25, label='Front')) path.append(Space(d=10, diameter=25, label='Lens')) path.append(DielectricInterface(R=20, n1=1.55, n2=1.0, diameter=25, label='Back')) path.append(Space(d=50)) path.display()""")if13inexamples:# Demo #13, forward and backward conjugates# We can obtain the position of the image for any matrix# by using forwardConjugate(): it calculates the distance# after the element where the image is, assuming an object# at the front surface.M1=Space(d=10)M2=Lens(f=5)M3=M2*M1print(M3.forwardConjugate())print(M3.backwardConjugate())if14inexamples:# Demo #14: Generic objectivesobj=Objective(f=10,NA=0.8,focusToFocusLength=60,backAperture=18,workingDistance=2,label="Objective")print("Focal distances: ",obj.focalDistances())print("Position of PP1 and PP2: ",obj.principalPlanePositions(z=0))print("Focal spots positions: ",obj.focusPositions(z=0))print("Distance between entrance and exit planes: ",obj.L)path=ImagingPath()path.fanAngle=0.0path.fanNumber=1path.rayNumber=15path.objectHeight=10.0path.label="Demo #14 Path with generic objective"path.append(Space(180))path.append(obj)path.append(Space(10))path.display(comments=path.label+""" path = ImagingPath() path.fanAngle = 0.0 path.fanNumber = 1 path.rayNumber = 15 path.objectHeight = 10.0 path.label = "Path with generic objective" path.append(Space(180)) path.append(obj) path.append(Space(10)) path.display()""")if15inexamples:# Demo #15: Olympus objective LUMPlanFL40Xpath=ImagingPath()path.fanAngle=0.0path.fanNumber=1path.rayNumber=15path.objectHeight=10.0path.label="Demo #15 Path with LUMPlanFL40X"path.append(Space(180))path.append(olympus.LUMPlanFL40X())path.display(comments=path.label+""" path = ImagingPath() path.fanAngle = 0.0 path.fanNumber = 1 path.rayNumber = 15 path.objectHeight = 10.0 path.label = "Path with LUMPlanFL40X" path.append(Space(180)) path.append(olympus.LUMPlanFL40X()) path.append(Space(10)) path.display()""")if16inexamples:# Demo #16: Vendor lensesthorlabs.AC254_050_A().display()eo.PN_33_921().display()if17inexamples:# Demo #17: Vendor lensespath=ImagingPath()path.label="Demo #17: Vendor Lenses"path.append(Space(d=50))path.append(thorlabs.AC254_050_A())path.append(Space(d=50))path.append(thorlabs.AC254_050_A())path.append(Space(d=150))path.append(eo.PN_33_921())path.append(Space(d=50))path.append(eo.PN_88_593())path.append(Space(180))path.append(olympus.LUMPlanFL40X())path.append(Space(10))path.display(comments=path.label+"""\n path = ImagingPath() path.label = "Demo #17: Vendor Lenses" path.append(Space(d=50)) path.append(thorlabs.AC254_050_A()) path.append(Space(d=50)) path.append(thorlabs.AC254_050_A()) path.append(Space(d=150)) path.append(eo.PN_33_921()) path.append(Space(d=50)) path.append(eo.PN_88_593()) path.append(Space(180)) path.append(olympus.LUMPlanFL40X()) path.append(Space(10)) path.display()""")if18inexamples:# Demo #18: Laser beam and vendor lensespath=LaserPath()path.label="Demo #18: Laser beam and vendor lenses"path.append(Space(d=50))path.append(thorlabs.AC254_050_A())path.append(Space(d=50))path.append(thorlabs.AC254_050_A())path.append(Space(d=150))path.append(eo.PN_33_921())path.append(Space(d=50))path.append(eo.PN_88_593())path.append(Space(d=180))path.append(olympus.LUMPlanFL40X())path.append(Space(d=10))path.display(inputBeam=GaussianBeam(w=0.001),comments=""" path = LaserPath() path.label = "Demo #18: Laser beam and vendor lenses" path.append(Space(d=50)) path.append(thorlabs.AC254_050_A()) path.append(Space(d=50)) path.append(thorlabs.AC254_050_A()) path.append(Space(d=150)) path.append(eo.PN_33_921()) path.append(Space(d=50)) path.append(eo.PN_88_593()) path.append(Space(d=180)) path.append(olympus.LUMPlanFL40X()) path.append(Space(d=10)) path.display()""")if19inexamples:cavity=LaserPath(label="Laser cavity: round trip\nCalculated laser modes")cavity.isResonator=Truecavity.append(Space(d=160))cavity.append(DielectricSlab(thickness=100,n=1.8))cavity.append(Space(d=160))cavity.append(CurvedMirror(R=400))cavity.append(Space(d=160))cavity.append(DielectricSlab(thickness=100,n=1.8))cavity.append(Space(d=160))# Calculate all self-replicating modes (i.e. eigenmodes)(q1,q2)=cavity.eigenModes()print(q1,q2)# Obtain all physical (i.e. finite) self-replicating modesqs=cavity.laserModes()forqinqs:print(q)# Showcavity.display()
已知限制
实际计算中没有已知的错误,但显示中有错误或限制:
- 在一个图上放置几个没有重叠的标签是不容易的。我还在努力呢。
- 也不容易弄清楚箭头的"正确大小"、字体、标签的位置、光圈上"刻度"的大小。
- 用适当的二级标签标记焦点应该是可能的,也许是上标?
- 当元素具有无限大的直径时,y比例并不总是适当设置的:光线将超出在图形上绘制的元素。
许可证
此代码在麻省理工学院许可证下提供。