RayTracing
Simple ray tracing library in Python for optical design that considers simple optical elements (with ABCD ray matrices) but also finite diameters of elements to calculate aperture and field stops, field of view, etc... Useful to validate the design of an optical system (lenses positions, power and diameters). Also permits the propagation of gaussian laser beams through the same elements.
Install / Use
/learn @DCC-Lab/RayTracingREADME
RayTracing
by the DCC/M Lab group http://www.dccmlab.ca, guided by Prof. Daniel Côté.
New: A graphical interface for Raytracing
A graphical interface is now available (macOS, Windows and Linux), with (python 3.12 and earlier, not 3.13):
pip install raytracing
python -m raytracing -a
<img width="1280" alt="image" src="https://github.com/user-attachments/assets/472b54ca-cb5e-41b4-abdc-f90c16cacc7c" />
This code aims to provide a simple ray tracing module for calculating various properties of optical paths (object, image, aperture stops, field stops). It makes use of ABCD matrices and does not consider spherical aberrations but can compute chromatic aberrations for simple cases when the materials are known. Since it uses the ABCD formalism (or Ray matrices, or Gauss matrices) it can perform tracing of rays and gaussian laser beams.
It is not a package to do "Rendering in 3D with raytracing".
The code has been developed first for teaching purposes and is used in my "Optique" Study Notes (french only), but also for actual use in my research. As of January 21st, 2021, there is an extensive, freely accessible, peer-reviewed tutorial in Journal of Neurophotonics:
"Tools and tutorial on practical ray tracing for microscopy"
by V. Pineau Noël*, S. Masoumi*, E. Parham*, G. Genest, L. Bégin, M.-A. Vigneault, D. C. Côté, Neurophotonics, 8(1), 010801 (2021). *Equal contributions. Permalink: https://doi.org/10.1117/1.NPh.8.1.010801
The published tutorial assumes version 1.3.x. There are video tutorials (in english or french, with english subtitles when in french) on YouTube. We have made no attempts at making high performance code but there is a GPU acceleration option through OpenCL and it still uses a python-like approach and hides the details, see below. Readability and simplicity of usage are the key here. It is a module with a few files, and only matplotlib and numpy as dependent modules.
Where do I get started?
- If you want to use the module, keep reading.
- If you have a suggestion or a bug report, go to Issues.
- If you want to read and contribute to the code, go to the Wiki for general considerations. We plan to have a roadmap in the near future.
Getting started
The module defines Ray , Matrix, MatrixGroup and ImagingPath as the main elements for tracing rays. Matrix and MatrixGroup are either one or a sequence of many matrices into which Ray will propagate. ImagingPath is also a sequence of elements, with an object at the front edge. Specific subclasses of Matrix exists: Space, Lens, ThicklLens, and Aperture. Finally, a ray fan is a collection of rays, originating from a given point with a range of angles.
We have tried to separate the calculation code (i.e. the matrices and subclasses) from the drawing code (figures and graphics). One can use the calculation code without any graphics calls.
If you want to perform calculations with coherent laser beams, then you use GaussianBeam and LaserPath. Everything is essentially the same, except that the formalism does not allow for the gaussian beam to be "blocked", hence any calculation of stops with aperture are not available in LaserPath. That part of the code is less developed, but it is nevertheless available.
What's new?
To get information about what is new, currently the best place is the release page on GitHub.
There is a Frequently Asked Questions page.
The article above is fully compatible with all 1.x versions starting at 1.3.
Installing and upgrading
You need matplotlib, which is a fairly standard Python module. If you do not have it, installing Anaconda is your best option. Python 3.6 or later is required. There are several ways to install the module:
- Simplest:
pip install raytracingorpip install --upgrade raytracing- If you need to install
pip, download getpip.py and run it withpython getpip.py
- If you need to install
- If you download the source of the module, then you can type:
python setup.py install - From GitHub, you can get the latest version (including bugs, which are 153% free!) and then type
python setup.py install - If you are completely lost, copying the folder
raytracing(the one that includes__init__.py) from the source file into the same directory as your own script will work. - Watch the tutorial with subtitles here.
Getting started
The simplest way to import the package in your own scripts after installing it:
from raytracing import *
This will import Ray , GaussianBeam, and several Matrix elements such as Space, Lens, ThickLens, Aperture, DielectricInterface, but also MatrixGroup (to group elements together), ImagingPath (to ray trace with an object at the front edge), LaserPath (to trace a gaussian laser beam from the front edge) and a few predefined other such as Objective (to create a very thick lens that mimicks an objective).
You create an ImagingPath or a LaserPath, which you then populate with optical elements such as Space, Lens or Aperture or vendor lenses. You can then adjust the path properties (object height in ImagingPath for instance or inputBeam for LaserPath) and display in matplotlib. You can create a group of elements with MatrixGroup for instance a telescope, a retrofocus or any group of optical elements you would like to treat as a "group". The Thorlabs and Edmund optics lenses, for instance, are defined as MatrixGroups.
This will show you a list of examples of things you can do (more on that in the Examples section):
python -m raytracing -l # List examples
python -m raytracing -e all # Run all of them
python -m raytracing -e 1,2,4,6 # Only run 1,2,4 and 6
python -m raytracing -t # Run all the tests. Some performance tests can take up to a minute, but they should all pass.
or request help with:
python -m raytracing -h
In your code, you would do this:
from raytracing import *
path = ImagingPath()
path.append(Space(d=50))
path.append(Lens(f=50, diameter=25))
path.append(Space(d=120))
path.append(Lens(f=70))
path.append(Space(d=100))
path.display()
<img src="https://github.com/DCC-Lab/RayTracing/raw/master/README.assets/simple.png" alt="simple" style="zoom:25%;" />
You can also call display() on an element to see the cardinal points, principal planes, BFL (back focal length, or the distance between the last interface and the focal point after the lens) and FFL (front focal length, or the distance between the focal point before the lens and the first interface). You can do it with any single Matrix element but also with MatrixGroup.
from raytracing import *
thorlabs.AC254_050_A().display()
eo.PN_33_921().display()


Finally, an addition as of 1.2.0 is the ability to obtain the intensity profile of a given source from the object plane at the exit plane of an OpticalPath. This is in fact really simple: by tracing a large number of rays, with the number of rays at y and θ being proportionnal to the intensity, one can obtain the intensity profile by plotting the histogram of rays reaching a given height at the image plane. Rays are small classes that return a Ray that satisfies the condition of the class. Currently, there is UniformRays,RandomUniformRays LambertianRays and RandomLambertianRays (a Lambertian distribution follows a cosθ distribution, it is a common diffuse surface source). They appear like iterators and can easily be used like this example script:
from raytracing import *
from numpy import *
import matplotlib.pyplot as plt
# Kohler illumination with these variables
fobj = 5
dObj = 5
f2 = 200
d2 = 50
f3 = 100
d3 = 50
# We build the path (i.e. not an Imaging path)
path = OpticalPath()
path.append(Space(d=f3))
path.append(Lens(f=f3, diameter=d3))
path.append(Space(d=f3))
path.append(Space(d=f2))
path.append(Lens(f=f2, diameter=d2))
path.append(Space(d=f2))
path.append(Space(d=fobj))
path.append(Lens(f=fobj, diameter=dObj))
path.append(Space(d=fobj))
# Obtaining the intensity profile
nRays = 1000000 # Increase for better resolution
inputRays = RandomLambertianRays(yMax=2.5, maxCount=nRays)
inputRays.display("Input profile")
outputRays = path.traceManyThrough(inputRays, progress=True)
# On macOS and Linux, you can do parallel computations.
# On Windows, who the hell knows? Maybe only on Windows 10 or Windows 7 32-bits, or whatever.
# outputRays = path.traceManyThroughInParallel(inputRays, progress=True, processes=8)
outputRays.display("Output profile")
and you will get the following ray histograms:
<img src="https://github.com/DCC-Lab/RayTracing/raw/master/README.assets/inputProfile.png" alt="inputProfile" style="zoom:25%;" /> <img src="https://github.com/DCC-Lab/RayTracing/raw/master/README.assets/outputProfile.png" alt="outputProfile" style="zoom:25%;" />Finally, it is possible to obtain the chromatic aberrations for compound lenses (achromatic doublets from Thorlabs and Edmund optics, and singlet lens because the materials are known). The following command will give you the focal shift as a function of wavelength (as a graph o
