├── .gitattributes
├── .github
└── workflows
│ ├── pythonpublish.yml
│ └── stale.yml
├── .gitignore
├── .idea
├── .gitignore
├── inspectionProfiles
│ └── profiles_settings.xml
├── modules.xml
├── other.xml
└── vcs.xml
├── .pylintrc
├── .readthedocs.yml
├── FAQ.md
├── LICENSE
├── MANIFEST.in
├── README-Adding-new-materials.md
├── README.assets
├── chromaticaberrations.png
├── e0-0356707.png
├── e0.png
├── ex01.png
├── ex02.png
├── ex03.png
├── ex04.png
├── ex05.png
├── ex06.png
├── ex07.png
├── ex08.png
├── ex09.png
├── ex10.png
├── ex11.png
├── ex12.png
├── ex14.png
├── ex15.png
├── ex16.1.png
├── ex16.2.png
├── ex16.3.png
├── ex17.png
├── ex18.png
├── hierarchy.png
├── image-20190511230415520.png
├── inputProfile.png
├── kholerIllumination.png
├── microscopeSimple.png
├── outputProfile.png
├── simple.png
├── simpleObjectImage.png
├── thorlabs-0356707.png
└── thorlabs.png
├── README.md
├── docs
├── FAQ.rst
├── Makefile
├── README-documentation.md
├── _templates
│ ├── autoClass.rst
│ ├── autoFunction.rst
│ ├── autoInheritedFunction.rst
│ ├── classDefault.rst
│ └── moduleDefault.rst
├── conf.py
├── contribute.md
├── examples.md
├── gettingStarted.md
├── images
│ ├── AngleHist.png
│ ├── Histogram.png
│ ├── ImagingPath.png
│ ├── LambertianRays.png
│ ├── RandomLambertianRays.png
│ ├── RandomUniformRays.png
│ ├── UniformRays.png
│ ├── apertureStop.png
│ ├── displayRays.png
│ ├── flipOrientation_after.png
│ ├── flipOrientation_before.png
│ └── matrixGraphicDisplay.png
├── index.rst
├── make.bat
├── raytracing.rst
├── reference.rst
├── requirements.txt
└── webgraphviz_url
│ ├── index.html
│ ├── jquery.min.js
│ ├── site.js
│ └── viz.js
├── examples
└── README.md
├── pyproject.toml
└── raytracing
├── __init__.py
├── __main__.py
├── axicon.py
├── components.py
├── eo.py
├── examples
├── README.md
├── __init__.py
├── confocalDetection.py
├── envexamples.py
├── ex01.py
├── ex02.py
├── ex03.py
├── ex04.py
├── ex05.py
├── ex06.py
├── ex07.py
├── ex08.py
├── ex09.py
├── ex10.py
├── ex11.py
├── ex12.py
├── ex13.py
├── ex14.py
├── ex15.py
├── ex16.py
├── ex17.py
├── ex18.py
├── ex19.py
├── fig1-matrices.png
├── fig2-properties.png
├── fig3-apertures.png
├── fig4-overviewFigure.png
├── fig5-LSM_reference.png
├── fig6-lsmConfocalpinhole.py
├── fig7-kohler.py
├── fig8-widefield.py
├── illuminator.py
├── invariant.py
├── invariantAndEfficiency.py
├── kohlerAndInvariant.py
├── laserScanningMicroscope.py
├── multiProcessingTracing.py
├── parallel_time_smallcounts.pdf
├── parallel_time_smallcounts.png
├── randomLambertianRays.py
├── randomUniformRays.py
├── sourceCollection.py
├── template.py
└── twoPhotonDescannedDetector.py
├── figure.py
├── gaussianbeam.py
├── graphicComponents.py
├── graphics.py
├── imagingpath.py
├── interface.py
├── lasercavity.py
├── laserpath.py
├── lensViewer.py
├── materials.py
├── matrix.py
├── matrixgroup.py
├── nikon.py
├── olympus.py
├── preferences.py
├── ray.py
├── rays.py
├── specialtylenses.py
├── specifications
├── AC254-100-A-Zemax(ZMX).zmx
├── AC254-125-A-Zemax(ZMX).zmx
├── ASL5040-UV-Zemax(ZMX).zmx
├── envtest.py
├── lensBuilder.py
├── olympus-table3.csv
├── references.md
├── thorlabs.Achromatic_Doublet_AR_Coatings.xlsx
└── zmax_49270.zmx
├── tests
├── .coveragerc
├── envtest.py
├── parallel_time_smallcounts.png
├── shifts-ac254-100-A.txt
├── shifts-ac254-200-A.txt
├── testDisplay.py
├── testsAxicon.py
├── testsCallOtherScript.py
├── testsComponents.py
├── testsDecorators.py
├── testsDocTestModule.py
├── testsEnvtest.py
├── testsExamples.py
├── testsGaussian.py
├── testsImagingPath.py
├── testsLagrangeInvariantIdeas.py
├── testsLaserPath.py
├── testsLimits.py
├── testsMaterials.py
├── testsMatrix.py
├── testsMatrixGroup.py
├── testsMatrixSubclasses.py
├── testsMultiprocessor.py
├── testsPreferences.py
├── testsPreferencesRaytracing.py
├── testsRay.py
├── testsRays.py
├── testsRaysSubclasses.py
├── testsReport.py
├── testsSpecialLenses.py
├── testsSpecialtyLenses.py
├── testsTransforms.py
├── testsUtils.py
├── testsWarnings.py
├── testsZemax.py
├── traceManyThroughInParallelNoOutput.py
└── traceManyThroughInParallelWithOutput.py
├── thorlabs.py
├── ui
├── __init__.py
└── raytracing_app.py
├── utils.py
└── zemax.py
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
--------------------------------------------------------------------------------
/.github/workflows/pythonpublish.yml:
--------------------------------------------------------------------------------
1 | # This workflows will upload a Python Package using Twine when a release is created
2 | # For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries
3 |
4 | name: Upload Python Package
5 |
6 | on:
7 | release:
8 | types: [created]
9 |
10 | jobs:
11 | deploy:
12 |
13 | runs-on: ubuntu-latest
14 |
15 | steps:
16 | - uses: actions/checkout@v2
17 | - name: Set up Python
18 | uses: actions/setup-python@v1
19 | with:
20 | python-version: '3.6'
21 | - name: Install dependencies
22 | run: |
23 | python -m pip install --upgrade pip
24 | pip install setuptools wheel twine
25 | - name: Build and publish
26 | env:
27 | TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }}
28 | TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }}
29 | run: |
30 | python setup.py sdist bdist_wheel
31 | twine upload dist/*
32 |
--------------------------------------------------------------------------------
/.github/workflows/stale.yml:
--------------------------------------------------------------------------------
1 | # This workflow warns and then closes issues and PRs that have had no activity for a specified amount of time.
2 | #
3 | # You can adjust the behavior by modifying this file.
4 | # For more information, see:
5 | # https://github.com/actions/stale
6 | name: Mark stale issues and pull requests
7 |
8 | on:
9 | schedule:
10 | - cron: '37 11 * * *'
11 |
12 | jobs:
13 | stale:
14 |
15 | runs-on: ubuntu-latest
16 | permissions:
17 | issues: write
18 | pull-requests: write
19 |
20 | steps:
21 | - uses: actions/stale@v3
22 | with:
23 | repo-token: ${{ secrets.GITHUB_TOKEN }}
24 | stale-issue-message: 'Stale issue message'
25 | stale-pr-message: 'Stale pull request message'
26 | stale-issue-label: 'no-issue-activity'
27 | stale-pr-label: 'no-pr-activity'
28 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 |
6 | # C extensions
7 | *.so
8 |
9 | # Distribution / packaging
10 | .Python
11 | build/
12 | develop-eggs/
13 | dist/
14 | downloads/
15 | eggs/
16 | .eggs/
17 | lib/
18 | lib64/
19 | parts/
20 | sdist/
21 | var/
22 | wheels/
23 | *.egg-info/
24 | .installed.cfg
25 | *.egg
26 | MANIFEST
27 |
28 | # PyInstaller
29 | # Usually these files are written by a python script from a template
30 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
31 | *.manifest
32 | *.spec
33 |
34 | # Installer logs
35 | pip-log.txt
36 | pip-delete-this-directory.txt
37 |
38 | # Unit test / coverage reports
39 | htmlcov/
40 | coverage_html_report/
41 | .tox/
42 | .nox/
43 | .coverage
44 | .coverage.*
45 | .cache
46 | nosetests.xml
47 | coverage.xml
48 | *.cover
49 | .hypothesis/
50 | .pytest_cache/
51 |
52 | # Translations
53 | *.mo
54 | *.pot
55 |
56 | # Django stuff:
57 | *.log
58 | local_settings.py
59 | db.sqlite3
60 |
61 | # Flask stuff:
62 | instance/
63 | .webassets-cache
64 |
65 | # Scrapy stuff:
66 | .scrapy
67 |
68 | # Sphinx documentation
69 | docs/_build/
70 | docs/modules/
71 |
72 | # PyBuilder
73 | target/
74 |
75 | # Jupyter Notebook
76 | .ipynb_checkpoints
77 |
78 | # IPython
79 | profile_default/
80 | ipython_config.py
81 |
82 | # pyenv
83 | .python-version
84 |
85 | # celery beat schedule file
86 | celerybeat-schedule
87 |
88 | # SageMath parsed files
89 | *.sage.py
90 |
91 | # Environments
92 | .env
93 | .venv
94 | env/
95 | venv*/
96 | ENV/
97 | env.bak/
98 | venv.bak/
99 |
100 | # Spyder project settings
101 | .spyderproject
102 | .spyproject
103 |
104 | # Rope project settings
105 | .ropeproject
106 |
107 | # mkdocs documentation
108 | /site
109 |
110 | # mypy
111 | .mypy_cache/
112 | .dmypy.json
113 | dmypy.json
114 |
115 | # Pyre type checker
116 | .pyre/
117 | test.py
118 |
119 | # Everything below is specific to JetBrains and PyCharm
120 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
121 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
122 |
123 | # User-specific stuff
124 | .idea/**/workspace.xml
125 | .idea/**/tasks.xml
126 | .idea/**/usage.statistics.xml
127 | .idea/**/dictionaries
128 | .idea/**/shelf
129 |
130 | # Generated files
131 | .idea/**/contentModel.xml
132 |
133 | # Sensitive or high-churn files
134 | .idea/**/dataSources/
135 | .idea/**/dataSources.ids
136 | .idea/**/dataSources.local.xml
137 | .idea/**/sqlDataSources.xml
138 | .idea/**/dynamic.xml
139 | .idea/**/uiDesigner.xml
140 | .idea/**/dbnavigator.xml
141 |
142 | # Gradle
143 | .idea/**/gradle.xml
144 | .idea/**/libraries
145 |
146 | # Gradle and Maven with auto-import
147 | # When using Gradle or Maven with auto-import, you should exclude module files,
148 | # since they will be recreated, and may cause churn. Uncomment if using
149 | # auto-import.
150 | # .idea/artifacts
151 | # .idea/compiler.xml
152 | # .idea/jarRepositories.xml
153 | # .idea/modules.xml
154 | # .idea/*.iml
155 | # .idea/modules
156 | # *.iml
157 | # *.ipr
158 |
159 | # CMake
160 | cmake-build-*/
161 |
162 | # Mongo Explorer plugin
163 | .idea/**/mongoSettings.xml
164 |
165 | # File-based project format
166 | *.iws
167 |
168 | # IntelliJ
169 | out/
170 |
171 | # mpeltonen/sbt-idea plugin
172 | .idea_modules/
173 |
174 | # JIRA plugin
175 | atlassian-ide-plugin.xml
176 |
177 | # Cursive Clojure plugin
178 | .idea/replstate.xml
179 |
180 | # Crashlytics plugin (for Android Studio and IntelliJ)
181 | com_crashlytics_export_strings.xml
182 | crashlytics.properties
183 | crashlytics-build.properties
184 | fabric.properties
185 |
186 | # Editor-based Rest Client
187 | .idea/httpRequests
188 |
189 | # Android studio 3.1+ serialized cache file
190 | .idea/caches/build_file_checksums.ser
191 |
192 | .idea/RayTracing.iml
193 |
194 | .idea/misc.xml
195 | .DS_Store
196 | raytracing/_version.py
197 |
--------------------------------------------------------------------------------
/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | # Default ignored files
2 | /shelf/
3 | /workspace.xml
4 | # Editor-based HTTP Client requests
5 | /httpRequests/
6 | # Datasource local storage ignored files
7 | /dataSources/
8 | /dataSources.local.xml
9 |
--------------------------------------------------------------------------------
/.idea/inspectionProfiles/profiles_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.idea/other.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.readthedocs.yml:
--------------------------------------------------------------------------------
1 | # .readthedocs.yml
2 | # Read the Docs configuration file
3 | # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
4 |
5 | # Required
6 | version: 2
7 |
8 | # Build documentation in the docs/ directory with Sphinx
9 | sphinx:
10 | configuration: docs/conf.py
11 |
12 | # Build documentation with MkDocs
13 | #mkdocs:
14 | # configuration: mkdocs.yml
15 |
16 | # Optionally build your docs in additional formats such as PDF
17 | formats: []
18 |
19 | # Optionally set the version of Python and requirements required to build your docs
20 | python:
21 | version: 3.8
22 | install:
23 | - requirements: docs/requirements.txt
24 | - method: setuptools
25 | path: .
26 |
--------------------------------------------------------------------------------
/FAQ.md:
--------------------------------------------------------------------------------
1 | # Frequently Asked Questions
2 |
3 |
4 |
5 | 1. **What is the formalism used and where can I learn about it?**
6 |
7 | We use the ray matrix formalism and you can learn about it in most optics books as well as in the publication:
8 |
9 | > ["Tools and tutorial on practical ray tracing for microscopy"](https://doi.org/10.1117/1.NPh.8.1.010801)
10 | >
11 | > by V. Pineau Noël\*, S. Masoumi\*, E. Parham*, G. Genest, L. Bégin, M.-A. Vigneault, D. C. Côté,
12 | > Neurophotonics, 8(1), 010801 (2021).
13 | > *Equal contributions.
14 | > Permalink: https://doi.org/10.1117/1.NPh.8.1.010801
15 |
16 | If you happen to speak French, D.C. Côté's Optique notes are available [online](https://books.apple.com/ca/book/optique/id949326768) and as PDF and there is a chapter on it.
17 |
18 | 2. **Can you do spherical aberrations?**
19 |
20 | No we cannot. It may appear at first sight that it is possible (we could easily correct the sinθ ≅ θ approximation when off-axis) but the ray matrix formalism does not allow us to consider the different propagation distances of the off-axis rays. By definition, for exemple, a element of `Space` has a given length `d` and it is the same for all rays. When considering spherical aberrations, this varying distance for rays that are off-axis is part of the aberration, which is not given solely by the correction to the angle. Since we cannot have a different distance for all rays (i.e. it is a matrix multiplication), then spherical aberrations are not possible (as far as we know). ***You think we are wrong? Teach us how to do it!***
21 |
22 | 3. **Can you do chromatic aberrations?**
23 |
24 | Yes we can, when the lenses are built from dielectric interfaces and materials with `Materials`. The Thorlabs and Edmund Optics lenses are built with a radius of curvature and the proper material as obtained from the excellent site http://refractiveindex.info.
25 | It is not possible however to consider chromatic aberrations for a thin lens. The thin lens is created directly with a given focal length and no material at all (it is infinitely thin). The focal length is fixed. To create a lens with chromatic aberration, you need to use `SingletLens` or `AchromaticDoubletLens`.
26 |
27 | 4. **Can I perform more complex calculations than just tracing rays?**
28 | Of course you can. Although the display functions are central to the module, you can also extract more information in a script. For instance, the example [lsmConfocalPinhole.py](https://github.com/DCC-Lab/RayTracing/blob/master/raytracing/examples/fig6-lsmConfocalpinhole.py) is a good example where the transmission efficacy of a pinhole is calculated as a function of distance from the focal plane.
29 |
30 | 5. **Can you perform a 2D efficiency calculation that will apply to my real-life optical system?**
31 | Yes, but you need to use a ray distribution that considers the cylindrical symmetry (currently in preparation). This will provide proper numbers for the 2D energy efficiency, but it will not be "traceable" easily. To trace your rays, use the `RandomUniformRays` class for instance.
32 |
33 | 6. **There are stored preferences. Where are they?**
34 | Some variables can be "saved" into a Preferences file on disk. Where it is depends on the platform. The file is always the same (it is a JSON dictionary) but the location is platform-specific:
35 |
36 | * macOS: `(username)/Library/Preferences/ca.dcclab.python.raytracing.json`
37 |
38 | * Windows: `(username)/AppData(something-something)/ca.dcclab.python.raytracing.json`
39 |
40 | * linux: `(username)/.config/ca.dcclab.python.raytracing.json`
41 |
42 |
43 | Some of the variables that are stored are: `lastVersionCheck` (date of the last version check on PyPI) and `mode` which can be `beginner`, `expert`, or `silent`.
44 |
45 | 7. **There are a lot of warnings printed on screen, and I don't like it because I know what I am doing. Can I get rid of that?**
46 | Yes you can. There are three modes: `beginner` (many warnings), `expert` (very few, but some), and `silent` (nothing). Call `silentMode(saveToPrefs=True)` or `expertMode(saveToPrefs=True)` and you will be all set.
47 |
48 | 8. **What is the front and the back of a lens?**
49 | A lens has a focus on both sides. The side where the light enters is called the front side, and therefore the focus on that side is at a distance "front focal length (or FFL)" away from the physical surface of the lens. Similarly (and possibly surprisingly) the focus to which the light will go after having propagated through the lens is the "back focus" and is a distance "back focal length or (BFL)" away from the last physical surface of the lens.
50 |
51 | These terms are standard terms in optical design.
52 |
53 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019-2022 DCCLab and Daniel C. Côté
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/MANIFEST.in:
--------------------------------------------------------------------------------
1 | include README.md
2 | include assets/*
3 | include examples/*
4 | include doc/*.html
--------------------------------------------------------------------------------
/README-Adding-new-materials.md:
--------------------------------------------------------------------------------
1 | ## Adding a new material to the Raytracing module
2 |
3 | If you attempt to read a Zemax file with ZMXReader, you may encounter an error if the material is not recognized:
4 |
5 | ```shell
6 | ValueError: The requested material 'SomeWeirdMaterial' is not recognized in the list of materials of raytracing: ['Air', 'N_BK7', 'N_SF2', 'N_SF8', 'SF2', 'SF5', 'N_SF5', 'N_SF6', 'N_SF6HT', 'N_SF10', 'N_SF11', 'N_SF57', 'N_BAF10', 'E_BAF11', 'N_BAK1', 'N_BAK4', 'FK51A', 'LAFN7', 'N_LASF9', 'N_LAK22', 'N_SSK5', 'E_FD10', 'FusedSilica']. You need to implement it as asubclass of Material, see materials.py for examples.
7 | ```
8 |
9 |
10 |
11 | The reason is that there are tons of materials available and it is ridiculous to enter them all ifthey are not used in optical components. With the excellent http://refractiveindex.info, we are always just one click away from an answer.
12 |
13 | ## How do you add a new material?
14 |
15 | It is in fact quite simple:
16 |
17 | 1. Take a look at `materials.py` for many examples.
18 |
19 | 2. Derive a class from material, give it the name of your material (e.g., `N_LAK21`)
20 |
21 | 3. Then you need to define `n()` and the Abbe number:
22 |
23 | ```python
24 | @classmethod
25 | def n(cls, wavelength): # Wavelengths are always in microns (like Zemax, refractiveindex.info, etc).
26 | return calculate the value
27 |
28 | @classmethod
29 | def abbeNumber(cls):
30 | return aValue
31 | ```
32 |
33 | 4. All you need to define is in this example for N_BK7:
34 |
35 | ```python
36 | class N_BK7(Material):
37 | """ All data from https://refractiveindex.info/tmp/data/glass/schott/N-BK7.html """
38 | @classmethod
39 | def n(cls, wavelength):
40 | if wavelength > 10 or wavelength < 0.01:
41 | raise ValueError("Wavelength must be in microns")
42 | x = wavelength
43 |
44 | n=(1+1.03961212/(1-0.00600069867/x**2)+0.231792344/(1-0.0200179144/x**2)+1.01046945/(1-103.560653/x**2))**.5
45 | return n
46 |
47 | @classmethod
48 | def abbeNumber(cls):
49 | return 64.17
50 | ```
51 |
52 | ## Will Raytracing know about my new material?
53 |
54 | Yes it will, through the magic of Python, it is possible to know when subclasses are created.
55 |
56 | There are functions to find materials (such as `Material.findByName()` or `Material.findByIndex()`. You actually don't need to include your code into the main raytracing code: if you derive a class from Material (as above) it will be included in the search without having to do anything.
57 |
58 | You can then send it to us, we'll include it if it is relevant. Send to dccote@cervo.ulaval.ca, or create an [Issue on GitHub](https://github.com/DCC-Lab/RayTracing/issues) and paste your code.
59 |
60 |
--------------------------------------------------------------------------------
/README.assets/chromaticaberrations.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DCC-Lab/RayTracing/9609b55f16c8ce4499c85a2622619f60b7675fa3/README.assets/chromaticaberrations.png
--------------------------------------------------------------------------------
/README.assets/e0-0356707.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DCC-Lab/RayTracing/9609b55f16c8ce4499c85a2622619f60b7675fa3/README.assets/e0-0356707.png
--------------------------------------------------------------------------------
/README.assets/e0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DCC-Lab/RayTracing/9609b55f16c8ce4499c85a2622619f60b7675fa3/README.assets/e0.png
--------------------------------------------------------------------------------
/README.assets/ex01.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DCC-Lab/RayTracing/9609b55f16c8ce4499c85a2622619f60b7675fa3/README.assets/ex01.png
--------------------------------------------------------------------------------
/README.assets/ex02.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DCC-Lab/RayTracing/9609b55f16c8ce4499c85a2622619f60b7675fa3/README.assets/ex02.png
--------------------------------------------------------------------------------
/README.assets/ex03.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DCC-Lab/RayTracing/9609b55f16c8ce4499c85a2622619f60b7675fa3/README.assets/ex03.png
--------------------------------------------------------------------------------
/README.assets/ex04.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DCC-Lab/RayTracing/9609b55f16c8ce4499c85a2622619f60b7675fa3/README.assets/ex04.png
--------------------------------------------------------------------------------
/README.assets/ex05.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DCC-Lab/RayTracing/9609b55f16c8ce4499c85a2622619f60b7675fa3/README.assets/ex05.png
--------------------------------------------------------------------------------
/README.assets/ex06.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DCC-Lab/RayTracing/9609b55f16c8ce4499c85a2622619f60b7675fa3/README.assets/ex06.png
--------------------------------------------------------------------------------
/README.assets/ex07.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DCC-Lab/RayTracing/9609b55f16c8ce4499c85a2622619f60b7675fa3/README.assets/ex07.png
--------------------------------------------------------------------------------
/README.assets/ex08.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DCC-Lab/RayTracing/9609b55f16c8ce4499c85a2622619f60b7675fa3/README.assets/ex08.png
--------------------------------------------------------------------------------
/README.assets/ex09.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DCC-Lab/RayTracing/9609b55f16c8ce4499c85a2622619f60b7675fa3/README.assets/ex09.png
--------------------------------------------------------------------------------
/README.assets/ex10.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DCC-Lab/RayTracing/9609b55f16c8ce4499c85a2622619f60b7675fa3/README.assets/ex10.png
--------------------------------------------------------------------------------
/README.assets/ex11.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DCC-Lab/RayTracing/9609b55f16c8ce4499c85a2622619f60b7675fa3/README.assets/ex11.png
--------------------------------------------------------------------------------
/README.assets/ex12.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DCC-Lab/RayTracing/9609b55f16c8ce4499c85a2622619f60b7675fa3/README.assets/ex12.png
--------------------------------------------------------------------------------
/README.assets/ex14.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DCC-Lab/RayTracing/9609b55f16c8ce4499c85a2622619f60b7675fa3/README.assets/ex14.png
--------------------------------------------------------------------------------
/README.assets/ex15.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DCC-Lab/RayTracing/9609b55f16c8ce4499c85a2622619f60b7675fa3/README.assets/ex15.png
--------------------------------------------------------------------------------
/README.assets/ex16.1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DCC-Lab/RayTracing/9609b55f16c8ce4499c85a2622619f60b7675fa3/README.assets/ex16.1.png
--------------------------------------------------------------------------------
/README.assets/ex16.2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DCC-Lab/RayTracing/9609b55f16c8ce4499c85a2622619f60b7675fa3/README.assets/ex16.2.png
--------------------------------------------------------------------------------
/README.assets/ex16.3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DCC-Lab/RayTracing/9609b55f16c8ce4499c85a2622619f60b7675fa3/README.assets/ex16.3.png
--------------------------------------------------------------------------------
/README.assets/ex17.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DCC-Lab/RayTracing/9609b55f16c8ce4499c85a2622619f60b7675fa3/README.assets/ex17.png
--------------------------------------------------------------------------------
/README.assets/ex18.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DCC-Lab/RayTracing/9609b55f16c8ce4499c85a2622619f60b7675fa3/README.assets/ex18.png
--------------------------------------------------------------------------------
/README.assets/hierarchy.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DCC-Lab/RayTracing/9609b55f16c8ce4499c85a2622619f60b7675fa3/README.assets/hierarchy.png
--------------------------------------------------------------------------------
/README.assets/image-20190511230415520.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DCC-Lab/RayTracing/9609b55f16c8ce4499c85a2622619f60b7675fa3/README.assets/image-20190511230415520.png
--------------------------------------------------------------------------------
/README.assets/inputProfile.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DCC-Lab/RayTracing/9609b55f16c8ce4499c85a2622619f60b7675fa3/README.assets/inputProfile.png
--------------------------------------------------------------------------------
/README.assets/kholerIllumination.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DCC-Lab/RayTracing/9609b55f16c8ce4499c85a2622619f60b7675fa3/README.assets/kholerIllumination.png
--------------------------------------------------------------------------------
/README.assets/microscopeSimple.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DCC-Lab/RayTracing/9609b55f16c8ce4499c85a2622619f60b7675fa3/README.assets/microscopeSimple.png
--------------------------------------------------------------------------------
/README.assets/outputProfile.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DCC-Lab/RayTracing/9609b55f16c8ce4499c85a2622619f60b7675fa3/README.assets/outputProfile.png
--------------------------------------------------------------------------------
/README.assets/simple.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DCC-Lab/RayTracing/9609b55f16c8ce4499c85a2622619f60b7675fa3/README.assets/simple.png
--------------------------------------------------------------------------------
/README.assets/simpleObjectImage.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DCC-Lab/RayTracing/9609b55f16c8ce4499c85a2622619f60b7675fa3/README.assets/simpleObjectImage.png
--------------------------------------------------------------------------------
/README.assets/thorlabs-0356707.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DCC-Lab/RayTracing/9609b55f16c8ce4499c85a2622619f60b7675fa3/README.assets/thorlabs-0356707.png
--------------------------------------------------------------------------------
/README.assets/thorlabs.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DCC-Lab/RayTracing/9609b55f16c8ce4499c85a2622619f60b7675fa3/README.assets/thorlabs.png
--------------------------------------------------------------------------------
/docs/FAQ.rst:
--------------------------------------------------------------------------------
1 | FAQ
2 | ========================
3 |
--------------------------------------------------------------------------------
/docs/Makefile:
--------------------------------------------------------------------------------
1 | # Minimal makefile for Sphinx documentation
2 | #
3 |
4 | # You can set these variables from the command line, and also
5 | # from the environment for the first two.
6 | SPHINXOPTS ?=
7 | SPHINXBUILD ?= sphinx-build
8 | SOURCEDIR = .
9 | BUILDDIR = _build
10 |
11 | # Put it first so that "make" without argument is like "make help".
12 | help:
13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
14 |
15 | .PHONY: help Makefile
16 |
17 | # Catch-all target: route all unknown targets to Sphinx using the new
18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
19 | %: Makefile
20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
21 |
--------------------------------------------------------------------------------
/docs/_templates/autoClass.rst:
--------------------------------------------------------------------------------
1 | {{ objname | escape | underline}}
2 |
3 | .. currentmodule:: {{ module }}
4 |
5 | .. autoclass:: {{ objname }}
6 | :no-undoc-members:
7 | :show-inheritance:
8 | :inherited-members:
9 |
10 | {% block methods %}
11 |
12 | {% if methods %}
13 | .. rubric:: Methods
14 |
15 | .. autosummary::
16 | :template: autoFunction.rst
17 | :toctree: methods/{{ objname }}
18 |
19 | {% for item in methods %}
20 | {%- if item not in inherited_members %}
21 | ~{{ name }}.{{ item }}
22 | {%- endif %}
23 | {%- endfor %}
24 |
25 | .. rubric:: Inherited Methods
26 |
27 | .. autosummary::
28 | :template: autoInheritedFunction.rst
29 | :toctree: methods/{{ objname }}
30 |
31 | {% for item in methods %}
32 | {%- if item in inherited_members %}
33 | ~{{ name }}.{{ item }}
34 | {%- endif %}
35 | {%- endfor %}
36 |
37 | {% endif %}
38 | {% endblock %}
39 |
40 | {% block attributes %}
41 | {% if attributes %}
42 | .. rubric:: Attributes
43 |
44 | .. autosummary::
45 |
46 | {% for item in attributes %}
47 | ~{{ name }}.{{ item }}
48 | {%- endfor %}
49 | {% endif %}
50 | {% endblock %}
51 |
--------------------------------------------------------------------------------
/docs/_templates/autoFunction.rst:
--------------------------------------------------------------------------------
1 | {{ objname | escape | underline}}
2 |
3 | .. autofunction:: {{ fullname }}
4 | :noindex:
5 |
6 |
--------------------------------------------------------------------------------
/docs/_templates/autoInheritedFunction.rst:
--------------------------------------------------------------------------------
1 | {{ objname | escape | underline}}
2 |
3 | .. autofunction:: {{ fullname }}
4 | :noindex:
5 |
--------------------------------------------------------------------------------
/docs/_templates/classDefault.rst:
--------------------------------------------------------------------------------
1 | {{ fullname | escape | underline}}
2 |
3 | .. currentmodule:: {{ module }}
4 |
5 | .. autoclass:: {{ objname }}
6 |
7 | {% block methods %}
8 | .. automethod:: __init__
9 |
10 | {% if methods %}
11 | .. rubric:: Methods
12 |
13 | .. autosummary::
14 | {% for item in methods %}
15 | ~{{ name }}.{{ item }}
16 | {%- endfor %}
17 | {% endif %}
18 | {% endblock %}
19 |
20 | {% block attributes %}
21 | {% if attributes %}
22 | .. rubric:: Attributes
23 |
24 | .. autosummary::
25 | {% for item in attributes %}
26 | ~{{ name }}.{{ item }}
27 | {%- endfor %}
28 | {% endif %}
29 | {% endblock %}
30 |
--------------------------------------------------------------------------------
/docs/_templates/moduleDefault.rst:
--------------------------------------------------------------------------------
1 | {{ fullname | escape | underline}}
2 |
3 | .. automodule:: {{ fullname }}
4 |
5 | {% block functions %}
6 | {% if functions %}
7 | .. rubric:: Functions
8 |
9 | .. autosummary::
10 | {% for item in functions %}
11 | {{ item }}
12 | {%- endfor %}
13 | {% endif %}
14 | {% endblock %}
15 |
16 | {% block classes %}
17 | {% if classes %}
18 | .. rubric:: Classes
19 |
20 | .. autosummary::
21 | {% for item in classes %}
22 | {{ item }}
23 | {%- endfor %}
24 | {% endif %}
25 | {% endblock %}
26 |
27 | {% block exceptions %}
28 | {% if exceptions %}
29 | .. rubric:: Exceptions
30 |
31 | .. autosummary::
32 | {% for item in exceptions %}
33 | {{ item }}
34 | {%- endfor %}
35 | {% endif %}
36 | {% endblock %}
37 |
--------------------------------------------------------------------------------
/docs/conf.py:
--------------------------------------------------------------------------------
1 | # Configuration file for the Sphinx documentation builder.
2 | #
3 | # This file only contains a selection of the most common options. For a full
4 | # list see the documentation:
5 | # https://www.sphinx-doc.org/en/master/usage/configuration.html
6 |
7 | # -- Path setup --------------------------------------------------------------
8 |
9 | # If extensions (or modules to document with autodoc) are in another directory,
10 | # add these directories to sys.path here. If the directory is relative to the
11 | # documentation root, use os.path.abspath to make it absolute, like shown here.
12 |
13 | import os
14 | import sys
15 | import sphinx_rtd_theme
16 | sys.path.insert(0, os.path.abspath('.'))
17 | sys.path.insert(0, os.path.abspath('..'))
18 | # -- Project information -----------------------------------------------------
19 |
20 | project = 'RayTracing'
21 | copyright = '2020, DCC-Lab'
22 | author = 'DCC-Lab'
23 |
24 |
25 | # -- General configuration ---------------------------------------------------
26 | autosummary_generate = True
27 |
28 | # Add any Sphinx extension module names here, as strings. They can be
29 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
30 | # ones.
31 | extensions = ['sphinx.ext.autodoc', 'sphinx.ext.autosummary', 'sphinx.ext.napoleon', 'recommonmark', 'sphinx_rtd_theme']
32 | autodoc_default_flags = ['members', 'show-inheritance']
33 | autodoc_default_options = {
34 | "members": True,
35 | "inherited-members": False,
36 | "show-inheritance": True,
37 | }
38 |
39 | # Add any paths that contain templates here, relative to this directory.
40 | templates_path = ['_templates']
41 |
42 | # List of patterns, relative to source directory, that match files and
43 | # directories to ignore when looking for source files.
44 | # This pattern also affects html_static_path and html_extra_path.
45 | exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
46 |
47 |
48 | # -- Options for HTML output -------------------------------------------------
49 |
50 | # The theme to use for HTML and HTML Help pages. See the documentation for
51 | # a list of builtin themes.
52 | html_theme = 'sphinx_rtd_theme'
53 |
54 | # Add any paths that contain custom static files (such as style sheets) here,
55 | # relative to this directory. They are copied after the builtin static files,
56 | # so a file named "default.css" will overwrite the builtin "default.css".
57 | html_static_path = ['_static']
58 |
59 | # explicit master doc for ReadTheDocs build
60 | master_doc = 'index'
61 |
--------------------------------------------------------------------------------
/docs/contribute.md:
--------------------------------------------------------------------------------
1 | # Contribute
2 |
3 | ## Bug reporting
4 |
5 | If you found a bug and your code does not work you can follow these steps to report that:
6 |
7 |
8 |
9 | 1. Make sure you are using the latest version of the raytracing
10 | `pip install --upgrade raytracing`
11 |
12 |
13 |
14 | 2. Search for similar issues among the [Raytracing GitHub issues](https://github.com/DCC-Lab/RayTracing/issues).
15 |
16 |
17 |
18 | 3. Not solved? Open an issue on the [Raytracing GitHub](https://github.com/DCC-Lab/RayTracing/issues).
19 | Provide useful information: OS? Python version?
20 |
21 |
22 |
23 | 4. Explain your problem clearly: What is the error? Where is the problem?
24 | (Make sure to include a test code that shows the error)
25 |
26 |
27 |
28 | 5. Still having a problem? [e-mail](mailto:dccote%40cervo.ulaval.ca?subject=RaytracingPythonModule) us!
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/docs/images/AngleHist.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DCC-Lab/RayTracing/9609b55f16c8ce4499c85a2622619f60b7675fa3/docs/images/AngleHist.png
--------------------------------------------------------------------------------
/docs/images/Histogram.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DCC-Lab/RayTracing/9609b55f16c8ce4499c85a2622619f60b7675fa3/docs/images/Histogram.png
--------------------------------------------------------------------------------
/docs/images/ImagingPath.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DCC-Lab/RayTracing/9609b55f16c8ce4499c85a2622619f60b7675fa3/docs/images/ImagingPath.png
--------------------------------------------------------------------------------
/docs/images/LambertianRays.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DCC-Lab/RayTracing/9609b55f16c8ce4499c85a2622619f60b7675fa3/docs/images/LambertianRays.png
--------------------------------------------------------------------------------
/docs/images/RandomLambertianRays.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DCC-Lab/RayTracing/9609b55f16c8ce4499c85a2622619f60b7675fa3/docs/images/RandomLambertianRays.png
--------------------------------------------------------------------------------
/docs/images/RandomUniformRays.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DCC-Lab/RayTracing/9609b55f16c8ce4499c85a2622619f60b7675fa3/docs/images/RandomUniformRays.png
--------------------------------------------------------------------------------
/docs/images/UniformRays.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DCC-Lab/RayTracing/9609b55f16c8ce4499c85a2622619f60b7675fa3/docs/images/UniformRays.png
--------------------------------------------------------------------------------
/docs/images/apertureStop.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DCC-Lab/RayTracing/9609b55f16c8ce4499c85a2622619f60b7675fa3/docs/images/apertureStop.png
--------------------------------------------------------------------------------
/docs/images/displayRays.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DCC-Lab/RayTracing/9609b55f16c8ce4499c85a2622619f60b7675fa3/docs/images/displayRays.png
--------------------------------------------------------------------------------
/docs/images/flipOrientation_after.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DCC-Lab/RayTracing/9609b55f16c8ce4499c85a2622619f60b7675fa3/docs/images/flipOrientation_after.png
--------------------------------------------------------------------------------
/docs/images/flipOrientation_before.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DCC-Lab/RayTracing/9609b55f16c8ce4499c85a2622619f60b7675fa3/docs/images/flipOrientation_before.png
--------------------------------------------------------------------------------
/docs/images/matrixGraphicDisplay.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DCC-Lab/RayTracing/9609b55f16c8ce4499c85a2622619f60b7675fa3/docs/images/matrixGraphicDisplay.png
--------------------------------------------------------------------------------
/docs/index.rst:
--------------------------------------------------------------------------------
1 | .. raytracing documentation master file, created by
2 | sphinx-quickstart on Wed May 13 15:07:24 2020.
3 | You can adapt this file completely to your liking, but it should at least
4 | contain the root `toctree` directive.
5 |
6 | RayTracing
7 | ======================================
8 | This code aims to provide a simple ray tracing module for calculating various
9 | properties of optical paths (object, image, aperture stops, field stops). It
10 | makes use of ABCD matrices and does not consider aberrations (spherical or
11 | chromatic). Since it uses the ABCD formalism (or Ray matrices, or Gauss
12 | matrices) it can perform tracing of rays and gaussian laser beams.
13 |
14 | A tutorial was published in the journal Neurophotonics. If you use this RayTracing module, please cite:
15 |
16 | **"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. https://doi.org/10.1117/1.NPh.8.1.010801
17 |
18 | This package is developed by `DCClab `_ members. The many contributions cannot be described in all their details, but a list of contributors is provided here in no particular order:
19 |
20 | * Elahe Parham: Documentation and examples writing
21 | * Shadi Masoumi: Tutorials, Examples and usage
22 | * Valérie Pineau Noël: Tutorials, Examples and usage
23 | * Gabriel Genest: Extensive Unit Testing and BugFinder Extraordinaire
24 | * Ludovick Bégin: Layout Artist Class Designer
25 | * Francois Côté: Bug finding, layout and Bob Ross look-alike
26 | * Mathieu Fournier: Unit Testing
27 | * Marc-André Vigneault: Examples
28 | * Daniel Côté: Official Designer & Merger, and "Gunnery Sergeant Hartman" Impersonator
29 |
30 |
31 | Contents
32 | ^^^^^^^^
33 |
34 | .. toctree::
35 | :maxdepth: 1
36 |
37 | raytracing
38 | gettingStarted
39 | reference
40 | examples
41 | contribute
42 | FAQ
43 |
44 | Indices and tables
45 | ^^^^^^^^^^^^^^^^^^
46 |
47 | * :ref:`genindex`
48 | * :ref:`modindex`
49 | * :ref:`search`
50 |
--------------------------------------------------------------------------------
/docs/make.bat:
--------------------------------------------------------------------------------
1 | @ECHO OFF
2 |
3 | pushd %~dp0
4 |
5 | REM Command file for Sphinx documentation
6 |
7 | if "%SPHINXBUILD%" == "" (
8 | set SPHINXBUILD=sphinx-build
9 | )
10 | set SOURCEDIR=.
11 | set BUILDDIR=_build
12 |
13 | if "%1" == "" goto help
14 |
15 | %SPHINXBUILD% >NUL 2>NUL
16 | if errorlevel 9009 (
17 | echo.
18 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
19 | echo.installed, then set the SPHINXBUILD environment variable to point
20 | echo.to the full path of the 'sphinx-build' executable. Alternatively you
21 | echo.may add the Sphinx directory to PATH.
22 | echo.
23 | echo.If you don't have Sphinx installed, grab it from
24 | echo.http://sphinx-doc.org/
25 | exit /b 1
26 | )
27 |
28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
29 | goto end
30 |
31 | :help
32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
33 |
34 | :end
35 | popd
36 |
--------------------------------------------------------------------------------
/docs/raytracing.rst:
--------------------------------------------------------------------------------
1 | RayTracing
2 | ========================
3 |
4 |
5 | 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 aberrations (spherical or chromatic). Since it uses the ABCD formalism (or Ray matrices, or Gauss matrices) it can perform tracing of rays and gaussian laser beams.
6 |
7 | It is not a package to do "Rendering in 3D with raytracing".
8 |
9 | The code has been developed first for teaching purposes and is used in my "`Optique `_
10 | " Study Notes (french only), but also for actual use in my research. There are `tutorials `_ (in french, with English subtitles) on YouTube. I have made no attempts at making high-performance code. **Readability** and **simplicity of usage** are the key here. It is a module with only a few files, and only matplotlib as a dependent module.
11 |
12 | 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.
13 |
14 | 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 is not available in ``LaserPath``.
15 |
16 | by [Daniel Côté](mailto:dccote@cervo.ulaval.ca?subject=Raytracing python module)
--------------------------------------------------------------------------------
/docs/reference.rst:
--------------------------------------------------------------------------------
1 | RayTracing API Reference
2 | ========================
3 | .. automodule:: raytracing
4 |
5 | Matrix
6 | ------------------
7 |
8 | .. autosummary::
9 | :template: autoClass.rst
10 | :toctree: modules
11 |
12 | Matrix
13 | Aperture
14 | CurvedMirror
15 | DielectricInterface
16 | DielectricSlab
17 | Lens
18 | Space
19 | ThickLens
20 |
21 | MatrixGroup
22 | ------------------
23 |
24 | .. autosummary::
25 | :template: autoClass.rst
26 | :toctree: modules
27 |
28 | MatrixGroup
29 |
30 | ImagingPath
31 | ------------------
32 |
33 | .. autosummary::
34 | :template: autoClass.rst
35 | :toctree: modules
36 |
37 | ImagingPath
38 |
39 | Ray
40 | ------------------
41 |
42 | .. autosummary::
43 | :template: autoClass.rst
44 | :toctree: modules
45 |
46 | Ray
47 |
48 | Rays
49 | ------------------
50 |
51 | .. autosummary::
52 | :template: autoClass.rst
53 | :toctree: modules
54 |
55 | Rays
56 | ObjectRays
57 | UniformRays
58 | RandomRays
59 | LambertianRays
60 | RandomLambertianRays
61 | RandomUniformRays
62 |
63 | Laser Path
64 | ------------------
65 |
66 | .. autosummary::
67 | :template: autoClass.rst
68 | :toctree: modules
69 |
70 | LaserPath
71 |
72 | Gaussian Beam
73 | ------------------
74 |
75 | .. autosummary::
76 | :template: autoClass.rst
77 | :toctree: modules
78 |
79 | GaussianBeam
80 |
81 | Components
82 | ------------------
83 |
84 | .. autosummary::
85 | :template: autoClass.rst
86 | :toctree: modules
87 |
88 | System2f
89 | System4f
90 |
91 | Axicon
92 | ------------------
93 |
94 | .. autosummary::
95 | :template: autoClass.rst
96 | :toctree: modules
97 |
98 | Axicon
99 |
100 | Specialty Lenses
101 | ------------------
102 |
103 | .. autosummary::
104 | :template: autoClass.rst
105 | :toctree: modules
106 |
107 | AchromatDoubletLens
108 | Objective
109 |
110 | Olympus
111 | ------------------
112 |
113 | .. autosummary::
114 | :template: autoClass.rst
115 | :toctree: modules
116 |
117 | olympus.LUMPlanFL40X
118 | olympus.XLUMPlanFLN20X
119 | olympus.MVPlapo2XC
120 | olympus.UMPLFN20XW
121 | olympus.XLPLN25X
122 |
123 | Edmund Optics
124 | ------------------
125 |
126 | .. autosummary::
127 | :template: autoClass.rst
128 | :toctree: modules
129 |
130 | eo.PN_33_921
131 | eo.PN_33_922
132 | eo.PN_88_593
133 | eo.PN_85_877
134 |
135 | Nikon
136 | ------------------
137 |
138 | .. autosummary::
139 | :template: autoClass.rst
140 | :toctree: modules
141 |
142 | nikon.LWD16X
143 |
144 | Thorlabs
145 | ------------------
146 |
147 | .. autosummary::
148 | :template: autoClass.rst
149 | :toctree: modules
150 |
151 | thorlabs.ACN254_100_A
152 | thorlabs.ACN254_075_A
153 | thorlabs.ACN254_050_A
154 | thorlabs.ACN254_040_A
155 | thorlabs.AC254_030_A
156 | thorlabs.AC254_035_A
157 | thorlabs.AC254_040_A
158 | thorlabs.AC254_045_A
159 | thorlabs.AC254_050_A
160 | thorlabs.AC254_060_A
161 | thorlabs.AC254_075_A
162 | thorlabs.AC254_080_A
163 | thorlabs.AC254_100_A
164 | thorlabs.AC254_125_A
165 | thorlabs.AC254_150_A
166 | thorlabs.AC254_200_A
167 | thorlabs.AC254_250_A
168 | thorlabs.AC254_300_A
169 | thorlabs.AC254_400_A
170 | thorlabs.AC254_500_A
171 | thorlabs.AC254_050_B
172 | thorlabs.AC508_075_B
173 | thorlabs.AC508_080_B
174 | thorlabs.AC508_100_B
175 | thorlabs.AC508_150_B
176 | thorlabs.AC508_200_B
177 | thorlabs.AC508_250_B
178 | thorlabs.AC508_300_B
179 | thorlabs.AC508_400_B
180 | thorlabs.AC508_500_B
181 | thorlabs.AC508_750_B
182 | thorlabs.AC508_1000_B
183 | thorlabs.LA1608_A
184 | thorlabs.LA1134_A
185 | thorlabs.LA1131_A
186 | thorlabs.LA1422_A
187 | thorlabs.LA1805_A
188 | thorlabs.LA1274_A
189 |
190 | Materials
191 | ------------------
192 |
193 | .. autosummary::
194 | :template: autoClass.rst
195 | :toctree: modules
196 |
197 | Material
198 | Air
199 | N_BK7
200 | N_SF2
201 | SF2
202 | SF5
203 | N_SF5
204 | N_SF6HT
205 | N_SF10
206 | N_SF11
207 | N_BAF10
208 | E_BAF11
209 | N_BAK1
210 | N_BAK4
211 | FK51A
212 | LAFN7
213 | N_LASF9
214 | N_LAK22
215 | N_SSK5
216 | E_FD10
217 | FusedSilica
218 |
219 | Zemax reader
220 | ------------------
221 |
222 | .. autosummary::
223 | :template: autoClass.rst
224 | :toctree: modules
225 |
226 | ZMXReader
--------------------------------------------------------------------------------
/docs/requirements.txt:
--------------------------------------------------------------------------------
1 | sphinx_rtd_theme==0.4.3
2 |
--------------------------------------------------------------------------------
/docs/webgraphviz_url/site.js:
--------------------------------------------------------------------------------
1 | // Delay loading any function until the html dom has loaded. All functions are
2 | // defined in this top level function to ensure private scope.
3 | jQuery(document).ready(function () {
4 |
5 | // Installs error handling.
6 | jQuery.ajaxSetup({
7 | error: function(resp, e) {
8 | if (resp.status == 0){
9 | alert('You are offline!!\n Please Check Your Network.');
10 | } else if (resp.status == 404){
11 | alert('Requested URL not found.');
12 | } else if (resp.status == 500){
13 | alert('Internel Server Error:\n\t' + resp.responseText);
14 | } else if (e == 'parsererror') {
15 | alert('Error.\nParsing JSON Request failed.');
16 | } else if (e == 'timeout') {
17 | alert('Request timeout.');
18 | } else {
19 | alert('Unknown Error.\n' + resp.responseText);
20 | }
21 | }
22 | }); // error:function()
23 |
24 |
25 | var generate_btn = jQuery('#generate_btn');
26 | var sample_1_btn = jQuery('#sample_1_btn');
27 | var sample_2_btn = jQuery('#sample_2_btn');
28 | var sample_3_btn = jQuery('#sample_3_btn');
29 | var sample_4_btn = jQuery('#sample_4_btn');
30 | var sample_5_btn = jQuery('#sample_5_btn');
31 |
32 | var svg_div = jQuery('#graphviz_svg_div');
33 | var graphviz_data_textarea = jQuery('#graphviz_data');
34 |
35 | function InsertGraphvizText(text) {
36 | graphviz_data_textarea.val(text);
37 | }
38 |
39 |
40 | function UpdateGraphviz() {
41 | svg_div.html("");
42 | var data = graphviz_data_textarea.val();
43 | // Generate the Visualization of the Graph into "svg".
44 | var svg = Viz(data, "svg");
45 | svg_div.html("
"+svg);
46 | }
47 |
48 | // Startup function: call UpdateGraphviz
49 | jQuery(function() {
50 | // The buttons are disabled, enable them now that this script
51 | // has loaded.
52 | generate_btn.removeAttr("disabled")
53 | .text("Generate Graph!");
54 |
55 | sample_1_btn.removeAttr("disabled");
56 | sample_2_btn.removeAttr("disabled");
57 | sample_3_btn.removeAttr("disabled");
58 | sample_4_btn.removeAttr("disabled");
59 | sample_5_btn.removeAttr("disabled");
60 | });
61 |
62 | // Bind actions to form buttons.
63 | generate_btn.click(UpdateGraphviz);
64 |
65 | sample_1_btn.click(function(){
66 | InsertGraphvizText(jQuery("#sample1_text").html().trim());
67 | });
68 |
69 | sample_2_btn.click(function(){
70 | InsertGraphvizText(jQuery("#sample2_text").html().trim());
71 | });
72 |
73 | sample_3_btn.click(function(){
74 | InsertGraphvizText(jQuery("#sample3_text").html().trim());
75 | });
76 |
77 | sample_4_btn.click(function(){
78 | InsertGraphvizText(jQuery("#sample4_text").html().trim());
79 | });
80 |
81 | sample_5_btn.click(function(){
82 | InsertGraphvizText(jQuery("#sample5_text").html().trim());
83 | });
84 |
85 | });
86 |
--------------------------------------------------------------------------------
/examples/README.md:
--------------------------------------------------------------------------------
1 | # Examples (moved)
2 |
3 | by Prof. [Daniel Côté](mailto:dccote@cervo.ulaval.ca?subject=Raytracing%20python%20module) and his group http://www.dcclab.ca
4 |
5 | The publication:
6 |
7 | > ["Tools and tutorial on practical ray tracing for microscopy"](https://doi.org/10.1117/1.NPh.8.1.010801)
8 | >
9 | > by V. Pineau Noël*, S. Masoumi*, E. Parham*, G. Genest, L. Bégin, M.-A. Vigneault, D. C. Côté,
10 | > Neurophotonics, 8(1), 010801 (2021).
11 | > *Equal contributions.
12 | > Permalink: https://doi.org/10.1117/1.NPh.8.1.010801
13 |
14 | has a link to this directory in its text, but all examples have been moved inside the module and can be found [here](../raytracing/examples/).
15 |
16 |
--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------
1 | # pyproject.toml is the newer method for module distribution
2 | # setup.py is not needed, you simply do python -m build
3 | #
4 | # To distribute:
5 | # =============
6 | # rm dist/*; python -m build; python -m twine upload dist/*
7 |
8 | [build-system]
9 | requires = ["setuptools>=61", "setuptools_scm>=7", "wheel"]
10 | build-backend = "setuptools.build_meta"
11 |
12 | [project]
13 | name = "raytracing"
14 | #
15 | # Version __version__ will be loaded by setuptools_scm using a git tag
16 | # This requires a git tag in the form of v.1.3.13
17 | dynamic = ["version"]
18 | description = "Simple optical ray tracing library to validate the design of an optical system."
19 | readme = { file = "README.md", content-type = "text/markdown" }
20 | requires-python = ">=3.6"
21 | license = { text = "MIT" }
22 |
23 | authors = [
24 | { name = "Daniel Cote", email = "dccote@cervo.ulaval.ca" }
25 | ]
26 |
27 | urls = { Homepage = "https://github.com/DCC-Lab/RayTracing" }
28 |
29 | keywords = [
30 | "optics", "optical", "lens", "ray tracing", "matrix", "matrices", "aperture", "field stop",
31 | "monte carlo", "design", "raytracing", "zemax", "chromatic aberrations"
32 | ]
33 |
34 | classifiers = [
35 | "Development Status :: 5 - Production/Stable",
36 | "Intended Audience :: Science/Research",
37 | "Intended Audience :: Education",
38 | "Topic :: Scientific/Engineering :: Physics",
39 | "Topic :: Scientific/Engineering :: Visualization",
40 | "Topic :: Education",
41 | "License :: OSI Approved :: MIT License",
42 | "Programming Language :: Python :: 3",
43 | "Programming Language :: Python :: 3.6",
44 | "Programming Language :: Python :: 3.7",
45 | "Programming Language :: Python :: 3.8",
46 | "Programming Language :: Python :: 3.9",
47 | "Programming Language :: Python :: 3.10",
48 | "Programming Language :: Python :: 3.11",
49 | "Programming Language :: Python :: 3.12",
50 | "Programming Language :: Python :: 3.13",
51 | "Operating System :: OS Independent"
52 | ]
53 |
54 | dependencies = [
55 | "matplotlib>=3.7",
56 | "numpy",
57 | "pygments",
58 | "mytk"
59 | ]
60 |
61 | [tool.setuptools_scm]
62 | version_scheme = "post-release"
63 | # This is apparently needed because ptyhon -m build loses the .git tags
64 | write_to = "raytracing/_version.py"
65 |
66 | [tool.setuptools]
67 | package-dir = {"" = "."}
68 | include-package-data = true
69 |
70 | [tool.setuptools.packages.find]
71 | where = ["."]
72 | include = ["raytracing*"]
73 |
74 | [tool.setuptools.package-data]
75 | raytracing = ["*.png"]
76 |
--------------------------------------------------------------------------------
/raytracing/__init__.py:
--------------------------------------------------------------------------------
1 | r"""A simple module for ray tracing with ABCD matrices.
2 | https://github.com/DCC-Lab/RayTracing
3 |
4 | Create an `ImagingPath()`, append matrices (optical elements or other
5 | group of elements), and then `display()`. This helps determine of
6 | course simple things like focal length of compound systems,
7 | object-image, etc... but also the aperture stop, field stop, field
8 | of view and any clipping issues that may occur.
9 |
10 | When displaying the result with an `ImagingPath()`, the `objectHeight`,
11 | `fanAngle`, and `fanNumber` are used if the field of view is not
12 | defined. You may adjust the values to suit your needs in `ImagingPath()`.
13 |
14 | Create a `LaserPath()` to analyse gaussian beams using the complex radius
15 | of curvature q and the same matrices.
16 |
17 | The class hierarchy can be obtained with `python -m raytracing --classes`
18 | """
19 |
20 | import math
21 |
22 | """ We import almost everything by default, in the general namespace because it is simpler for everyone """
23 |
24 | """ General matrices and groups for tracing rays and gaussian beams"""
25 | from .matrix import *
26 | from .matrixgroup import *
27 |
28 | """ Ray matrices for geometrical optics """
29 | from .ray import *
30 | from .rays import *
31 | from .imagingpath import *
32 |
33 | """ ABCD matrices for gaussian beams """
34 | from .gaussianbeam import *
35 | from .laserpath import *
36 | from .lasercavity import *
37 |
38 | """ Matrices for components: System4f (synonym: Telescope), System2f """
39 | from .components import *
40 |
41 | """ Specialty lenses : objectives and achromats, but we keep the namespace for the vendor lenses """
42 | from .specialtylenses import *
43 | from .axicon import *
44 |
45 | from . import thorlabs
46 | from . import eo
47 | from . import olympus
48 |
49 | from .zemax import *
50 |
51 | from .utils import *
52 | from .preferences import *
53 |
54 | import os
55 | from datetime import datetime
56 |
57 | """ Synonym of Matrix: Element
58 |
59 | We can use a mathematical language (Matrix) or optics terms (Element)
60 | """
61 | Element = Matrix
62 | Group = MatrixGroup
63 | OpticalPath = ImagingPath
64 |
65 | try: # to get __version__ dynamically from pyproject.toml
66 | from importlib.metadata import version
67 | except ImportError: # Python <3.8
68 | from importlib_metadata import version
69 |
70 | from ._version import version as __version__
71 |
72 | __author__ = "Daniel Cote "
73 |
74 | import os.path as path
75 | import time
76 | import tempfile
77 |
78 |
79 | def lastCheckMoreThanADay():
80 | if "lastVersionCheck" in prefs:
81 | isoFormat = "%Y-%m-%dT%H:%M:%S.%f"
82 | then = datetime.strptime(prefs["lastVersionCheck"], isoFormat)
83 | difference = datetime.now() - then
84 | if difference.days > 1:
85 | return True
86 | else:
87 | return False
88 | else:
89 | return True
90 |
91 |
92 | prefs = Preferences()
93 | if lastCheckMoreThanADay():
94 | checkLatestVersion(currentVersion=__version__)
95 | prefs["lastVersionCheck"] = datetime.now().isoformat()
96 |
97 | if "RAYTRACING_EXPERT" in os.environ:
98 | prefs["mode"] = "expert"
99 |
100 | try:
101 | if "mode" in prefs:
102 | if prefs["mode"] == "silent":
103 | silentMode()
104 | elif prefs["mode"] == "expert":
105 | expertMode()
106 | else:
107 | beginnerMode(saveToPrefs=False)
108 | else:
109 | beginnerMode(saveToPrefs=True)
110 |
111 | except Exception as err:
112 | beginnerMode(saveToPrefs=True)
113 | pass
114 |
--------------------------------------------------------------------------------
/raytracing/__main__.py:
--------------------------------------------------------------------------------
1 | from .imagingpath import *
2 | from .laserpath import *
3 | from .lasercavity import *
4 |
5 | from .specialtylenses import *
6 | from .axicon import *
7 | from . import thorlabs
8 | from . import eo
9 | from . import olympus
10 | from . import utils
11 |
12 | import os
13 | import sys
14 | import argparse
15 | import re
16 |
17 | from io import BytesIO
18 | from PIL import Image
19 | import matplotlib.pyplot as plt
20 | from . import examples # 'all' will gather all example files dynamically
21 | import subprocess
22 |
23 | # We start by figuring out what the user really wants. If they don't know,
24 | # we offer some help
25 | ap = argparse.ArgumentParser(prog="python -m raytracing")
26 | ap.add_argument(
27 | "-a",
28 | "--app",
29 | action="store_true",
30 | help="Start the graphical user interface",
31 | )
32 | ap.add_argument(
33 | "-e",
34 | "--examples",
35 | required=False,
36 | default="all",
37 | help="Specific example numbers, separated by a comma",
38 | )
39 | ap.add_argument(
40 | "-c",
41 | "--classes",
42 | required=False,
43 | action="store_true",
44 | help="Print the class hierarchy in graphviz format",
45 | )
46 | ap.add_argument(
47 | "-l",
48 | "--list",
49 | required=False,
50 | action="store_const",
51 | const=True,
52 | help="List all the accessible examples",
53 | )
54 | ap.add_argument(
55 | "-t",
56 | "--tests",
57 | required=False,
58 | action="store_true",
59 | help="Run all Unit tests",
60 | )
61 |
62 | args = vars(ap.parse_args())
63 | runApp = args["app"]
64 | runExamples = args["examples"]
65 | runTests = args["tests"]
66 | printClasses = args["classes"]
67 | listExamples = args["list"]
68 |
69 | if runApp:
70 | # Build path to gui_app.py
71 | current_dir = os.path.dirname(__file__)
72 | gui_path = os.path.join(current_dir, "ui", "raytracing_app.py")
73 |
74 | # Call the GUI app as a subprocess
75 | subprocess.run([sys.executable, gui_path])
76 | exit()
77 |
78 | if runExamples == "all":
79 | runExamples = range(1, len(examples.short) + 1)
80 | elif runExamples == "":
81 | runExamples = []
82 | else:
83 | runExamples = [int(y) for y in runExamples.split(",")]
84 |
85 | if printClasses:
86 | printClassHierarchy(Rays)
87 | printClassHierarchy(Matrix)
88 | elif runTests:
89 | moduleDir = os.path.dirname(os.path.realpath(__file__))
90 | err = os.system("cd {0}/tests; {1} -m unittest".format(moduleDir, sys.executable))
91 | elif listExamples:
92 | topDir = os.path.dirname(os.path.realpath(examples.__file__))
93 | all = []
94 | all.extend(examples.short)
95 | all.extend(examples.short)
96 |
97 | print("\nAll examples")
98 | print("==============")
99 | for i, entry in enumerate(examples.short):
100 | print("{0:2d}. {1}.py {2}".format(i + 1, entry["name"], entry["title"]))
101 |
102 | print("\nMore examples code available in: {0}".format(topDir))
103 | elif runExamples:
104 | # Some decent parameters for plots
105 | # See https://matplotlib.org/api/font_manager_api.html#matplotlib.font_manager.FontProperties.set_size
106 | params = {
107 | "legend.fontsize": "x-large",
108 | "figure.figsize": (10, 7),
109 | "axes.labelsize": "x-large",
110 | "axes.titlesize": "x-large",
111 | "xtick.labelsize": "x-large",
112 | "ytick.labelsize": "x-large",
113 | "font.family": "helvetica",
114 | }
115 | plt.rcParams.update(params)
116 |
117 | print("Running example code : {0}".format(runExamples))
118 | for i in runExamples:
119 | entry = examples.short[i - 1]
120 | print("\nScript '{0}.py' - begin source code".format(entry["name"]))
121 | print(entry["terminalSourceCode"], end="")
122 | print("\nScript '{0}.py' - end source code".format(entry["name"]))
123 | print("\nScript '{0}.py' - begin output".format(entry["name"]))
124 | entry["code"](comments=entry["bmpSourceCode"])
125 | print("Script '{0}.py' - end output".format(entry["name"]))
126 |
--------------------------------------------------------------------------------
/raytracing/axicon.py:
--------------------------------------------------------------------------------
1 | from .matrix import *
2 | import matplotlib.pyplot as plt
3 |
4 |
5 | class Axicon(Matrix):
6 | """
7 | This class is an advanced module that describes an axicon lens,
8 | not part of the basic formalism. Using this class an axicon
9 | conical lens can be presented.
10 | Axicon lenses are used to obtain a line focus instead of a point.
11 | The formalism is described in Kloos, sec. 2.2.3
12 |
13 | Parameters
14 | ----------
15 | alpha : float
16 | alpha is the small angle in radians of the axicon
17 | (typically 2.5 or 5 degrees) corresponding to 90-apex angle
18 | n : float
19 | index of refraction.
20 | This value cannot be less than 1.0. It is assumed the axicon
21 | is in air.
22 | diameter : float
23 | Aperture of the element. (default = +Inf)
24 | The diameter of the aperture must be a positive value.
25 | label : string
26 | The label of the axicon lens.
27 |
28 | """
29 |
30 | def __init__(self, alpha, n, diameter=float('+Inf'), label=''):
31 |
32 | self.n = n
33 | self.alpha = alpha
34 | super(Axicon, self).__init__(A=1, B=0, C=0, D=1, physicalLength=0, apertureDiameter=diameter, label=label,
35 | frontIndex=1.0, backIndex=1.0)
36 |
37 | def deviationAngle(self):
38 | """ This function provides deviation angle delta assuming that
39 | the axicon is in air and that the incidence is near normal,
40 | which is the usual way of using an axicon.
41 |
42 | Returns
43 | -------
44 | delta : float
45 | the deviation angle
46 |
47 | See ALso
48 | --------
49 | https://ru.b-ok2.org/book/2482970/9062b7, p.48
50 |
51 | """
52 |
53 | return (self.n - 1.0) * self.alpha
54 |
55 | def focalLineLength(self, yMax=None):
56 | """ Provides the line length, assuming a ray at height yMax
57 |
58 | Parameters
59 | ----------
60 | yMax : float
61 | the height of the ray (default=None)
62 | If no height is defined for the ray, then yMax would be set to the height of the axicon (apertureDiameter/2)
63 |
64 | Returns
65 | -------
66 | focalLineLength : float
67 | the length of the focal line
68 |
69 | See ALso
70 | --------
71 | https://ru.b-ok2.org/book/2482970/9062b7, p.48
72 |
73 | """
74 |
75 | if yMax == None:
76 | yMax = self.apertureDiameter / 2
77 |
78 | return abs(yMax) / (self.n - 1.0) / self.alpha
79 |
80 | def mul_ray(self, rightSideRay):
81 | """ This function is used to calculate the output ray through an axicon.
82 |
83 | Parameters
84 | ----------
85 | rightSideRay : object of ray class
86 | A ray with a defined height and angle.
87 |
88 | Returns
89 | -------
90 | outputRay : object of ray class
91 | the height and angle of the output ray.
92 |
93 | See Also
94 | --------
95 | raytracing.Matrix.mul_ray
96 |
97 |
98 | """
99 |
100 | outputRay = super(Axicon, self).mul_ray(rightSideRay)
101 |
102 | if rightSideRay.y > 0:
103 | outputRay.theta += -self.deviationAngle()
104 | elif rightSideRay.y < 0:
105 | outputRay.theta += self.deviationAngle()
106 | # theta == 0 is not deviated
107 |
108 | return outputRay
109 |
110 | def mul_beam(self, rightSideBeam):
111 | """This function calculates the multiplication of a coherent beam with complex radius
112 | of curvature q by an ABCD matrix. However it will raise an error in case the input is an axicon
113 |
114 | Parameters
115 | ----------
116 | rightSideBeam : object from GaussianBeam class
117 | including the beam properties
118 |
119 |
120 | Returns
121 | -------
122 | outputBeam : object from GaussianBeam class
123 | The properties of the beam at the output of the system with the defined ABCD matrix
124 |
125 | See Also
126 | --------
127 | raytracing.Matrix.mul_matrix
128 | raytracing.Matrix.mul_ray
129 | raytracing.GaussianBeam
130 | """
131 |
132 | raise TypeError("Cannot use Axicon with GaussianBeam, only with Ray")
133 |
134 | @property
135 | def forwardSurfaces(self):
136 | """ A list of surfaces that represents the element for drawing purposes
137 | """
138 | minThickness = - np.tan(self.alpha) * self.displayHalfHeight()
139 | return [FlatInterface(n=self.n, L=minThickness),
140 | ConicalInterface(alpha=self.alpha)]
141 |
--------------------------------------------------------------------------------
/raytracing/components.py:
--------------------------------------------------------------------------------
1 | from .matrix import *
2 | from .matrixgroup import *
3 | from math import *
4 |
5 | class System4f(MatrixGroup):
6 | """
7 | The matrix group of a 4f system can be defined using this function.
8 |
9 | Parameters
10 | ----------
11 | f1 : float
12 | The focal length of the first lens
13 | f2 : float
14 | The focal length of the second lens
15 | diameter1 : float
16 | The diameter of the first lens. This value must be positive. (default=+Inf)
17 | diameter2 : float
18 | The diameter of the second lens. This value must be positive. (default=+Inf)
19 | label : string
20 | The label for the 4f system
21 |
22 | """
23 | def __init__(self,f1, f2, diameter1=float('+Inf'), diameter2=float('+Inf'), label=''):
24 | elements = []
25 | elements.append(Space(d=f1))
26 | elements.append(Lens(f=f1, diameter=diameter1))
27 | elements.append(Space(d=f1))
28 | elements.append(Space(d=f2))
29 | elements.append(Lens(f=f2, diameter=diameter2))
30 | elements.append(Space(d=f2))
31 | super(Telescope, self).__init__(elements=elements, label=label)
32 |
33 |
34 | class System2f(MatrixGroup):
35 | """
36 | The matrix group of a 2f system can be defined using this function.
37 |
38 | Parameters
39 | ----------
40 | f : float
41 | The focal length of the lens
42 | diameter : float
43 | The diameter of the lens. This value must be positive. (default=+Inf)
44 | label : string
45 | The label for the 2f system
46 |
47 | """
48 | def __init__(self,f, diameter=float('+Inf'), label=''):
49 | elements = []
50 | elements.append(Space(d=f))
51 | elements.append(Lens(f=f, diameter=diameter))
52 | elements.append(Space(d=f))
53 | super(System2f, self).__init__(elements=elements, label=label)
54 |
55 | Telescope = System4f
56 |
57 |
--------------------------------------------------------------------------------
/raytracing/eo.py:
--------------------------------------------------------------------------------
1 | from .specialtylenses import *
2 |
3 | class PN_33_921(AchromatDoubletLens):
4 | """PN_33_921
5 |
6 | .. csv-table::
7 | :header: Parameter, value
8 |
9 | "fa", "100.0"
10 | "fb", "78.32"
11 | "R1", "64.67"
12 | "R2", "-64.67"
13 | "R3", "-343.59"
14 | "tc1", "26.0"
15 | "tc2", "12.7"
16 | "te", "24.66"
17 | "n1", "1.6700"
18 | "n2", "1.8467"
19 | "diameter", "75"
20 |
21 | """
22 | def __init__(self):
23 | # PN for Part number
24 | super(PN_33_921,self).__init__(fa=100.00,fb=78.32, R1=64.67,R2=-64.67, R3=-343.59,
25 | tc1=26.00, tc2=12.7, te=24.66, n1=1.6700, n2=1.8467, diameter=75,
26 | label="EO #33-921",
27 | url="https://www.edmundoptics.com/p/75mm-dia-x-100mm-fl-vis-0-coated-achromatic-lens/3374/")
28 |
29 | class PN_33_922(AchromatDoubletLens):
30 | """PN_33_922
31 |
32 | .. csv-table::
33 | :header: Parameter, value
34 |
35 | "fa", "150.0"
36 | "fb", "126.46"
37 | "R1", "92.05"
38 | "R2", "-72.85"
39 | "R3", "-305.87"
40 | "tc1", "23.2"
41 | "tc2", "23.1"
42 | "te", "36.01"
43 | "n1", "0.5876"
44 | "n2", "0.5876"
45 | "diameter", "75"
46 |
47 | """
48 | def __init__(self):
49 | # PN for Part number
50 | super(PN_33_922,self).__init__(fa=150.00,fb=126.46, R1=92.05,R2=-72.85, R3=-305.87,
51 | tc1=23.2, tc2=23.1, te=36.01, n1=None, mat1=N_BAK1, n2=None, mat2=N_SF8, diameter=75,
52 | label="EO #33-922",
53 | url="https://www.edmundoptics.com/p/75mm-dia-x-150mm-fl-vis-0-coated-achromatic-lens/3376/",
54 | wavelengthRef=0.5876)
55 |
56 | class PN_88_593(AchromatDoubletLens):
57 | """PN_88_593
58 |
59 | .. csv-table::
60 | :header: Parameter, value
61 |
62 | "fa", "200.0"
63 | "fb", "187.69"
64 | "R1", "118.81"
65 | "R2", "-96.37"
66 | "R3", "-288.97"
67 | "tc1", "17.94"
68 | "tc2", "6.00"
69 | "te", "15.42"
70 | "n1", "1.5168"
71 | "n2", "1.6727"
72 | "diameter", "75"
73 |
74 | """
75 | def __init__(self):
76 | super(PN_88_593,self).__init__(fa=200.00,fb=187.69, R1=118.81, R2=-96.37, R3=-288.97,
77 | tc1=17.94, tc2=6.00, te=15.42, n1=1.5168, n2=1.6727, diameter=75,
78 | label="EO #88-593",
79 | url="https://www.edmundoptics.com/p/75mm-dia-x-200mm-fl-vis-0deg-coated-achromatic-lens/30844/")
80 |
81 | class PN_85_877(AchromatDoubletLens):
82 | """PN_85_877
83 |
84 | .. csv-table::
85 | :header: Parameter, value
86 |
87 | "fa", "-10.0"
88 | "fb", "-11.92"
89 | "R1", "-6.55"
90 | "R2", "-5.10"
91 | "R3", "89.10"
92 | "tc1", "1.0"
93 | "tc2", "2.5"
94 | "te", "4.2"
95 | "n1", "0.5876"
96 | "n2", "0.5876"
97 | "diameter", "6.25"
98 |
99 | """
100 | def __init__(self):
101 | super(PN_85_877,self).__init__(fa=-10.0,fb=-11.92, R1=-6.55, R2=5.10, R3=89.10,
102 | tc1=1.0, tc2=2.5, te=4.2, n1=None, mat1=N_BAF10, n2=None, mat2=N_SF10, diameter=6.25,
103 | label="EO #85-877",
104 | url="https://www.edmundoptics.com/p/625mm-dia-x10mm-fl-vis-nir-coated-negative-achromatic-lens/28478/",
105 | wavelengthRef=0.5876)
106 |
--------------------------------------------------------------------------------
/raytracing/examples/README.md:
--------------------------------------------------------------------------------
1 | # RayTracing examples
2 |
3 | by Prof. [Daniel Côté](mailto:dccote@cervo.ulaval.ca?subject=Raytracing%20python%20module) and his group http://www.dcclab.ca
4 |
5 | The publication is available here:
6 |
7 | > ["Tools and tutorial on practical ray tracing for microscopy"](https://doi.org/10.1117/1.NPh.8.1.010801)
8 | >
9 | > by V. Pineau Noël*, S. Masoumi*, E. Parham*, G. Genest, L. Bégin, M.-A. Vigneault, D. C. Côté,
10 | > Neurophotonics, 8(1), 010801 (2021).
11 | > *Equal contributions.
12 | > Permalink: https://doi.org/10.1117/1.NPh.8.1.010801
13 |
14 | ## Listing the examples
15 |
16 | The present directory contains a large number of examples. Some are short and show how to use the raytracing module for simple tasks. They start with the prefix `ex`. Others are longer and perform more than just a simple trace and display a graph: they use Monte Carlo, the get values from functions and perform other calculations, etc... Finally, the scripts that generated the figures from the article cited above start with the prefix `fig` and end with `.py`. Finally, some files are simply the actual figures (`.png`) if they were not calculated.
17 |
18 | This will show you a list of examples of things you can do:
19 |
20 | ```shell
21 | python -m raytracing -l # List examples
22 | python -m raytracing -e all # Run all of them
23 | python -m raytracing -e 1,2,4,6 # Only run 1,2,4 and 6
24 | python -m raytracing -f 5,6,7. # Run scripts that generated figures 5,6 and 7 in article
25 | ```
26 |
27 | or request help with:
28 |
29 | ```shell
30 | python -m raytracing -h
31 | ```
32 |
33 | ## Licence
34 |
35 | This code is provided under the [MIT License](./LICENSE).
--------------------------------------------------------------------------------
/raytracing/examples/__init__.py:
--------------------------------------------------------------------------------
1 | import os
2 | import re
3 | import importlib
4 |
5 | from pygments import highlight
6 | from pygments.lexers import PythonLexer
7 | from pygments.formatters import TerminalFormatter, BmpImageFormatter
8 |
9 | from PIL import Image
10 | from io import BytesIO
11 |
12 | """ This module is only here to support examples, so it dynamically
13 | loads all files that appear to be example files from the directory.
14 | We get the title, the description and the entry function.
15 |
16 | TODO: all other examples from the article are in the diretory
17 | but are not added to the list because they have a structure
18 | that is different (no TITLE and not constrined to a single function).
19 | """
20 | topDir = os.path.dirname(os.path.realpath(__file__))
21 | allFiles = os.listdir(topDir)
22 | allFiles.sort()
23 |
24 | short = []
25 | long = []
26 | for file in allFiles:
27 | pattern = r'^(ex\d+|fig.+)\.py$'
28 | matchObj = re.match(pattern, file)
29 | if matchObj:
30 | name = matchObj.group(1)
31 | module = importlib.import_module(".{0}".format(name),package="raytracing.examples")
32 | with open("{0}/{1}".format(topDir, file)) as f:
33 | srcCode = f.readlines()
34 | # The last three lines are always the main() call
35 | srcCode = srcCode[:-3]
36 | srcCode = str.join('', srcCode)
37 | module.__SRC_CODE = srcCode
38 |
39 | bmpSrcCode = highlight(srcCode, PythonLexer(), BmpImageFormatter())
40 | module.__IMG_CODE = Image.open(BytesIO(bmpSrcCode))
41 |
42 | short.append({"name":name,
43 | "title":module.TITLE,
44 | "code":module.exampleCode,
45 | "sourceCode":srcCode,
46 | "terminalSourceCode":highlight(srcCode, PythonLexer(), TerminalFormatter()),
47 | "bmpSourceCode":Image.open(BytesIO(bmpSrcCode)),
48 | "path":"{0}/{1}".format(topDir, file)
49 | })
50 | else:
51 | matchObj = re.match(r'^(.+)\.py$', file)
52 | if matchObj:
53 | # Other more complete examples:
54 | name = matchObj.group(1)
55 | if name in ["__init__","template", "envexamples"]:
56 | continue
57 |
58 | with open("{0}/{1}".format(topDir, file)) as f:
59 | srcCode = f.readlines()
60 |
61 | srcCode = str.join('', srcCode)
62 | bmpSrcCode = highlight(srcCode, PythonLexer(), BmpImageFormatter())
63 |
64 | long.append({"name":name,
65 | "sourceCode":srcCode,
66 | "terminalSourceCode":highlight(srcCode, PythonLexer(), TerminalFormatter()),
67 | "bmpSourceCode":Image.open(BytesIO(bmpSrcCode)),
68 | "path":"{0}/{1}".format(topDir, file)
69 | })
70 |
--------------------------------------------------------------------------------
/raytracing/examples/confocalDetection.py:
--------------------------------------------------------------------------------
1 | import envexamples
2 |
3 | from raytracing import *
4 | import matplotlib.pyplot as plt
5 |
6 | """ To obtain and plot the intensity of a point source at the pinhole of a
7 | confocal microscope (with variable pinhole size) as a function of position of
8 | focal spot by sending a large number of rays in the system (changing the
9 | position of the focal spot provides an optical sectioning process). """
10 |
11 | # Focal spot radius (Airy disk radius)
12 | focalRadius = 0.000250
13 | # Dictionary of pinhole factors with an empty list which will subsequently
14 | # contain the transmission efficiency for each focal spot position
15 | pinholeModifier = {1 / 3: [], 1: [], 3: []}
16 | # list of all relative positions from the ideal focal spot position in nm
17 | positions = [1000, 800, 500, 300, 150, 100, 50, 25, 0, -25, -50, -100, -150, -300, -500, -800, -1000]
18 | # Number of total rays produced by the focal spot
19 | nRays = 100000
20 | # Production of rays from a focal spot with a radius determined by
21 | # focalRadius
22 | inputRays = RandomUniformRays(yMax=focalRadius, yMin=-focalRadius, maxCount=nRays)
23 | # Focal length of the objective
24 | objFocalLength = 5
25 |
26 | def exampleCode():
27 |
28 | for pinhole in pinholeModifier:
29 | print("\nComputing transmission for pinhole size {0:0.1f}".format(pinhole))
30 |
31 | efficiencyValues = []
32 | for z in positions:
33 | print(".",end='')
34 | newPosition = 5 + (z * 0.000001)
35 | efficiency = illuminationPath(pinholeFactor=pinhole, focalSpotPosition=newPosition)
36 | efficiencyValues.append(efficiency)
37 | pinholeModifier[pinhole] = efficiencyValues
38 |
39 | plt.plot(positions, pinholeModifier[1 / 3], 'k:', label='Small pinhole', linestyle='dashed')
40 | plt.plot(positions, pinholeModifier[1], 'k-', label='Ideal pinhole')
41 | plt.plot(positions, pinholeModifier[3], 'k--', label='Large pinhole', linestyle='dotted')
42 | plt.ylabel('Transmission efficiency')
43 | plt.xlabel('Position of the focal spot (nm)')
44 | plt.legend()
45 | plt.show()
46 |
47 | def path(focalSpotPosition=objFocalLength):
48 | illumination = ImagingPath()
49 | illumination.append(Space(d=focalSpotPosition))
50 | illumination.append(Lens(f=objFocalLength))
51 | illumination.append(Space(d=105))
52 | illumination.append(Lens(f=100))
53 | illumination.append(Space(d=100))
54 | illumination.append(System4f(f1=100, f2=75))
55 | illumination.append(System4f(f1=40, f2=50)) # Path finishes at the pinhole position
56 |
57 | return illumination
58 |
59 | def optimalPinholeSize():
60 | """
61 | Finds the magnification of the optical path and use it to find the optimal pinhole size when the focal spot is at one
62 | focal length distance of the objective.
63 |
64 | Return
65 | -------
66 | pinholeIdeal : Float
67 | Returns the optimal pinhole size
68 | """
69 |
70 | # Dictionnary of the position and magnification of all conjugate planes of the focal spot.
71 | planes = path().intermediateConjugates()
72 | # The last conjugate plane is the pinhole. The magnification of this position is saved in mag.
73 | mag = planes[-1][1]
74 | # Calculates the pinhole size that fits perfectly the focal spot diameter.
75 | pinholeIdeal = abs(mag * (focalRadius * 2))
76 |
77 | return pinholeIdeal
78 |
79 |
80 | def illuminationPath(pinholeFactor=None, focalSpotPosition=None):
81 | """
82 | Determines the amount of rays emitted from the object that are detected at the pinhole plane.
83 |
84 | Parameter
85 | ---------
86 | pinholeFactor : Float
87 | Factor changing the pinhole size according to the ideal pinhole size.
88 |
89 | focalSpotPosition : float
90 | Position of the focal spot according to the objective (first lens)
91 |
92 | Returns
93 | -------
94 | illumination : object of ImagingPath class.
95 | Returns the illumination path
96 | """
97 |
98 | illumination = path(focalSpotPosition)
99 |
100 | pinholeSize = optimalPinholeSize() * pinholeFactor
101 | illumination.append(Aperture(diameter=pinholeSize))
102 |
103 | # Counts how many rays make it through the pinhole
104 | outputRays = illumination.traceManyThroughInParallel(inputRays, progress=False)
105 |
106 | return outputRays.count / inputRays.count
107 |
108 | if __name__ == "__main__":
109 | exampleCode()
--------------------------------------------------------------------------------
/raytracing/examples/envexamples.py:
--------------------------------------------------------------------------------
1 | import sys
2 | import os
3 | import matplotlib.pyplot as plt
4 |
5 | """ This file is needed so the examples in the `examples` directory can be run direclty from there with
6 | the current version of the module """
7 |
8 | # Append module root directory to sys.path
9 | sys.path.insert(0, os.path.dirname( os.path.dirname( os.path.dirname( os.path.abspath(__file__) ) )) )
10 |
11 | try:
12 | import raytracing
13 | except Exception as err:
14 | print("Unable to import : {0} {1}".format(err, sys.path))
15 |
16 | # Some decent parameters for plots
17 | # See https://matplotlib.org/api/font_manager_api.html#matplotlib.font_manager.FontProperties.set_size
18 | params = {'legend.fontsize': 'x-large',
19 | 'figure.figsize': (10, 7),
20 | 'axes.labelsize': 'x-large',
21 | 'axes.titlesize':'x-large',
22 | 'xtick.labelsize':'x-large',
23 | 'ytick.labelsize':'x-large',
24 | 'font.family':'helvetica'}
25 | plt.rcParams.update(params)
26 |
27 |
--------------------------------------------------------------------------------
/raytracing/examples/ex01.py:
--------------------------------------------------------------------------------
1 | TITLE = "A single lens f = 50 mm, infinite diameter"
2 | DESCRIPTION = """ Here we have a single lens of focal length f=50 mm. It has
3 | an infinite diameter. An default object (in blue) is positionned at 100 mm in
4 | front of the lens. The image conjugate (in red) is seen 100 mm after the lens,
5 | as expected. """
6 |
7 | from raytracing import *
8 |
9 | def exampleCode(comments=None):
10 | path = ImagingPath()
11 | path.label = TITLE
12 | path.append(Space(d=100))
13 | path.append(Lens(f=50))
14 | path.append(Space(d=100))
15 | path.display(comments=comments)
16 |
17 | if __name__ == "__main__":
18 | exampleCode()
--------------------------------------------------------------------------------
/raytracing/examples/ex02.py:
--------------------------------------------------------------------------------
1 | TITLE = "Two lenses, infinite diameters"
2 | DESCRIPTION = """ Here we have two thin lenses with infinite diameters. They
3 | are set up in a 4f configuration (telescope), since they are separated by the
4 | sum of their focal lengths. All elements are added to an ImagingPath in the
5 | order they are traversed. An ImagingPath assumes an object (in blue) at the
6 | front edge of the path. You can see the image conjugate (in red) at the focal
7 | plane of the second lens. """
8 |
9 | from raytracing import *
10 |
11 | def exampleCode(comments=None):
12 | path = ImagingPath()
13 | path.label = TITLE
14 | path.append(Space(d=50))
15 | path.append(Lens(f=50))
16 | path.append(Space(d=200))
17 | path.append(Lens(f=50))
18 | path.append(Space(d=100))
19 | path.display(comments=comments)
20 |
21 | if __name__ == "__main__":
22 | exampleCode()
--------------------------------------------------------------------------------
/raytracing/examples/ex03.py:
--------------------------------------------------------------------------------
1 | TITLE = "Finite-diameter lens"
2 | DESCRIPTION = """ An object at z=0 (front edge) is always present in
3 | ImagingPath. Notice the aperture stop (AS) identified at the lens which blocks
4 | the cone of light from the point of the object on the axis. There is no field
5 | stop to restrict the field of view, which is why we must use the default
6 | object and why we cannot restrict the display to the field of view.
7 | Notice the presence of an Aperture Stop (AS) but no Field Stop (FS)."""
8 |
9 | from raytracing import *
10 |
11 | def exampleCode(comments=None):
12 | path = ImagingPath()
13 | path.label = TITLE
14 | path.append(Space(d=100))
15 | path.append(Lens(f=50, diameter=25))
16 | path.append(Space(d=150))
17 | path.display(comments=comments)
18 |
19 | if __name__ == "__main__":
20 | exampleCode()
--------------------------------------------------------------------------------
/raytracing/examples/ex04.py:
--------------------------------------------------------------------------------
1 | TITLE = "Aperture behind lens acting as Field Stop"
2 | DESCRIPTION = """ An object at z=0 (front edge of ImagingPath) is used with
3 | default properties. Notice the aperture stop (AS) identified at the lens which
4 | blocks the cone of light. The second aperture, after the lens, is the Field Stop
5 | (FS) and limits the field of view."""
6 |
7 | from raytracing import *
8 |
9 | def exampleCode(comments=None):
10 | path = ImagingPath()
11 | path.label = TITLE
12 | path.append(Space(d=100))
13 | path.append(Lens(f=50, diameter=30))
14 | path.append(Space(d=30))
15 | path.append(Aperture(diameter=30))
16 | path.append(Space(d=170))
17 | path.display(comments=comments)
18 |
19 | if __name__ == "__main__":
20 | exampleCode()
--------------------------------------------------------------------------------
/raytracing/examples/ex05.py:
--------------------------------------------------------------------------------
1 | TITLE = "Simple microscope system"
2 | DESCRIPTION = """
3 | This is an extremely simple microscope with ideal lenses: the objective lens
4 | (labelled 'Obj') has a focal length of 4 mm and is positionned 184 mm from the
5 | tube lens (f=180 mm). You can zoom using the mouse to inspect the object.
6 | You can see the field of view (hollow blue arrow) and the object (filled blue
7 | arrow) at the focal plan of the objective.
8 | """
9 |
10 | from raytracing import *
11 |
12 | def exampleCode(comments=None):
13 | path = ImagingPath()
14 | path.label = TITLE
15 | path.append(Space(d=4))
16 | path.append(Lens(f=4, diameter=8, label='Obj'))
17 | path.append(Space(d=4 + 180))
18 | path.append(Lens(f=180, diameter=50, label='Tube Lens'))
19 | path.append(Space(d=180))
20 | path.display(ObjectRays(diameter=1, halfAngle=0.5),comments=comments)
21 |
22 | if __name__ == "__main__":
23 | exampleCode()
24 |
--------------------------------------------------------------------------------
/raytracing/examples/ex06.py:
--------------------------------------------------------------------------------
1 | TITLE = "Kohler illumination"
2 | DESCRIPTION = """ Kohler illumination is a strategy to obtain the most uniform
3 | illumination at the sample. Instead of imaging the lamp filament on the
4 | sample, one images the lamp at the back of the objective, which will then
5 | project the Fourier transform of the illumination pattern at the object plane.
6 | Notice that the object (the lamp) has an image at the back focal plane of the
7 | objective. """
8 |
9 | from raytracing import *
10 |
11 | def exampleCode(comments=DESCRIPTION):
12 | fobj, dObj, f2, d2, f3, d3 = 9, 20, 200, 50, 100, 50
13 |
14 | path = OpticalPath()
15 | path.append(Space(d=f3))
16 | path.append(Lens(f=f3, diameter=d3))
17 | path.append(Space(d=f3))
18 | path.append(Space(d=f2))
19 | path.append(Lens(f=f2, diameter=d2))
20 | path.append(Space(d=f2))
21 | path.append(Space(d=fobj))
22 | path.append(Lens(f=fobj, diameter=dObj))
23 | path.append(Space(d=fobj))
24 | path.label = TITLE
25 | path.display(comments=comments)
26 |
27 | if __name__ == "__main__":
28 | exampleCode()
--------------------------------------------------------------------------------
/raytracing/examples/ex07.py:
--------------------------------------------------------------------------------
1 | TITLE = "Focussing through a dielectric slab"
2 | DESCRIPTION = """ When light focusses through a dielectric material, it is
3 | refracted. The effect on the position of the image can be see here: the first
4 | red arrow at z=200 mm arrow is the image that would have been obtained without
5 | the dielectric. However, the presence of this dielectric moves the final
6 | image to approximately 213 mm. Because all elements have infinite diameters,
7 | it is not possible to use the principal ray and the axial ray because they
8 | are not defined."""
9 |
10 | from raytracing import *
11 |
12 | def exampleCode(comments=None):
13 | path = ImagingPath()
14 | path.label = TITLE
15 | path.append(Space(d=100))
16 | path.append(Lens(f=50))
17 | path.append(Space(d=30))
18 | path.append(DielectricSlab(n=1.5, thickness=40))
19 | path.append(Space(d=100))
20 | path.display(comments=comments)
21 |
22 | if __name__ == "__main__":
23 | exampleCode()
--------------------------------------------------------------------------------
/raytracing/examples/ex08.py:
--------------------------------------------------------------------------------
1 | TITLE = "Virtual image at -f with object at f/2"
2 | DESCRIPTION = """
3 | With an object midway between the focus and the lens, we obtain a virtual
4 | image at the front focal plane (d=-f to the left of the lens).
5 | The exact position can be obtained with path.forwardConjugate(), which
6 | will return the distance (from the end of the path) and the transfer matrix
7 | to that point. That transfer marix is necessarily an imaging matrix (B=0).
8 | """
9 |
10 | from raytracing import *
11 |
12 | def exampleCode(comments=None):
13 | path = ImagingPath()
14 | path.label = TITLE
15 | path.append(Space(d=25))
16 | path.append(Lens(f=50))
17 | path.append(Space(d=50))
18 | distance, transferMatrix = path.forwardConjugate()
19 | path.display(comments=comments)
20 |
21 | if __name__ == "__main__":
22 | exampleCode()
--------------------------------------------------------------------------------
/raytracing/examples/ex09.py:
--------------------------------------------------------------------------------
1 | TITLE = "Infinite telecentric 4f telescope"
2 | DESCRIPTION = """ Two lenses separated by the sum of their focal lengths is a
3 | telecentric telescope, or a 4f-system. If we set up the path from the focal
4 | plane of the first lens to the focal plane of the second lens (as is done
5 | here), then the system has a transfer matrix that is "imaging", with B=0. This
6 | can be checked with path.isImaging.
7 |
8 | Raytracing defines System4f() to conveniently put two lenses
9 | in this configuration: System4f(lens1, lens2).
10 | """
11 |
12 | from raytracing import *
13 |
14 | def exampleCode(comments=None):
15 | path = ImagingPath()
16 | path.label = TITLE
17 | path.append(Space(d=50))
18 | path.append(Lens(f=50))
19 | path.append(Space(d=100))
20 | path.append(Lens(f=50))
21 | path.append(Space(d=50))
22 | print("Transfer matrix of system is imaging (i.e. B=0): {0}".format(path.isImaging))
23 | path.display(comments=comments)
24 |
25 | if __name__ == "__main__":
26 | exampleCode()
27 |
--------------------------------------------------------------------------------
/raytracing/examples/ex10.py:
--------------------------------------------------------------------------------
1 | TITLE = "Retrofocus $f_e$={0:.1f} cm, and BFL={1:.1f}"
2 | DESCRIPTION = """ A retrofocus is a system of lenses organized in such a way
3 | that the effective focal length is shorter than the back focal length (i.e.
4 | the distance between the surface of the lens and the focal spot). It consists
5 | of a diverging lens followed by a converging lens. This is used to obtain a
6 | short focal length in situations where distances are constrained. """
7 |
8 | from raytracing import *
9 |
10 | def exampleCode(comments=None):
11 | path = ImagingPath()
12 | path.append(Space(d=20))
13 | path.append(Lens(f=-10, label='Div'))
14 | path.append(Space(d=7))
15 | path.append(Lens(f=10, label='Foc'))
16 | path.append(Space(d=40))
17 | (focal, focal) = path.effectiveFocalLengths()
18 | bfl = path.backFocalLength()
19 | path.label = TITLE.format(focal, bfl)
20 | path.display(comments=comments)
21 |
22 | if __name__ == "__main__":
23 | exampleCode()
--------------------------------------------------------------------------------
/raytracing/examples/ex11.py:
--------------------------------------------------------------------------------
1 | TITLE = "Thick diverging lens computed from the Lensmaker equation"
2 | DESCRIPTION = """ Raytracing offers a simple "thick lens" that has a non-null
3 | thickness. You need to provide R1 and R2 as well as the index and the
4 | thickness of the lens. It computes a single transfer matrix based on the
5 | application of a dielectric, space and dielectric transfer matrix. Notice
6 | that, in line with the paraxial approximation, the vertex is where the
7 | curvature of the rays occur, even if in this case the curvature brings the
8 | surface away from the vertex. Blocking of rays inside the lens is not
9 | calculated: you would need to build a MatrixGroup with all elements to do so
10 | (and this is what CompoundLens and AchromaticDoublets do)."""
11 |
12 | from raytracing import *
13 |
14 | def exampleCode(comments=None):
15 | path = ImagingPath()
16 | path.label = TITLE
17 | path.append(Space(d=50))
18 | path.append(ThickLens(R1=-20, R2=20, n=1.55, thickness=10, diameter=25, label='Lens'))
19 | path.append(Space(d=50))
20 | path.displayWithObject(diameter=20, comments=comments)
21 |
22 | if __name__ == "__main__":
23 | exampleCode()
--------------------------------------------------------------------------------
/raytracing/examples/ex12.py:
--------------------------------------------------------------------------------
1 | TITLE = "Thick diverging lens built from individual elements"
2 | DESCRIPTION = """ If you build a lens from individual elements
3 | (DielectricInterface, Space and DielectrcInterface), then the rays inside the
4 | lens may be blocked from the finite diameter (in this case, Space() has a
5 | finite diameter of 25 mm). The drawing on the graph is not as elegant, but
6 | the physics is more correct. Notice the upward rays from the green point is
7 | actually blocked inside. However, notice that again the rays are refracting
8 | at the vertex, in accord with the paraxial approximation. """
9 |
10 | from raytracing import *
11 |
12 | def exampleCode(comments=None):
13 | # Demo #12: Thick diverging lens built from individual elements
14 | path = ImagingPath()
15 | path.label = TITLE
16 | path.append(Space(d=50))
17 | path.append(DielectricInterface(R=-20, n1=1.0, n2=1.55, diameter=25, label='Front'))
18 | path.append(Space(d=10, diameter=25, label='Lens'))
19 | path.append(DielectricInterface(R=20, n1=1.55, n2=1.0, diameter=25, label='Back'))
20 | path.append(Space(d=50))
21 | path.displayWithObject(diameter=20, comments=comments)
22 |
23 |
24 | if __name__ == "__main__":
25 | exampleCode()
--------------------------------------------------------------------------------
/raytracing/examples/ex13.py:
--------------------------------------------------------------------------------
1 | TITLE = "Obtain the forward and backward conjugates"
2 | DESCRIPTION = """ This example does not show a plot, it calculates the
3 | position of the image. forwardConjugate() finds the position and transfer
4 | matrix assuming an object at the front of the matrix/element. The distance is
5 | given from the end of the element. This is typically when we wish to obtain
6 | the image position and the transfer matrix to the image. With
7 | backwardConjugate(), we can also assume an image at the end of the
8 | matrix/element, and calculate where the object is to obtain an image at the
9 | poin (the distance is given from the front of the element). """
10 |
11 | from raytracing import *
12 |
13 | def exampleCode(comments=None):
14 | M1 = Space(d=10)
15 | M2 = Lens(f=5)
16 | M3 = M2 * M1
17 | print("Image is at distance and transfer matrix: {0} ".format(M3.forwardConjugate()))
18 | print("Object is at distance and transfer matrix: {0} ".format(M3.backwardConjugate()))
19 |
20 | if __name__ == "__main__":
21 | exampleCode()
--------------------------------------------------------------------------------
/raytracing/examples/ex14.py:
--------------------------------------------------------------------------------
1 | TITLE = "Generic objectives"
2 |
3 | DESCRIPTION = """ It is possible to define microscopy Objectives that will
4 | behave similarly to real objectives (with a finite thickness, a back aperture,
5 | etc...). Using the specifications of an objective, one can use the Objective
6 | class to model essentially any objective. Real objectives from manufacturers
7 | (Olympus, Nikon) are included in the module."""
8 |
9 | from raytracing import *
10 |
11 | def exampleCode(comments=None):
12 | obj = Objective(f=10, NA=0.8, focusToFocusLength=60, backAperture=18, workingDistance=2,
13 | magnification=40, fieldNumber=1.4, label="Objective")
14 | print("Focal distances: ", obj.focalDistances())
15 | print("Position of PP1 and PP2: ", obj.principalPlanePositions(z=0))
16 | print("Focal spots positions: ", obj.focusPositions(z=0))
17 | print("Distance between entrance and exit planes: ", obj.L)
18 |
19 | path = ImagingPath()
20 | path.label = "Path with generic objective"
21 | path.append(Space(180))
22 | path.append(obj)
23 | path.append(Space(10))
24 |
25 | path.displayWithObject(diameter=20, fanAngle=0.005, comments=comments)
26 |
27 | if __name__ == "__main__":
28 | exampleCode()
--------------------------------------------------------------------------------
/raytracing/examples/ex15.py:
--------------------------------------------------------------------------------
1 | TITLE = "Model Olympus objective LUMPlanFL40X"
2 | DESCRIPTION = """ Using the Objective class of Raytracing, we have used the
3 | specifications of the LUMPlanFL40X objective to model it for use. It can be
4 | used in any system, and it can be flipped with the flip() command if needed.
5 | """
6 |
7 |
8 | def exampleCode(comments=None):
9 | from raytracing import ImagingPath, Space, olympus
10 | path = ImagingPath()
11 | path.label = TITLE
12 | path.append(Space(180))
13 | path.append(olympus.LUMPlanFL40X())
14 | path.append(Space(10))
15 | path.displayWithObject(diameter=10, fanAngle=0.005, comments=comments)
16 |
17 | if __name__ == "__main__":
18 | import envexamples
19 | exampleCode()
--------------------------------------------------------------------------------
/raytracing/examples/ex16.py:
--------------------------------------------------------------------------------
1 | TITLE = "Commercial doublets from Thorlabs and Edmund"
2 | DESCRIPTION = """ Several achromatic doublets from Thorlabs and Edmund Optics
3 | have been included (the visible series AC254-xxx-A from thorlabs for
4 | instance). They have been modelled according to the specifications from both
5 | manufacturers using the complete dispersion curve for glasses through a
6 | Material class and the curvatures of the surfaces. It is possible to show the
7 | focal shifts from chromatic aberrations. A Zemax file reader (ZMXReader) also
8 | exists to read other lenses. """
9 |
10 | def exampleCode(comments=None):
11 | from raytracing import thorlabs, eo
12 |
13 | thorlabs.AC254_050_A().display()
14 | thorlabs.AC254_100_A().showChromaticAberrations()
15 | eo.PN_33_921().display()
16 |
17 | if __name__ == "__main__":
18 | import envexamples
19 | exampleCode()
20 |
21 |
--------------------------------------------------------------------------------
/raytracing/examples/ex17.py:
--------------------------------------------------------------------------------
1 | TITLE = "An optical system with vendor lenses"
2 | DESCRIPTION = """
3 | All vendor lenses could be used just like any other elements. Remember to
4 | check backFocalLength() and effectiveFocalLengths() to understand that the focal
5 | point is not "f_e" after the lens but rather "BFL" after the lens.
6 | """
7 |
8 | from raytracing import *
9 |
10 | def exampleCode(comments=None):
11 | path = ImagingPath()
12 | path.label = TITLE
13 | path.append(Space(d=50))
14 | path.append(thorlabs.AC254_050_A())
15 | path.append(Space(d=50))
16 | path.append(thorlabs.AC254_050_A())
17 | path.append(Space(d=150))
18 | path.append(eo.PN_33_921())
19 | path.append(Space(d=50))
20 | path.append(eo.PN_88_593())
21 | path.append(Space(180))
22 | path.append(olympus.LUMPlanFL40X())
23 | path.append(Space(10))
24 | path.display(comments=comments)
25 |
26 | if __name__ == "__main__":
27 | exampleCode()
--------------------------------------------------------------------------------
/raytracing/examples/ex18.py:
--------------------------------------------------------------------------------
1 | TITLE = "Laser beam and vendor lenses"
2 | DESCRIPTION = """
3 | It is possible to propagate gaussian beams using a LaserPath instead of an imaging
4 | path. The formalism makes use of the same matrices, but the GaussianBeam is
5 | different from a ray: it is a complex radius of curvature (q), but all the complexity
6 | is hidden in the GaussianBeam class (although you can access q, w, zo, etc..).
7 | Note that any diffraction of the beam from edges of element is not considered
8 | because the formalism does not allow it: a gaussian beam remains a gaussian beam
9 | and therefore will not be clipped by lenses.
10 | """
11 |
12 | from raytracing import *
13 |
14 | def exampleCode(comments=None):
15 | # Demo #18: Laser beam and vendor lenses
16 | path = LaserPath()
17 | path.label = TITLE
18 | path.append(Space(d=50))
19 | path.append(thorlabs.AC254_050_A())
20 | path.append(Space(d=50))
21 | path.append(thorlabs.AC254_050_A())
22 | path.append(Space(d=150))
23 | path.append(eo.PN_33_921())
24 | path.append(Space(d=50))
25 | path.append(eo.PN_88_593())
26 | path.append(Space(d=180))
27 | path.append(olympus.LUMPlanFL40X())
28 | path.append(Space(d=10))
29 | path.display(beams=[GaussianBeam(w=0.001)], comments=comments)
30 |
31 | if __name__ == "__main__":
32 | exampleCode()
--------------------------------------------------------------------------------
/raytracing/examples/ex19.py:
--------------------------------------------------------------------------------
1 | TITLE = "Cavity round trip and calculated laser modes"
2 | DESCRIPTION = """
3 | Although less developed in the raytracing module, it is also possible to calculate
4 | eigenmodes of laser cavities.
5 | """
6 |
7 | from raytracing import *
8 |
9 | def exampleCode(comments=None):
10 | cavity = LaserCavity(label="")
11 | cavity.append(Space(d=160))
12 | cavity.append(DielectricSlab(thickness=100, n=1.8))
13 | cavity.append(Space(d=160))
14 | cavity.append(CurvedMirror(R=-400))
15 | cavity.append(Space(d=160))
16 | cavity.append(DielectricSlab(thickness=100, n=1.8))
17 | cavity.append(Space(d=160))
18 |
19 | # Calculate all self-replicating modes (i.e. eigenmodes)
20 | (q1, q2) = cavity.eigenModes()
21 | print(q1,q2)
22 |
23 | # Obtain all physical modes (i.e. only finite eigenmodes)
24 | qs = cavity.laserModes()
25 | for q in qs:
26 | print(q)
27 |
28 | cavity.display(comments=comments)
29 |
30 | if __name__ == "__main__":
31 | exampleCode()
--------------------------------------------------------------------------------
/raytracing/examples/fig1-matrices.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DCC-Lab/RayTracing/9609b55f16c8ce4499c85a2622619f60b7675fa3/raytracing/examples/fig1-matrices.png
--------------------------------------------------------------------------------
/raytracing/examples/fig2-properties.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DCC-Lab/RayTracing/9609b55f16c8ce4499c85a2622619f60b7675fa3/raytracing/examples/fig2-properties.png
--------------------------------------------------------------------------------
/raytracing/examples/fig3-apertures.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DCC-Lab/RayTracing/9609b55f16c8ce4499c85a2622619f60b7675fa3/raytracing/examples/fig3-apertures.png
--------------------------------------------------------------------------------
/raytracing/examples/fig4-overviewFigure.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DCC-Lab/RayTracing/9609b55f16c8ce4499c85a2622619f60b7675fa3/raytracing/examples/fig4-overviewFigure.png
--------------------------------------------------------------------------------
/raytracing/examples/fig5-LSM_reference.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DCC-Lab/RayTracing/9609b55f16c8ce4499c85a2622619f60b7675fa3/raytracing/examples/fig5-LSM_reference.png
--------------------------------------------------------------------------------
/raytracing/examples/fig7-kohler.py:
--------------------------------------------------------------------------------
1 | TITLE = "Kohler and critical (i.e. non-Kohler) illumination"
2 | DESCRIPTION = ''' This code compares the same system with and without Kohler illumination.
3 | The overlay of conjugate planes can determine indeed if the light source is
4 | imaged with the sample or not, significantly reducing the quality of the
5 | images. '''
6 |
7 | from raytracing import *
8 |
9 | def exampleCode(comments=None):
10 | illumination = ImagingPath()
11 | illumination.design(fontScale=1.5)
12 | illumination.append(Space(d=20))
13 | illumination.append(Lens(f=10, diameter=25.4, label="Collector"))
14 | illumination.append(Space(d=30))
15 | illumination.append(Aperture(diameter=2, label="Field diaphragm"))
16 | illumination.append(Space(d=10+30))
17 | illumination.append(Lens(f=30, diameter=25.4, label="Condenser"))
18 | illumination.append(Space(d=30+30))
19 | illumination.append(Lens(f=30, diameter=25.4, label="Objective"))
20 | illumination.append(Space(d=30+30))
21 | illumination.append(Lens(f=30, diameter=25.4, label="Tube"))
22 | illumination.append(Space(d=30+30))
23 | illumination.append(Lens(f=30, diameter=25.4, label="Eyepiece"))
24 | illumination.append(Space(d=30+2))
25 | illumination.append(Lens(f=2, diameter=10, label="Eye Entrance"))
26 | illumination.append(Space(d=2))
27 | illumination.display(interactive=False, raysList=[
28 | LampRays(diameter=0.1, NA=0.5, N=2, T=6, z=6.6666666, rayColors='r', label="Source"),
29 | ObjectRays(diameter=2, halfAngle=0.1, H=2, T=2, z=120, rayColors='g', color='g', label="Sample")], removeBlocked=False)
30 |
31 | if __name__ == "__main__":
32 | import envexamples
33 | exampleCode()
--------------------------------------------------------------------------------
/raytracing/examples/fig8-widefield.py:
--------------------------------------------------------------------------------
1 | TITLE = "The effect of lens diameters on collection efficiency"
2 | DESCRIPTION = """
3 | In any imaging system, lens diameters are of great importance as
4 | they dictate the position of the Aperture Stop(AS) and Field Stop(FS).
5 | We recall that the AS controls the light acceptance and that a
6 | poorly placed FS causes vignetting.
7 |
8 | The following code shows a simple imaging system with three different
9 | paths containing different lens diameter.
10 |
11 | (a). The first one shows that if both lenses are too small,
12 | the AS is the first lens and the FS is the second lens.
13 | We get vignetting on the final image, since the FS is poorly
14 | placed at a lens instead of the detector's camera.
15 | (b). The second one shows that the second lens is smaller
16 | than the first one, so the FS is at the camera but
17 | AS is on the second lens, which is suboptimal.
18 | (c). The last one shows that both lenses are big enough to make the first
19 | lens the AS and the detector's camera the FS.
20 | """
21 |
22 | from raytracing import *
23 | import gc
24 |
25 | # Defines the path. a and b are the diameter of the lenses.
26 | def imagingPathPreset(a=10, b=10, title=""):
27 |
28 | path = ImagingPath()
29 | path.design(fontScale=1.7)
30 | path.label=title
31 | path.append(System4f(f1=50, diameter1=a, f2=50, diameter2=b))
32 | path.append(Aperture(diameter=10, label='Camera'))
33 |
34 | return path
35 |
36 | def exampleCode(comments=None):
37 | # Input from the expected field of view
38 | inputRays = RandomUniformRays(yMax = 5,
39 | yMin = -5,
40 | thetaMin = -0.5,
41 | thetaMax = +0.5,
42 | maxCount=1000000)
43 |
44 | # Three paths with different sets of lens diameter.
45 | path1 = imagingPathPreset(a=15, b=15, title="Vignetting with FS poorly placed because of second lens diameter")
46 | outputRays = path1.traceManyThrough(inputRays)
47 | efficiency = 100*outputRays.count/inputRays.count
48 | path1.display(ObjectRays(diameter=4,H=3,T=5, halfAngle=0.15), removeBlocked=False)
49 | outputRays.display("Output profile with vignetting {0:.0f}% efficiency".format(efficiency), showTheta=False)
50 | path1.reportEfficiency()
51 |
52 | gc.collect()
53 |
54 | path2 = imagingPathPreset(a=40, b=15, title="Suboptimal AS at second lens, but without vignetting")
55 | outputRays = path2.traceManyThrough(inputRays)
56 | efficiency = 100*outputRays.count/inputRays.count
57 | path2.display(ObjectRays(diameter=4,H=3,T=5, halfAngle=0.15), removeBlocked=False)
58 | outputRays.display("Output profile {0:.0f}% efficiency".format(efficiency), showTheta=False)
59 | path2.reportEfficiency()
60 |
61 | gc.collect()
62 |
63 | path3 = imagingPathPreset(a=25, b=50, title="Better AS at first lens and FS at Camera")
64 | outputRays = path3.traceManyThrough(inputRays)
65 | efficiency = 100*outputRays.count/inputRays.count
66 | path3.display(ObjectRays(diameter=4,H=3,T=5, halfAngle=0.15), removeBlocked=False)
67 | outputRays.display("Output profile {0:.0f}% efficiency".format(efficiency), showTheta=False)
68 | path3.reportEfficiency()
69 |
70 | if __name__ == "__main__":
71 | import envexamples
72 | exampleCode()
--------------------------------------------------------------------------------
/raytracing/examples/illuminator.py:
--------------------------------------------------------------------------------
1 | TITLE = "Kohler illumination"
2 | DESCRIPTION = """
3 | """
4 |
5 | import envexamples
6 | from raytracing import *
7 |
8 | def exampleCode():
9 | path = ImagingPath()
10 | path.name = "Kohler illumination with 1 cm wide lamp and 0.5 NA"
11 | path.append(Space(d=40))
12 | path.append(Lens(f=40,diameter=25, label='Collector'))
13 | path.append(Space(d=40+25))
14 | path.append(Lens(f=25, diameter=75, label='Condenser'))
15 | path.append(Space(d=25))
16 | path.append(Space(d=9))
17 | path.append(Lens(f=9, diameter=8, label='Objective'))
18 | path.append(Space(d=9))
19 | path.showLabels=True
20 | print(path.fieldStop())
21 | print(path.fieldOfView())
22 | path.display(ObjectRays(diameter=20, H=3, T=3, halfAngle=0.5), onlyPrincipalAndAxialRays=True, limitObjectToFieldOfView=True)
23 | #path.saveFigure("Illumination.png", onlyPrincipalAndAxialRays=True, limitObjectToFieldOfView=True)
24 |
25 | if __name__ == "__main__":
26 | exampleCode()
27 |
--------------------------------------------------------------------------------
/raytracing/examples/invariant.py:
--------------------------------------------------------------------------------
1 | import envexamples
2 | from raytracing import *
3 |
4 | path = ImagingPath()
5 | path.name = "4f system, 1 cm object, small lenses"
6 | path.append(Space(d=5))
7 | path.append(Lens(f=5, diameter=2.5))
8 | path.append(Space(d=15))
9 | path.append(Lens(f=10,diameter=2.5))
10 | path.append(Space(d=10))
11 | path.display()
12 | #path.saveFigure('object-smallLenses.png')
13 |
14 | path = ImagingPath()
15 | path.name = "4f system, 1 cm object, small and large lenses"
16 | path.append(Space(d=5))
17 | path.append(Lens(f=5, diameter=2.5))
18 | path.append(Space(d=15))
19 | path.append(Lens(f=10,diameter=5))
20 | path.append(Space(d=10))
21 | path.display()
22 | #path.saveFigure('object-smallLargeLenses.png')
23 |
24 | path = ImagingPath()
25 | path.name = "4f system, calculated field of view, small lenses"
26 | path.append(Space(d=5))
27 | path.append(Lens(f=5, diameter=2.5))
28 | path.append(Space(d=15))
29 | path.append(Lens(f=10,diameter=2.5))
30 | path.append(Space(d=10))
31 | path.display(onlyPrincipalAndAxialRays=True, limitObjectToFieldOfView=True)
32 | #path.saveFigure('fov-smallLenses.png', onlyChiefAndMarginalRays=True, limitObjectToFieldOfView=True)
33 |
34 | path = ImagingPath()
35 | path.name = "4f system, improved field of view, small and large lenses"
36 | path.append(Space(d=5))
37 | path.append(Lens(f=5, diameter=2.5))
38 | path.append(Space(d=15))
39 | path.append(Lens(f=10,diameter=5.0))
40 | path.append(Space(d=10))
41 | path.display(onlyPrincipalAndAxialRays=True, limitObjectToFieldOfView=True)
42 | #path.saveFigure('fov-smallLargeLenses.png', onlyChiefAndMarginalRays=True, limitObjectToFieldOfView=True)
43 |
44 | path = ImagingPath()
45 | path.name = "4f systeme, no change in field of view with large first lens"
46 | path.append(Space(d=5))
47 | path.append(Lens(f=5, diameter=5.0))
48 | path.append(Space(d=15))
49 | path.append(Lens(f=10,diameter=5.0))
50 | path.append(Space(d=10))
51 | path.display(onlyPrincipalAndAxialRays=True, limitObjectToFieldOfView=True)
52 | #path.saveFigure('fov-largeLenses.png', onlyChiefAndMarginalRays=True, limitObjectToFieldOfView=True)
53 |
--------------------------------------------------------------------------------
/raytracing/examples/invariantAndEfficiency.py:
--------------------------------------------------------------------------------
1 | import envexamples # modifies path
2 | from raytracing import *
3 |
4 | """
5 | The Lagrange invariant is a constant defining the collection efficiency of an optical system. The Lagrange
6 | invariant is calculated using the principal and axial rays, whether the optical invariant is calculated with
7 | another combination of rays. This code uses the optical invariant to characterize the ray transmission in a
8 | 4f system and shows that the optical invariant is greatly affected by the used optics. Indeed, changing the
9 | diameter of the first lens affects the number of detected rays at the imaged plane.
10 | """
11 |
12 | path = ImagingPath()
13 | path.design(fontScale=1.7)
14 | path.append(System4f(f1=10, diameter1=25.4, f2=20, diameter2=25.4))
15 | path.reportEfficiency()
16 | path.display(interactive=False)
17 |
18 | path2 = ImagingPath()
19 | path2.design(fontScale=1.5)
20 | path2.append(System4f(f1=10, diameter1=12.7, f2=20, diameter2=25.4))
21 | path2.reportEfficiency()
22 | path2.display(interactive=False)
--------------------------------------------------------------------------------
/raytracing/examples/kohlerAndInvariant.py:
--------------------------------------------------------------------------------
1 | import envexamples
2 |
3 | from raytracing import *
4 |
5 | '''
6 | DESCRIPTION
7 | '''
8 |
9 | #print("The Lagrange Invariant of this system is {}".format(illumination1.lagrangeInvariant()))
10 |
11 | def displayMultiple(paths: list,
12 | limitObjectToFieldOfView=False,
13 | onlyPrincipalAndAxialRays=False,
14 | removeBlockedRaysCompletely=False,
15 | comments=None):
16 | fig, axes = plt.subplots(figsize=(10, 7))
17 | for path in paths:
18 | path.createRayTracePlot(axes=axes, limitObjectToFieldOfView=False,
19 | onlyPrincipalAndAxialRays=False,
20 | removeBlockedRaysCompletely=False)
21 | # can only def callbacks for only one path
22 | axes.callbacks.connect('ylim_changed', paths[0].updateDisplay)
23 | axes.set_ylim([-paths[0].displayRange(axes) / 2 * 1.5, paths[0].displayRange(axes) / 2 * 1.5])
24 | paths[0]._showPlot()
25 |
26 |
27 | # Source with Kohler illumination
28 | illumination1 = ImagingPath()
29 | illumination1.fanNumber = 3
30 | illumination1.fanAngle = 0.1
31 | illumination1.design(rayColors = ["r","r","r"])
32 |
33 | illumination1.append(Space(d=10))
34 | illumination1.append(Lens(f=10, diameter=100, label="Collector"))
35 | illumination1.append(Space(d=10+30))
36 | illumination1.append(Lens(f=30, diameter=100, label="Condenser"))
37 | illumination1.append(Space(d=30+30))
38 | illumination1.append(Lens(f=30, diameter=100, label="Objective"))
39 | illumination1.append(Space(d=30+30))
40 | illumination1.append(Lens(f=30, diameter=100, label="Tube"))
41 | illumination1.append(Space(d=30+30))
42 | illumination1.append(Lens(f=30, diameter=100, label="Eyepiece"))
43 | illumination1.append(Space(d=30+2))
44 | illumination1.append(Lens(f=2, diameter=10, label="Eye Entrance"))
45 | illumination1.append(Space(d=2))
46 | illumination1.display()
47 |
48 |
49 |
50 |
51 | # Source without Kohler illumination
52 | illumination2 = ImagingPath()
53 | illumination2.fanNumber = 3
54 | illumination2.fanAngle = 0.1
55 | illumination2.objectPosition = 20
56 | illumination2.design(rayColors = ["b","b","b"])
57 |
58 | illumination2.append(Space(d=30))
59 | illumination2.append(Lens(f=30, diameter=100, label="Condenser"))
60 | illumination2.append(Space(d=30+30))
61 | illumination2.append(Lens(f=30, diameter=100, label="Objective"))
62 | illumination2.append(Space(d=30+30))
63 | illumination2.append(Lens(f=30, diameter=100, label="Tube"))
64 | illumination2.append(Space(d=30+30))
65 | illumination2.append(Lens(f=30, diameter=100, label="Eyepiece"))
66 | illumination2.append(Space(d=30+2))
67 | illumination2.append(Lens(f=2, diameter=10, label="Eye Entrance"))
68 | illumination2.append(Space(d=2))
69 | illumination2.display()
70 |
71 |
72 |
73 |
74 | # Sample imaging path
75 | illumination3 = ImagingPath()
76 | illumination3.objectPosition = 80
77 | illumination3.objectHeight = 5
78 | illumination3.fanNumber = 1
79 | illumination3.fanAngle = 0
80 | illumination3.design(rayColors = ["g","g","g"])
81 |
82 |
83 | illumination3.append(Space(d=10))
84 | illumination3.append(Lens(f=10, diameter=100, label="Collector"))
85 | illumination3.append(Space(d=10+30))
86 | illumination3.append(Lens(f=30, diameter=100, label="Condenser"))
87 | illumination3.append(Space(d=30+30))
88 | illumination3.append(Lens(f=30, diameter=100, label="Objective"))
89 | illumination3.append(Space(d=30+30))
90 | illumination3.append(Lens(f=30, diameter=100, label="Tube"))
91 | illumination3.append(Space(d=30+30))
92 | illumination3.append(Lens(f=30, diameter=100, label="Eyepiece"))
93 | illumination3.append(Space(d=30+2))
94 | illumination3.append(Lens(f=2, diameter=10, label="Eye Entrance"))
95 | illumination3.append(Space(d=2))
96 | illumination3.display()
97 |
98 | #displayMultiple([illumination3, illumination1])
99 | #displayMultiple([illumination3, illumination2])
100 |
--------------------------------------------------------------------------------
/raytracing/examples/laserScanningMicroscope.py:
--------------------------------------------------------------------------------
1 | import envexamples
2 | from raytracing import *
3 | import matplotlib.pyplot as plt
4 | import numpy as np
5 |
6 |
7 | """
8 | In a laser scanning system, the scanning components define
9 | the covered field-of-view (FOV) at the sample plane. Here,
10 | we show a one-dimensional example with a polygonal mirror
11 | of 36 facets that rotates rapidly to scan
12 | the beam along the horizontal direction. It produces a meachnical
13 | sweep of 10 degrees, or 0.1750 rad, between each facets.
14 | Therefore, the laser beam covers a total optical scan angle of 20 degrees.
15 | In the following example, the object is considered to be the
16 | laser beam at the polygonal mirror plane.
17 | The output profile shows on its x-axis the width of the FOV under the objective.
18 | """
19 |
20 | # List of the scan angle of the ray making it throught the system.
21 | thetas = []
22 |
23 | # List of 1 corresponding to the number of elements in heights
24 | # so that plt.plot() doesn't freak out.
25 | positions = []
26 |
27 | # Radius of the laser beam at the scanning element.
28 | objectHalfHeight = 0.000250
29 |
30 | # Angle produced by the scanning element.
31 | scanAngle = 10*np.pi/180
32 |
33 | # Number of total rays considered in these calculations.
34 | nRays = 10000
35 |
36 | # Production of rays in the angle range of the scanning element.
37 | scanRays = UniformRays(yMax=0, thetaMax=scanAngle, M=1, N=nRays)
38 |
39 | class UISUPLAPO60XW(Objective):
40 |
41 | def __init__(self):
42 | super(UISUPLAPO60XW, self).__init__(f=180/60,
43 | NA=1.2,
44 | focusToFocusLength=40,
45 | backAperture=7,
46 | workingDistance=0.28,
47 | magnification=60,
48 | fieldNumber=22,
49 | label='UISUPLAPO60XW Objective')
50 |
51 |
52 | def illuminationPath():
53 |
54 | illumination = ImagingPath()
55 | # The object in this situation is the laser beam at the scanning element.
56 | illumination.objectHeight = objectHalfHeight*2
57 | illumination.rayNumber = 3
58 | illumination.fanNumber = 3
59 | illumination.fanAngle = 0
60 | illumination.append(System4f(f1=40, f2=75, diameter1=24.5, diameter2=24.5))
61 | illumination.append(System4f(f1=100, f2=100, diameter1=24.5, diameter2=24.5))
62 | illumination.append(Space(d=180/40))
63 | illumination.append(UISUPLAPO60XW())
64 | illumination.append(Space(d=180/40))
65 |
66 | return illumination
67 |
68 |
69 | path = illuminationPath()
70 | outputRays = path.traceManyThrough(scanRays)
71 | for i in range(len(outputRays)):
72 | thetas.append(scanRays[i].theta*180/np.pi)
73 | positions.append(outputRays[i].y*1000)
74 |
75 | scanRays.displayProgress()
76 |
77 | plt.plot(thetas,positions)
78 | plt.xlabel('Scan angle (degrees)')
79 | plt.ylabel('Scanning position of the focal spot (µm)')
80 | plt.show()
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
--------------------------------------------------------------------------------
/raytracing/examples/multiProcessingTracing.py:
--------------------------------------------------------------------------------
1 | import envexamples
2 | import time
3 | import numpy
4 |
5 | from raytracing import *
6 | from matplotlib import pyplot, patches
7 |
8 | fobj, backaperture = 5., 5.
9 |
10 | if __name__ == "__main__":
11 |
12 | reps = 1
13 | processes = None
14 |
15 | path = ImagingPath()
16 |
17 | # Objective
18 | space = fobj
19 | path.append(Space(d=space))
20 | path.append(Lens(f=fobj, diameter=backaperture))
21 | path.append(Space(d=fobj))
22 | path.append(Space(d=space))
23 | path.append(Lens(f=fobj, diameter=backaperture))
24 | path.append(Space(d=fobj))
25 | path.append(Space(d=space))
26 | path.append(Lens(f=fobj, diameter=backaperture))
27 | path.append(Space(d=fobj))
28 | path.append(Space(d=space))
29 | path.append(Lens(f=fobj, diameter=backaperture))
30 | path.append(Space(d=fobj))
31 | path.append(Space(d=space))
32 | path.append(Lens(f=fobj, diameter=backaperture))
33 | path.append(Space(d=fobj))
34 | path.append(Space(d=space))
35 | path.append(Lens(f=fobj, diameter=backaperture))
36 | path.append(Space(d=fobj))
37 | path.append(Space(d=space))
38 | path.append(Lens(f=fobj))
39 | path.append(Space(d=fobj))
40 |
41 | test_functions = {
42 | "naive" : path.traceManyThrough,
43 | "chunks-multiprocessing" : path.traceManyThroughInParallel
44 | }
45 |
46 | fig, ax = pyplot.subplots()
47 | width = 1 / (len(test_functions) + 1)
48 | possible_rays = [1e+2, 1e+3, 1e+4,1e+5]
49 | for i, nRays in enumerate(possible_rays):
50 | nRays = int(nRays)
51 | inputRays = RandomUniformRays(yMax=0, maxCount=nRays)
52 | output = {}
53 | for name, func in test_functions.items():
54 | times = []
55 | for _ in range(reps):
56 | start = time.time()
57 | try:
58 | outputRays = func(inputRays, processes=processes)
59 | except TypeError:
60 | outputRays = func(inputRays)
61 | times.append(time.time() - start)
62 | output[name] = times
63 | xs = i + numpy.arange(len(test_functions)) / (len(test_functions) + 1)
64 | ax.bar(xs, [numpy.mean(values) for values in output.values()],
65 | yerr=[numpy.std(values) for values in output.values()],
66 | width=width, align="edge", color=["tab:blue", "tab:orange", "tab:green", "tab:red"])
67 | ax.set(
68 | xticks = numpy.arange(len(possible_rays)) + len(test_functions) * width / 2,
69 | xticklabels = [f"{int(nRays)}" for nRays in possible_rays],
70 | xlabel = "Number of propagated rays",
71 | ylabel = "Calculation time (s)"
72 | )
73 | ax.legend(handles = [patches.Patch(facecolor=color, label='Color Patch') for color in ["tab:blue", "tab:orange", "tab:green", "tab:red"]],
74 | labels = list(test_functions.keys()))
75 | fig.savefig("parallel_time_smallcounts.png", transparent=True, bbox_inches="tight")
76 | pyplot.show()
77 |
78 | ######################################################################
79 | # Uncomment the following lines to calculate for more rays
80 | ######################################################################
81 |
82 | # fig, ax = pyplot.subplots()
83 | # width = 1 / (len(test_functions) + 1)
84 | # possible_rays = [1e+5, 1e+6]
85 | # for i, nRays in enumerate(possible_rays):
86 | # nRays = int(nRays)
87 | # inputRays = RandomUniformRays(yMax=0, maxCount=nRays)
88 | # output = {}
89 | # for name, func in test_functions.items():
90 | # times = []
91 | # for _ in range(reps):
92 | # start = time.time()
93 | # outputRays = func(inputRays)
94 | # times.append(time.time() - start)
95 | # output[name] = times
96 | # xs = i + numpy.arange(len(test_functions)) / (len(test_functions) + 1)
97 | # ax.bar(xs, [numpy.mean(values) for values in output.values()],
98 | # yerr=[numpy.std(values) for values in output.values()],
99 | # width=width, align="edge", color=["tab:blue", "tab:orange", "tab:green", "tab:red"])
100 | # ax.set(
101 | # xticks = numpy.arange(len(possible_rays)) + len(test_functions) * width / 2,
102 | # xticklabels = [f"{int(nRays)}" for nRays in possible_rays],
103 | # xlabel = "Number of propagated rays",
104 | # ylabel = "Calculation time (s)"
105 | # )
106 | # ax.legend(handles = [patches.Patch(facecolor=color, label='Color Patch') for color in ["tab:blue", "tab:orange", "tab:green", "tab:red"]],
107 | # labels = list(test_functions.keys()))
108 | # fig.savefig("parallel_time.pdf", transparent=True, bbox_inches="tight")
109 | # pyplot.show()
110 |
--------------------------------------------------------------------------------
/raytracing/examples/parallel_time_smallcounts.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DCC-Lab/RayTracing/9609b55f16c8ce4499c85a2622619f60b7675fa3/raytracing/examples/parallel_time_smallcounts.pdf
--------------------------------------------------------------------------------
/raytracing/examples/parallel_time_smallcounts.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DCC-Lab/RayTracing/9609b55f16c8ce4499c85a2622619f60b7675fa3/raytracing/examples/parallel_time_smallcounts.png
--------------------------------------------------------------------------------
/raytracing/examples/randomLambertianRays.py:
--------------------------------------------------------------------------------
1 | import envexamples
2 | from raytracing import *
3 |
4 | nRays = 1000000 # Increase for better resolution
5 | minHeight = -5
6 | maxHeight = 5
7 |
8 | # Lambertian: distribution proportional to cos theta
9 | inputRays = RandomLambertianRays(yMin = minHeight,
10 | yMax = maxHeight,
11 | maxCount = nRays)
12 | inputRays.display()
13 |
--------------------------------------------------------------------------------
/raytracing/examples/randomUniformRays.py:
--------------------------------------------------------------------------------
1 | import envexamples
2 | from raytracing import *
3 |
4 | nRays = 1000000 # Increase for better resolution
5 | minHeight = -5
6 | maxHeight = 5
7 | minTheta = -0.5 # rad
8 | maxTheta = +0.5
9 |
10 | # define a list of rays with uniform distribution
11 | inputRays = RandomUniformRays(yMin = minHeight,
12 | yMax = maxHeight,
13 | maxCount = nRays,
14 | thetaMax = maxTheta,
15 | thetaMin = minTheta)
16 | inputRays.display()
17 |
--------------------------------------------------------------------------------
/raytracing/examples/sourceCollection.py:
--------------------------------------------------------------------------------
1 | import envexamples
2 | from raytracing import *
3 |
4 | path = ImagingPath(label="Original setup")
5 | path.append(Space(d=100))
6 | path.append(Lens(f=35,diameter=25))
7 | path.append(Space(d=35))
8 | path.append(Aperture(diameter=0.2))
9 | path.displayWithObject(diameter=1, removeBlocked=False)
10 | path.reportEfficiency(objectDiameter=1, emissionHalfAngle=1.57)
11 |
12 | path2 = ImagingPath(label="Better system imaging the emission spot on detector")
13 | path2.append(Space(d=70))
14 | path2.append(Lens(f=35,diameter=25))
15 | path2.append(Space(d=70))
16 | path2.append(Aperture(diameter=0.2))
17 | path2.displayWithObject(diameter=1, removeBlocked=False)
18 | path2.reportEfficiency(objectDiameter=1, emissionHalfAngle=1.57)
19 |
20 | path3 = ImagingPath(label="Best system and larger NA imaging spot on detector")
21 | path3.objectHeight=1
22 | path3.append(System4f(f1=35, diameter1=25, f2=35, diameter2=35))
23 | path3.append(Aperture(diameter=0.2))
24 | path3.displayWithObject(diameter=1, removeBlocked=False)
25 | #path3.reportEfficiency(objectDiameter=1, emissionHalfAngle=1.57)
26 |
--------------------------------------------------------------------------------
/raytracing/examples/template.py:
--------------------------------------------------------------------------------
1 | TITLE = ""
2 | DESCRIPTION = """
3 | """
4 |
5 | from raytracing import *
6 |
7 | def exampleCode():
8 | pass
9 | # path.display(comments=DESCRIPTION)
10 |
11 | if __name__ == "__main__":
12 | exampleCode()
--------------------------------------------------------------------------------
/raytracing/examples/twoPhotonDescannedDetector.py:
--------------------------------------------------------------------------------
1 | import envexamples
2 | from raytracing import *
3 | import matplotlib.pyplot as plt
4 |
5 | """ The emitted light in some optical systems, like two-photon descanned
6 | detector, is scattered in all directions. The size of the diffused light
7 | dictates which lenses and detector size to choose. Therefore, it is of great
8 | importance to find a well-sized detector that fits the optical system. """
9 |
10 | # Defines the path. d1 and d2 are the diameter of the lenses, fl1 and fl2 are the focal lengths and d3 is the diameter of the aperture.
11 | def imagingPath(fl1=10, d1=10, fl2=10, d2=10, d3=10, title=""):
12 |
13 | path = ImagingPath()
14 | path.label=title
15 | path.append(System4f(f1=fl1, diameter1=d1, f2=fl2, diameter2=d2))
16 | path.append(Aperture(diameter=d3, label='Detector'))
17 |
18 | return path
19 |
20 | def exampleCode():
21 | nRays = 100000
22 | minHeight=-0.5
23 | maxHeight=0.5
24 | inputRays = RandomLambertianRays(yMax=maxHeight, yMin=minHeight, maxCount=nRays)
25 |
26 | # Three paths with different sets of lens and aperture.
27 |
28 | ###
29 | path1 = imagingPath(fl1=75, fl2=75, d1=50, d2=75, d3=0.5, title="")
30 | outputRays1 = path1.traceManyThrough(inputRays, progress=False)
31 | efficiency1 = 100*outputRays1.count/inputRays.count
32 | path1.display(limitObjectToFieldOfView=False, onlyPrincipalAndAxialRays=True)
33 | outputRays1.display("Output profile {0:.0f}% efficiency".format(efficiency1), showTheta=False)
34 | print(efficiency1)
35 |
36 | ###
37 | path2 = imagingPath(fl1=50, fl2=50, d1=25, d2=50, d3=0.5, title="")
38 | outputRays2 = path2.traceManyThrough(inputRays, progress=False)
39 | efficiency2 = 100*outputRays2.count/inputRays.count
40 | path2.display(limitObjectToFieldOfView=False, onlyPrincipalAndAxialRays=True)
41 | outputRays2.display("Output profile {0:.0f}% efficiency".format(efficiency2), showTheta=False)
42 | print(efficiency2)
43 |
44 | ###
45 | path3 = imagingPath(fl1=50, fl2=50, d1=25, d2=50, d3=1, title="")
46 | outputRays3 = path3.traceManyThrough(inputRays, progress=False)
47 | efficiency3 = 100*outputRays3.count/inputRays.count
48 | path3.display(limitObjectToFieldOfView=False, onlyPrincipalAndAxialRays=True)
49 | outputRays3.display("Output profile {0:.0f}% efficiency".format(efficiency3), showTheta=False)
50 | print(efficiency3)
51 |
52 |
53 | if __name__ == "__main__":
54 | exampleCode()
--------------------------------------------------------------------------------
/raytracing/interface.py:
--------------------------------------------------------------------------------
1 | class Interface:
2 | def __init__(self, L=0.0, n=1.0):
3 | self.n = n
4 | self.L = L
5 |
6 |
7 | class SphericalInterface(Interface):
8 | def __init__(self, R: float, L=0.0, n=1.0):
9 | super(SphericalInterface, self).__init__(L=L, n=n)
10 | self.R = R
11 |
12 |
13 | class FlatInterface(SphericalInterface):
14 | def __init__(self, L=0.0, n=1.0):
15 | super(FlatInterface, self).__init__(R=float("+inf"), L=L, n=n)
16 |
17 |
18 | class ConicalInterface(Interface):
19 | def __init__(self, alpha: float, L=0.0, n=1.0):
20 | super(ConicalInterface, self).__init__(L=L, n=n)
21 | self.alpha = alpha
22 |
--------------------------------------------------------------------------------
/raytracing/lasercavity.py:
--------------------------------------------------------------------------------
1 | from .matrixgroup import *
2 | from .imagingpath import *
3 | from .laserpath import *
4 | import warnings
5 |
6 | class LaserCavity(LaserPath):
7 | """A laser cavity (i.e. a resonator). The beam is considered to go
8 | through all elements from first to last, then again through the first element.
9 |
10 |
11 | Usage is to create the LaserCavity(), then append() elements
12 | and display(). You need to include the elements twice: forward then backward
13 | propagation.
14 |
15 | Parameters
16 | ----------
17 | elements : list of elements
18 | A list of ABCD matrices in the laser cavity. Must include both forward and backward.
19 | You can use the copyReversedElements() function to help. Do not copy the last mirror twice.
20 | label : string
21 | the label for the laser cavity (Optional)
22 |
23 | Attributes
24 | ----------
25 | showElementLabels : bool
26 | If True, the labels of the elements will be shown on display. (default=True)
27 | showPointsOfInterest : bool
28 | If True, the points of interest will be shown on display. (default=True)
29 | showPointsOfInterestLabels : bool
30 | If True, the labels of the points of interest will be shown on display. (default=True)
31 | showPlanesAcrossPointsOfInterest : bool
32 | If True, the planes across the points of interest will be shown on display. (default=True)
33 |
34 | See Also
35 | --------
36 | raytracing.GaussianBeam
37 |
38 | Notes
39 | -----
40 |
41 | """
42 |
43 | def __init__(self, elements=None, label="Laser cavity"):
44 | super(LaserCavity, self).__init__(elements=elements, label=label)
45 |
46 | def appendElementsInReverse(self, skipFirstElement=False, skipLastElement=False):
47 | if skipFirstElement:
48 | firstElement = 1
49 | else:
50 | firstElement = 0
51 |
52 | if skipLastElement:
53 | forwardElements = self.elements[firstElement:-1]
54 | else:
55 | forwardElements = self.elements[firstElement:]
56 |
57 | for element in reversed(forwardElements):
58 | self.elements.append(element)
59 |
60 |
61 | def eigenModes(self):
62 | """
63 | Returns the two complex radii that are identical after a
64 | round trip, assuming the matrix of the LaserCavity() is one
65 | round trip: you will need to duplicate elements in reverse
66 | and append them manually.
67 |
68 | Knowing that q = (Aq + B)/(Cq + d), we get
69 | Cq^2 + Dq = Aq + B, therefore:
70 | Cq^2 + (D-A)q - B = 0
71 |
72 | and q = - ((D-A) +- sqrt( (D-A)^2 - 4 C (-B)))/(2C)
73 |
74 | You will typically obtain two values, where only one is physical.
75 | There could be two modes in a laser with complex matrices (i.e.
76 | with gain), but this is not considered here. See "Lasers" by Siegman.
77 | """
78 | if not self.hasPower:
79 | return None, None
80 | b = self.D - self.A
81 | sqrtDelta = cmath.sqrt(b * b - 4.0 * self.C * (-self.B))
82 |
83 | q1 = (- b + sqrtDelta) / (2.0 * self.C)
84 | q2 = (- b - sqrtDelta) / (2.0 * self.C)
85 | return (GaussianBeam(q=q1), GaussianBeam(q=q2))
86 |
87 | def laserModes(self):
88 | """
89 | Returns the laser modes that are physical (finite) when
90 | calculating the eigenmodes.
91 | """
92 |
93 | (q1, q2) = self.eigenModes()
94 | q = []
95 | if q1 is not None and q1.isFinite:
96 | q.append(q1)
97 |
98 | if q2 is not None and q2.isFinite:
99 | q.append(q2)
100 |
101 | return q
102 |
103 | @property
104 | def isStable(self):
105 | beams = self.laserModes()
106 | if len(beams) > 0:
107 | return True
108 |
109 | return False
110 |
111 | def display(self, comments=None): # pragma: no cover
112 | """ Display the optical cavity and trace the laser beam.
113 | If comments are included they will be displayed on a
114 | graph in the bottom half of the plot.
115 |
116 | Parameters
117 | ----------
118 | comments : string
119 | If comments are included they will be displayed on a
120 | graph in the bottom half of the plot. (default=None)
121 |
122 | """
123 |
124 | if not self.isStable:
125 | warnings.warn("`LaserCavity.display()` called with an unstable cavity. No beam will be drawn.", BeginnerHint)
126 | super(LaserCavity, self).display(beams=[])
127 | else:
128 | super(LaserCavity, self).display(beams=self.laserModes())
129 |
--------------------------------------------------------------------------------
/raytracing/laserpath.py:
--------------------------------------------------------------------------------
1 | from .matrixgroup import MatrixGroup
2 | from .figure import Figure
3 |
4 |
5 | class LaserPath(MatrixGroup):
6 | """The main class of the module for coherent
7 | laser beams: it is the combination of Matrix() or MatrixGroup()
8 | to be used as a laser path with a laser beam (GaussianBeam)
9 | at the entrance.
10 |
11 | Usage is to create the LaserPath(), then append() elements
12 | and display(). You may change the inputBeam to any GaussianBeam(),
13 | or provide one to display(beam=GaussianBeam())
14 |
15 | Parameters
16 | ----------
17 | elements : list of elements
18 | A list of ABCD matrices in the imaging path
19 | label : string
20 | the label for the imaging path (Optional)
21 |
22 | Attributes
23 | ----------
24 | inputBeam : object of GaussianBeam class
25 | the input beam of the imaging path is defined using this parameter.
26 | showElementLabels : bool
27 | If True, the labels of the elements will be shown on display. (default=True)
28 | showPointsOfInterest : bool
29 | If True, the points of interest will be shown on display. (default=True)
30 | showPointsOfInterestLabels : bool
31 | If True, the labels of the points of interest will be shown on display. (default=True)
32 | showPlanesAcrossPointsOfInterest : bool
33 | If True, the planes across the points of interest will be shown on display. (default=True)
34 |
35 | See Also
36 | --------
37 | raytracing.GaussianBeam
38 |
39 | Notes
40 | -----
41 | Gaussian laser beams are not "blocked" by aperture. The formalism
42 | does not explicitly allow that. However, if it appears that a
43 | GaussianBeam() would be clipped by finite aperture, a property
44 | is set to indicate it, but it will propagate nevertheless
45 | and without diffraction due to that aperture.
46 | """
47 |
48 | def __init__(self, elements=None, label=""):
49 | self.inputBeam = None
50 | self.showElementLabels = True
51 | self.showPointsOfInterest = True
52 | self.showPointsOfInterestLabels = True
53 | self.showPlanesAcrossPointsOfInterest = True
54 |
55 | self.figure = Figure(opticalPath=self)
56 | self.design = self.figure.design
57 | super(LaserPath, self).__init__(elements=elements, label=label)
58 |
59 | def display(self, beams=None, comments=None): # pragma: no cover
60 | """ Display the optical system and trace the laser beam.
61 | If comments are included they will be displayed on a
62 | graph in the bottom half of the plot.
63 |
64 | Parameters
65 | ----------
66 | inputBeam : object of GaussianBeam class
67 | inputBeams : list of object of GaussianBeam class
68 | A list of Gaussian beams
69 | comments : string
70 | If comments are included they will be displayed on a graph in the bottom half of the plot. (default=None)
71 |
72 | """
73 | if beams is None:
74 | beams = [self.inputBeam]
75 |
76 | self.figure.displayGaussianBeam(beams=beams,
77 | comments=comments, title=self.label, backend='matplotlib', display3D=False)
78 |
--------------------------------------------------------------------------------
/raytracing/nikon.py:
--------------------------------------------------------------------------------
1 | from .specialtylenses import *
2 |
3 | class LWD16X(Objective):
4 | """ Nikon 16x immersion objective
5 |
6 | .. csv-table::
7 | :header: Parameter, value
8 |
9 | "f", "200/16"
10 | "backAperture", "20"
11 | "Numerical Aperture (NA)", "1.56"
12 | "Working Distance (mm)", "3"
13 |
14 | Notes
15 | -----
16 | Immersion not considered at this point.
17 | """
18 |
19 | def __init__(self):
20 | super(LWD16X, self).__init__(f=200/16,
21 | NA=1.95*0.8,
22 | focusToFocusLength=75,
23 | backAperture=20,
24 | workingDistance=3,
25 | label='CFI75 LWD 16x W',
26 | url="https://www.thorlabs.com/thorproduct.cfm?partnumber=N16XLWD-PF")
27 |
--------------------------------------------------------------------------------
/raytracing/preferences.py:
--------------------------------------------------------------------------------
1 | import os
2 | import json
3 | import platform
4 |
5 | class Preferences(dict):
6 | def __init__(self, *args, **kwargs):
7 | self.update(*args, **kwargs)
8 |
9 | prefFilename = "ca.dcclab.python.raytracing.json"
10 | prefDir = "."
11 |
12 | try:
13 | if platform.system() == 'Darwin':
14 | home = os.path.expanduser("~")
15 | prefDir = os.path.join(home, "Library","Preferences")
16 | elif platform.system() == 'Windows':
17 | from win32com.shell import shell, shellcon
18 | prefDir = shell.SHGetFolderPath(0, shellcon.CSIDL_APPDATA, None, 0)
19 | elif platform.system() == 'Linux':
20 | home = os.path.expanduser("~")
21 | prefDir = os.path.join(home, ".config")
22 | if not os.path.exists(prefDir):
23 | os.mkdir(prefDir)
24 | except Exception as err:
25 | prefDir = "."
26 |
27 | self.path = os.path.join(prefDir, prefFilename)
28 | self.readFromDisk()
29 |
30 | def update(self, *args, **kwargs):
31 | for k, v in dict(*args, **kwargs).items():
32 | self[k] = v
33 |
34 | def __getitem__(self, key):
35 | self.readFromDisk()
36 | return super().__getitem__(key)
37 |
38 | def __setitem__(self, key, value):
39 | super().__setitem__(key, value)
40 | self.writeToDisk()
41 |
42 | def resetPreferences(self):
43 | try:
44 | os.remove(self.path)
45 | except Exception as err:
46 | pass
47 | self.clear()
48 | self.writeToDisk()
49 |
50 | def readFromDisk(self):
51 | if os.path.exists(self.path):
52 | with open(self.path, "r+") as prefFile:
53 | try:
54 | data = json.load(prefFile)
55 | except Exception as err:
56 | data = dict()
57 | self.update(data)
58 | else:
59 | self.clear()
60 | self.writeToDisk()
61 |
62 | def writeToDisk(self):
63 | with open(self.path, "w+") as prefFile:
64 | json.dump(self, prefFile)
65 |
--------------------------------------------------------------------------------
/raytracing/specifications/AC254-100-A-Zemax(ZMX).zmx:
--------------------------------------------------------------------------------
1 | VERS 120928 0 30221
2 | MODE SEQ
3 | NAME AC254-100-A AC254-100-A POSITIVE VISIBLE ACHROMATS: Infinite 100
4 | NOTE 0 FOR INFORMATION ONLY, NOT FOR MANUFACTURING. ALL VALUES AND COATING(S) ARE TYPICAL AND THEORETICAL, ACTUAL PERFORMANCE MAY VARY. FOR FURTHER INFORMATION CONTACT CUSTOMER SERVICE AT TECHSUPPORT@THORL
5 | NOTE 0 ABS.COM
6 | PFIL 0 0 0
7 | UNIT MM X W X CM MR CPMM
8 | ENPD 2.286E+1
9 | ENVD 2.0E+1 1 0
10 | GFAC 0 0
11 | GCAT SCHOTT INFRARED MISC
12 | RAIM 0 0 1 1 0 0 0 0 0
13 | PUSH 0 0 0 0 0 0
14 | SDMA 0 1 0
15 | FTYP 0 0 1 3 0 0 0
16 | ROPD 2
17 | PICB 1
18 | XFLN 0 0 0 0 0 0 0 0 0 0 0 0
19 | YFLN 0 0 0 0 0 0 0 0 0 0 0 0
20 | FWGN 1 0 0 0 0 0 0 0 0 0 0 0
21 | VDXN 0 0 0 0 0 0 0 0 0 0 0 0
22 | VDYN 0 0 0 0 0 0 0 0 0 0 0 0
23 | VCXN 0 0 0 0 0 0 0 0 0 0 0 0
24 | VCYN 0 0 0 0 0 0 0 0 0 0 0 0
25 | VANN 0 0 0 0 0 0 0 0 0 0 0 0
26 | WAVM 1 4.861E-1 1
27 | WAVM 2 5.876E-1 1
28 | WAVM 3 6.563E-1 1
29 | WAVM 4 5.5E-1 1
30 | WAVM 5 5.5E-1 1
31 | WAVM 6 5.5E-1 1
32 | WAVM 7 5.5E-1 1
33 | WAVM 8 5.5E-1 1
34 | WAVM 9 5.5E-1 1
35 | WAVM 10 5.5E-1 1
36 | WAVM 11 5.5E-1 1
37 | WAVM 12 5.5E-1 1
38 | WAVM 13 5.5E-1 1
39 | WAVM 14 5.5E-1 1
40 | WAVM 15 5.5E-1 1
41 | WAVM 16 5.5E-1 1
42 | WAVM 17 5.5E-1 1
43 | WAVM 18 5.5E-1 1
44 | WAVM 19 5.5E-1 1
45 | WAVM 20 5.5E-1 1
46 | WAVM 21 5.5E-1 1
47 | WAVM 22 5.5E-1 1
48 | WAVM 23 5.5E-1 1
49 | WAVM 24 5.5E-1 1
50 | PWAV 2
51 | POLS 1 0 1 0 0 1 0
52 | GLRS 1 0
53 | GSTD 0 100.000 100.000 100.000 100.000 100.000 100.000 0 1 1 0 0 1 1 1 1 1 1
54 | NSCD 100 500 0 1.0E-3 5 1.0E-6 0 0 0 0 0 0 1000000 0 2
55 | COFN QF "COATINGTHOR.DAT" "SCATTER_PROFILE.DAT" "ABG_DATA.DAT" "PROFILE.GRD"
56 | COFN COATINGTHOR.DAT SCATTER_PROFILE.DAT ABG_DATA.DAT PROFILE.GRD
57 | SURF 0
58 | TYPE STANDARD
59 | CURV 0.0 0 0 0 0 ""
60 | HIDE 0 0 0 0 0 0 0 0 0 0
61 | MIRR 2 0
62 | SLAB 3
63 | DISZ INFINITY
64 | DIAM 0 0 0 0 1 ""
65 | POPS 0 0 0 0 0 0 0 0 1 1 1 1 0 0 0
66 | SURF 1
67 | COMM AC254-100-A
68 | STOP
69 | TYPE STANDARD
70 | CURV 1.593625498007969800E-002 0 0 0 0 ""
71 | COAT THORA
72 | HIDE 0 0 0 0 0 0 0 0 0 0
73 | MIRR 2 0
74 | SLAB 4
75 | DISZ 4.0
76 | GLAS N-BK7 0 0 0 0 0 0 0 0 0 0
77 | DIAM 1.27E+1 1 0 0 1 ""
78 | POPS 0 0 0 0 0 0 0 0 1 1 1 1 0 0 0
79 | FLAP 0 1.27E+1 0
80 | SURF 2
81 | TYPE STANDARD
82 | CURV -2.187705097352880100E-002 0 0 0 0 ""
83 | HIDE 0 0 0 0 0 0 0 0 0 0
84 | MIRR 2 0
85 | SLAB 5
86 | DISZ 2.5
87 | GLAS SF5 0 0 0 0 0 0 0 0 0 0
88 | DIAM 1.27E+1 1 0 0 1 ""
89 | POPS 0 0 0 0 0 0 0 0 1 1 1 1 0 0 0
90 | FLAP 0 1.27E+1 0
91 | SURF 3
92 | TYPE STANDARD
93 | CURV -7.798487093503899700E-003 0 0 0 0 ""
94 | COAT THORASLAH64
95 | HIDE 0 0 0 0 0 0 0 0 0 0
96 | MIRR 2 0
97 | SLAB 1
98 | DISZ 9.706800996493E+1
99 | DIAM 1.27E+1 1 0 0 1 ""
100 | POPS 0 0 0 0 0 0 0 0 1 1 1 1 0 0 0
101 | FLAP 0 1.27E+1 0
102 | SURF 4
103 | TYPE STANDARD
104 | CURV 0.0 0 0 0 0 ""
105 | HIDE 0 0 0 0 0 0 0 0 0 0
106 | MIRR 2 0
107 | SLAB 2
108 | DISZ 0
109 | DIAM 6.803919974926E-3 0 0 0 1 ""
110 | POPS 0 0 0 0 0 0 0 0 1 1 1 1 0 0 0
111 | BLNK
112 | TOL TOFF 0 0 0 0 0 0 0
113 | MNUM 1 1
114 | MOFF 0 1 "" 0 0 0 1 1 0 0.0 ""
115 |
--------------------------------------------------------------------------------
/raytracing/specifications/AC254-125-A-Zemax(ZMX).zmx:
--------------------------------------------------------------------------------
1 | VERS 120928 106 30221
2 | MODE SEQ
3 | NAME 25.4 mm, f=125.0 mm, Near IR Achromat, ARC: 350-700 nm
4 | NOTE 0 FOR INFORMATION ONLY, NOT FOR MANUFACTURING. ALL VALUES AND COATING(S) ARE TYPICAL AND THEORETICAL, ACTUAL PERFORMANCE MAY VARY. FOR FURTHER INFORMATION CONTACT CUSTOMER SERVICE AT TECHSUPPORT@THORL
5 | NOTE 0 ABS.COM
6 | PFIL 0 0 0
7 | UNIT MM X W X CM MR CPMM
8 | ENPD 2.286E+1
9 | ENVD 2.0E+1 1 0
10 | GFAC 0 0
11 | GCAT SCHOTT INFRARED MISC
12 | RAIM 0 0 1 1 0 0 0 0 0
13 | PUSH 0 0 0 0 0 0
14 | SDMA 0 1 0
15 | FTYP 0 0 1 3 0 0 0
16 | ROPD 2
17 | PICB 1
18 | XFLN 0 0 0 0 0 0 0 0 0 0 0 0
19 | YFLN 0 0 0 0 0 0 0 0 0 0 0 0
20 | FWGN 1 0 0 0 0 0 0 0 0 0 0 0
21 | VDXN 0 0 0 0 0 0 0 0 0 0 0 0
22 | VDYN 0 0 0 0 0 0 0 0 0 0 0 0
23 | VCXN 0 0 0 0 0 0 0 0 0 0 0 0
24 | VCYN 0 0 0 0 0 0 0 0 0 0 0 0
25 | VANN 0 0 0 0 0 0 0 0 0 0 0 0
26 | WAVM 1 4.861E-1 1
27 | WAVM 2 5.876E-1 1
28 | WAVM 3 6.563E-1 1
29 | WAVM 4 5.5E-1 1
30 | WAVM 5 5.5E-1 1
31 | WAVM 6 5.5E-1 1
32 | WAVM 7 5.5E-1 1
33 | WAVM 8 5.5E-1 1
34 | WAVM 9 5.5E-1 1
35 | WAVM 10 5.5E-1 1
36 | WAVM 11 5.5E-1 1
37 | WAVM 12 5.5E-1 1
38 | WAVM 13 5.5E-1 1
39 | WAVM 14 5.5E-1 1
40 | WAVM 15 5.5E-1 1
41 | WAVM 16 5.5E-1 1
42 | WAVM 17 5.5E-1 1
43 | WAVM 18 5.5E-1 1
44 | WAVM 19 5.5E-1 1
45 | WAVM 20 5.5E-1 1
46 | WAVM 21 5.5E-1 1
47 | WAVM 22 5.5E-1 1
48 | WAVM 23 5.5E-1 1
49 | WAVM 24 5.5E-1 1
50 | PWAV 2
51 | POLS 1 0 1 0 0 1 0
52 | GLRS 1 0
53 | GSTD 0 100.000 100.000 100.000 100.000 100.000 100.000 0 1 1 0 0 1 1 1 1 1 1
54 | NSCD 100 500 0 1.0E-3 5 1.0E-6 0 0 0 0 0 0 1000000 0 2
55 | COFN QF "COATINGTHOR.DAT" "SCATTER_PROFILE.DAT" "ABG_DATA.DAT" "PROFILE.GRD"
56 | COFN COATINGTHOR.DAT SCATTER_PROFILE.DAT ABG_DATA.DAT PROFILE.GRD
57 | SURF 0
58 | TYPE STANDARD
59 | CURV 0.0 0 0 0 0 ""
60 | HIDE 0 0 0 0 0 0 0 0 0 0
61 | MIRR 2 0
62 | SLAB 3
63 | DISZ INFINITY
64 | DIAM 0 0 0 0 1 ""
65 | POPS 0 0 0 0 0 0 0 0 1 1 1 1 0 0 0
66 | SURF 1
67 | COMM AC254-125-A
68 | STOP
69 | TYPE STANDARD
70 | CURV 1.288164982260432700E-002 0 0 0 0 ""
71 | COAT THORA
72 | HIDE 0 0 0 0 0 0 0 0 0 0
73 | MIRR 2 0
74 | SLAB 4
75 | DISZ 4.003571813499
76 | GLAS N-BK7 0 0 0 0 0 0 0 0 0 0
77 | DIAM 1.27E+1 1 0 0 1 ""
78 | POPS 0 0 0 0 0 0 0 0 1 1 1 1 0 0 0
79 | FLAP 0 1.27E+1 0
80 | SURF 2
81 | TYPE STANDARD
82 | CURV -1.788258664719243400E-002 0 0 0 0 ""
83 | COAT THORA
84 | HIDE 0 0 0 0 0 0 0 0 0 0
85 | MIRR 2 0
86 | SLAB 5
87 | DISZ 2.833474807849
88 | GLAS N-SF5 0 0 0 0 0 0 0 0 0 0
89 | DIAM 1.27E+1 1 0 0 1 ""
90 | POPS 0 0 0 0 0 0 0 0 1 1 1 1 0 0 0
91 | FLAP 0 1.27E+1 0
92 | SURF 3
93 | TYPE STANDARD
94 | CURV -6.218054603015779400E-003 0 0 0 0 ""
95 | HIDE 0 0 0 0 0 0 0 0 0 0
96 | MIRR 2 0
97 | SLAB 1
98 | DISZ 1.21975878954E+2
99 | MAZH 0 0
100 | DIAM 1.27E+1 1 0 0 1 ""
101 | POPS 0 0 0 0 0 0 0 0 1 1 1 1 0 0 0
102 | FLAP 0 1.27E+1 0
103 | SURF 4
104 | TYPE STANDARD
105 | CURV 0.0 0 0 0 0 ""
106 | HIDE 0 0 0 0 0 0 0 0 0 0
107 | MIRR 2 0
108 | SLAB 2
109 | DISZ 0
110 | DIAM 5.780458450641E-3 0 0 0 1 ""
111 | POPS 0 0 0 0 0 0 0 0 1 1 1 1 0 0 0
112 | BLNK
113 | TOL TOFF 0 0 0 0 0 0 0
114 | MNUM 1 1
115 | MOFF 0 1 "" 0 0 0 1 1 0 0.0 ""
116 |
--------------------------------------------------------------------------------
/raytracing/specifications/ASL5040-UV-Zemax(ZMX).zmx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DCC-Lab/RayTracing/9609b55f16c8ce4499c85a2622619f60b7675fa3/raytracing/specifications/ASL5040-UV-Zemax(ZMX).zmx
--------------------------------------------------------------------------------
/raytracing/specifications/envtest.py:
--------------------------------------------------------------------------------
1 | import sys
2 | import os
3 | import io
4 | from contextlib import redirect_stdout, redirect_stderr
5 | import unittest
6 | import tempfile
7 | import matplotlib.pyplot as plt
8 | from unittest.mock import Mock, patch
9 | import numpy as np
10 | import warnings
11 |
12 | import io
13 | import contextlib
14 |
15 | #Use with patch('matplotlib.pyplot.show', new=Mock()):
16 | # or @patch('matplotlib.pyplot.show', new=Mock())
17 |
18 | perfTestKey = "RAYTRACING_PERF_TESTS"
19 | performanceTests = False
20 | if perfTestKey in os.environ:
21 | performanceTests = os.environ[perfTestKey]
22 | else:
23 | performanceTests = False
24 |
25 | class RaytracingTestCase(unittest.TestCase):
26 | tempDir = os.path.join(tempfile.gettempdir(), "tempDir")
27 | setupCalled = False
28 | stdout = None
29 | stderr = None
30 | def __init__(self, tests=()):
31 | super(RaytracingTestCase, self).__init__(tests)
32 | warnings.simplefilter("ignore")
33 |
34 | def setUp(self):
35 | super().setUp()
36 | warnings.simplefilter("ignore")
37 |
38 | # Seed with same seed every time
39 | np.random.seed(0)
40 |
41 | self.setupCalled = True
42 |
43 | def tearDown(self) -> None:
44 | self.clearMatplotlibPlots()
45 |
46 | def testProperlySetup(self):
47 | self.assertTrue(self.setupCalled, msg="You must call super().setUp() in your own setUp()")
48 |
49 | @classmethod
50 | def clearMatplotlibPlots(cls):
51 | plt.clf()
52 | plt.cla()
53 | plt.close()
54 |
55 | def assertDoesNotRaise(self, func, exceptionType=None, *funcArgs, **funcKwargs):
56 | returnValue = None
57 | if exceptionType is None:
58 | exceptionType = Exception
59 | try:
60 | returnValue = func(*funcArgs, **funcKwargs)
61 | except exceptionType as e:
62 | self.fail(f"An exception was raised:\n{e}")
63 | # Don't handle exceptions not in exceptionType
64 | return returnValue
65 |
66 | def assertPrints(self, func, out, stripOutput: bool = True, *funcArgs, **funcKwargs):
67 | @redirectStdOutToFile
68 | def getOutput():
69 | func(*funcArgs, **funcKwargs)
70 |
71 | value = getOutput()
72 | if stripOutput:
73 | value = value.strip()
74 | self.assertEqual(value, out)
75 |
76 |
77 | @classmethod
78 | def createTempDirectory(cls):
79 | if os.path.exists(cls.tempDir):
80 | cls.deleteTempDirectory()
81 | os.mkdir(cls.tempDir)
82 |
83 | @classmethod
84 | def deleteTempDirectory(cls):
85 | if os.path.exists(cls.tempDir):
86 | for file in os.listdir(cls.tempDir):
87 | os.remove(os.path.join(cls.tempDir, file))
88 | os.rmdir(cls.tempDir)
89 |
90 | @classmethod
91 | def setUpClass(cls) -> None:
92 | cls.createTempDirectory()
93 |
94 | @classmethod
95 | def tearDownClass(cls) -> None:
96 | cls.deleteTempDirectory()
97 |
98 | def tempFilePath(self, filename="temp.dat") -> str:
99 | return os.path.join(RaytracingTestCase.tempDir, filename)
100 |
101 |
102 | def redirectStdOutToFile(_func=None, file=None, returnOnlyValue: bool = True):
103 | if file is None:
104 | file = io.StringIO()
105 |
106 | def redirectStdOut(func):
107 | def wrapperRedirectStdOut(*args, **kwargs):
108 | with redirect_stdout(file):
109 | func(*args, **kwargs)
110 |
111 | return file.getvalue() if returnOnlyValue else file
112 |
113 | return wrapperRedirectStdOut
114 |
115 | if _func is None:
116 | return redirectStdOut
117 | return redirectStdOut(_func)
118 |
119 |
120 | def main():
121 | unittest.main()
122 |
123 |
124 | def skip(reason: str):
125 | return unittest.skip(reason)
126 |
127 |
128 | def skipIf(condition: object, reason: str):
129 | return unittest.skipIf(condition, reason)
130 |
131 |
132 | def skipUnless(condition: object, reason: str):
133 | return unittest.skipUnless(condition, reason)
134 |
135 |
136 | def expectedFailure(func):
137 | return unittest.expectedFailure(func)
138 |
139 | def patchMatplotLib():
140 | return patch('matplotlib.pyplot.show', new=Mock())
141 |
142 | # append module root directory to sys.path
143 | sys.path.insert(0,
144 | os.path.dirname(
145 | os.path.dirname(
146 | os.path.dirname(
147 | os.path.abspath(__file__)
148 | )
149 | )
150 | )
151 | )
152 |
--------------------------------------------------------------------------------
/raytracing/specifications/lensBuilder.py:
--------------------------------------------------------------------------------
1 | import envtest
2 | import csv
3 | from raytracing import *
4 |
5 | """
6 | This is just for experimenting with building lenses from prescriptions.
7 | We use an Olympus patent as a test, since everything is there
8 |
9 | """
10 | class PrescriptionFile:
11 | def __init__(self, filepath, columnId=None):
12 | self.filepath = filepath
13 | self.headers = []
14 | self.rows = self.readRows(self.filepath)
15 |
16 | def readRows(self, filepath):
17 | rows = []
18 | with open(filepath) as csvfile:
19 | dialect = csv.Sniffer().sniff(csvfile.read(1024))
20 | csvfile.seek(0)
21 | if csv.Sniffer().has_header(csvfile.read(1024)):
22 | csvfile.seek(0)
23 | self.headers = csvfile.readline().split(dialect.delimiter)
24 | else:
25 | csvfile.seek(0)
26 | fileReader = csv.reader(csvfile, dialect,
27 | quoting=csv.QUOTE_NONNUMERIC)
28 |
29 | for row in fileReader:
30 | rows.append(row)
31 | return rows
32 |
33 |
34 | file = PrescriptionFile("olympus-table3.csv")
35 |
36 | n1 = 1.33 # water immersion
37 | olympus20xPrescription = MatrixGroup(label="OlympusTest")
38 | for row in file.rows:
39 | i, R, d, n2, eta = row
40 |
41 | try:
42 | n2 = float(n2)
43 | except:
44 | n2 = 1.0
45 |
46 | element1 = DielectricInterface(n1=n1, n2=n2, R=R, diameter=20, label="#int{0}".format(i))
47 | element2 = Space(n=n2, d=float(d), label="space-{0}".format(i))
48 |
49 | olympus20xPrescription.append(element1)
50 | olympus20xPrescription.append(element2)
51 | n1 = n2
52 |
53 | print("--------------")
54 | print("From Raytracing calculations")
55 | print("f = {0:0.3f} mm".format(olympus20xPrescription.effectiveFocalLengths().f1))
56 | print("WD = {0:.3f} mm".format(olympus20xPrescription.frontFocalLength()))
57 | print("--------------")
58 | print("From Olympus patent, Table 3, at https://patents.google.com/patent/US6501603B2/en")
59 | print("f = 9.006 mm")
60 | print("WD = 2.04 mm")
61 |
62 | olympus20xPrescription = CompoundLens(olympus20xPrescription.elements,
63 | designFocalLength=olympus20xPrescription.effectiveFocalLengths().f1,
64 | diameter=20)
65 | olympus20xPrescription.display()
66 |
--------------------------------------------------------------------------------
/raytracing/specifications/olympus-table3.csv:
--------------------------------------------------------------------------------
1 | #int R D n abbe
2 | 1 +inf 1.6100 1.45853 67.94
3 | 2 -2.4060 5.7597 1.75500 52.32
4 | 3 -6.5059 0.2000
5 | 4 -25.5003 3.1500 1.49700 81.08
6 | 5 -10.9323 0.1200
7 | 6 56.1096 6.4800 1.49700 81.08
8 | 7 -11.9889 1.9500 1.52944 51.72
9 | 8 -25.3665 0.1200
10 | 9 223.7456 1.9500 1.75500 52.32
11 | 10 17.0397 6.5000 1.43875 94.99
12 | 11 -25.4913 0.1200
13 | 12 12.1419 4.8264 1.43875 94.99
14 | 13 30.1344 0.2000
15 | 14 10.8233 4.7695 1.43875 94.99
16 | 15 26.5356 1.5024 1.59551 39.21
17 | 16 6.2707 6.1162
18 | 17 -6.9886 1.5000 1.61340 43.84
19 | 18 18.1665 6.3698 1.43875 94.99
20 | 19 -11.5665 0.2500
21 | 20 -26.3050 3.3649 1.49700 81.08
22 | 21 -11.8757 0.1200
23 | 22 10.9414 7.0500 1.43875 94.99
24 | 23 -25.6275 1.6500 1.52944 51.72
25 | 24 8.8733 5.6500
26 | 25 -8.4093 1.7500 1.51633 64.14
27 | 26 95.8203 5.1500 1.61293 36.99
28 | 27 -12.3982 0 1.0 0
--------------------------------------------------------------------------------
/raytracing/specifications/references.md:
--------------------------------------------------------------------------------
1 | # Olympus patents for objectives
2 | # https://patents.google.com/patent/US6501603B2/en
3 | # Table 3
4 |
5 |
--------------------------------------------------------------------------------
/raytracing/specifications/thorlabs.Achromatic_Doublet_AR_Coatings.xlsx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DCC-Lab/RayTracing/9609b55f16c8ce4499c85a2622619f60b7675fa3/raytracing/specifications/thorlabs.Achromatic_Doublet_AR_Coatings.xlsx
--------------------------------------------------------------------------------
/raytracing/specifications/zmax_49270.zmx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DCC-Lab/RayTracing/9609b55f16c8ce4499c85a2622619f60b7675fa3/raytracing/specifications/zmax_49270.zmx
--------------------------------------------------------------------------------
/raytracing/tests/.coveragerc:
--------------------------------------------------------------------------------
1 | # .coveragerc to control coverage.py
2 | [run]
3 | branch = True
4 |
5 | omit =
6 | # omit anything in a .local directory anywhere
7 | */.local/*
8 | # omit everything in /usr
9 | /usr/*
10 | # omit this single file
11 | utils/tirefire.py
12 | tests/*
13 |
14 | [report]
15 | # Regexes for lines to exclude from consideration
16 | exclude_lines =
17 | # Have to re-enable the standard pragma
18 | pragma: no cover
19 |
20 | # Don't complain about missing debug-only code:
21 | def __repr__
22 | if self\.debug
23 |
24 | # Don't complain if tests don't hit defensive assertion code:
25 | raise AssertionError
26 | raise NotImplementedError
27 |
28 | # Don't complain if non-runnable code isn't run:
29 | if 0:
30 | if __name__ == .__main__.:
31 |
32 | ignore_errors = True
33 |
34 | [html]
35 | directory = coverage_html_report
36 |
--------------------------------------------------------------------------------
/raytracing/tests/envtest.py:
--------------------------------------------------------------------------------
1 | import sys
2 | import os
3 | import io
4 | from contextlib import redirect_stdout, redirect_stderr
5 | import unittest
6 | import tempfile
7 | import matplotlib.pyplot as plt
8 | from unittest.mock import Mock, patch
9 | import numpy as np
10 | import warnings
11 |
12 | import io
13 | import contextlib
14 |
15 | #Use with patch('matplotlib.pyplot.show', new=Mock()):
16 | # or @patch('matplotlib.pyplot.show', new=Mock())
17 |
18 | perfTestKey = "RAYTRACING_PERF_TESTS"
19 | performanceTests = False
20 | if perfTestKey in os.environ:
21 | performanceTests = os.environ[perfTestKey]
22 | else:
23 | performanceTests = False
24 |
25 | class RaytracingTestCase(unittest.TestCase):
26 | tempDir = os.path.join(tempfile.gettempdir(), "tempDir")
27 | setupCalled = False
28 | stdout = None
29 | stderr = None
30 | def __init__(self, tests=()):
31 | super(RaytracingTestCase, self).__init__(tests)
32 | warnings.simplefilter("ignore")
33 |
34 | def setUp(self):
35 | super().setUp()
36 | warnings.simplefilter("ignore")
37 |
38 | # Seed with same seed every time
39 | np.random.seed(0)
40 |
41 | self.setupCalled = True
42 |
43 | def tearDown(self) -> None:
44 | self.clearMatplotlibPlots()
45 |
46 | def testProperlySetup(self):
47 | self.assertTrue(self.setupCalled, msg="You must call super().setUp() in your own setUp()")
48 |
49 | @classmethod
50 | def clearMatplotlibPlots(cls):
51 | plt.clf()
52 | plt.cla()
53 | plt.close()
54 |
55 | def assertDoesNotRaise(self, func, exceptionType=None, *funcArgs, **funcKwargs):
56 | returnValue = None
57 | if exceptionType is None:
58 | exceptionType = Exception
59 | try:
60 | returnValue = func(*funcArgs, **funcKwargs)
61 | except exceptionType as e:
62 | self.fail(f"An exception was raised:\n{e}")
63 | # Don't handle exceptions not in exceptionType
64 | return returnValue
65 |
66 | def assertPrints(self, func, out, stripOutput: bool = True, *funcArgs, **funcKwargs):
67 | @redirectStdOutToFile
68 | def getOutput():
69 | func(*funcArgs, **funcKwargs)
70 |
71 | value = getOutput()
72 | if stripOutput:
73 | value = value.strip()
74 | self.assertEqual(value, out)
75 |
76 |
77 | @classmethod
78 | def createTempDirectory(cls):
79 | if os.path.exists(cls.tempDir):
80 | cls.deleteTempDirectory()
81 | os.mkdir(cls.tempDir)
82 |
83 | @classmethod
84 | def deleteTempDirectory(cls):
85 | if os.path.exists(cls.tempDir):
86 | for file in os.listdir(cls.tempDir):
87 | os.remove(os.path.join(cls.tempDir, file))
88 | os.rmdir(cls.tempDir)
89 |
90 | @classmethod
91 | def setUpClass(cls) -> None:
92 | cls.createTempDirectory()
93 |
94 | @classmethod
95 | def tearDownClass(cls) -> None:
96 | cls.deleteTempDirectory()
97 |
98 | def tempFilePath(self, filename="temp.dat") -> str:
99 | return os.path.join(RaytracingTestCase.tempDir, filename)
100 |
101 |
102 | def redirectStdOutToFile(_func=None, file=None, returnOnlyValue: bool = True):
103 | if file is None:
104 | file = io.StringIO()
105 |
106 | def redirectStdOut(func):
107 | def wrapperRedirectStdOut(*args, **kwargs):
108 | with redirect_stdout(file):
109 | func(*args, **kwargs)
110 |
111 | return file.getvalue() if returnOnlyValue else file
112 |
113 | return wrapperRedirectStdOut
114 |
115 | if _func is None:
116 | return redirectStdOut
117 | return redirectStdOut(_func)
118 |
119 |
120 | def main():
121 | unittest.main()
122 |
123 |
124 | def skip(reason: str):
125 | return unittest.skip(reason)
126 |
127 |
128 | def skipIf(condition: object, reason: str):
129 | return unittest.skipIf(condition, reason)
130 |
131 |
132 | def skipUnless(condition: object, reason: str):
133 | return unittest.skipUnless(condition, reason)
134 |
135 |
136 | def expectedFailure(func):
137 | return unittest.expectedFailure(func)
138 |
139 | def patchMatplotLib():
140 | return patch('matplotlib.pyplot.show', new=Mock())
141 |
142 | # append module root directory to sys.path
143 | sys.path.insert(0,
144 | os.path.dirname(
145 | os.path.dirname(
146 | os.path.dirname(
147 | os.path.abspath(__file__)
148 | )
149 | )
150 | )
151 | )
152 |
--------------------------------------------------------------------------------
/raytracing/tests/parallel_time_smallcounts.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DCC-Lab/RayTracing/9609b55f16c8ce4499c85a2622619f60b7675fa3/raytracing/tests/parallel_time_smallcounts.png
--------------------------------------------------------------------------------
/raytracing/tests/shifts-ac254-100-A.txt:
--------------------------------------------------------------------------------
1 | #Wavelength (nm) Focal Length Shift (mm)
2 | 400.00 0.29598107
3 | 402.50 0.27565484
4 | 405.00 0.25646567
5 | 407.50 0.23835086
6 | 410.00 0.22125176
7 | 412.50 0.20511344
8 | 415.00 0.18988444
9 | 417.50 0.17551653
10 | 420.00 0.16196443
11 | 422.50 0.14918564
12 | 425.00 0.13714024
13 | 427.50 0.12579067
14 | 430.00 0.11510164
15 | 432.50 0.1050399
16 | 435.00 0.09557416
17 | 437.50 0.08667492
18 | 440.00 0.07831436
19 | 442.50 0.07046627
20 | 445.00 0.06310586
21 | 447.50 0.05620977
22 | 450.00 0.0497559
23 | 452.50 0.04372337
24 | 455.00 0.03809243
25 | 457.50 0.03284439
26 | 460.00 0.02796156
27 | 462.50 0.02342721
28 | 465.00 0.01922546
29 | 467.50 0.01534127
30 | 470.00 0.01176037
31 | 472.50 0.00846926
32 | 475.00 0.00545508
33 | 477.50 0.00270565
34 | 480.00 0.0002094
35 | 482.50 -0.00204466
36 | 485.00 -0.00406698
37 | 487.50 -0.00586748
38 | 490.00 -0.00745563
39 | 492.50 -0.00884039
40 | 495.00 -0.01003033
41 | 497.50 -0.01103358
42 | 500.00 -0.01185788
43 | 502.50 -0.01251063
44 | 505.00 -0.01299886
45 | 507.50 -0.01332926
46 | 510.00 -0.01350824
47 | 512.50 -0.01354188
48 | 515.00 -0.013436
49 | 517.50 -0.01319614
50 | 520.00 -0.0128276
51 | 522.50 -0.01233544
52 | 525.00 -0.01172447
53 | 527.50 -0.0109993
54 | 530.00 -0.01016434
55 | 532.50 -0.00922379
56 | 535.00 -0.00818168
57 | 537.50 -0.00704185
58 | 540.00 -0.00580796
59 | 542.50 -0.00448355
60 | 545.00 -0.00307196
61 | 547.50 -0.00157642
62 | 550.00 0
63 | 552.50 0.00165435
64 | 555.00 0.0033838
65 | 557.50 0.00518567
66 | 560.00 0.00705736
67 | 562.50 0.00899639
68 | 565.00 0.01100039
69 | 567.50 0.01306709
70 | 570.00 0.0151943
71 | 572.50 0.01737994
72 | 575.00 0.01962201
73 | 577.50 0.02191858
74 | 580.00 0.02426782
75 | 582.50 0.02666795
76 | 585.00 0.02911729
77 | 587.50 0.0316142
78 | 590.00 0.03415714
79 | 592.50 0.0367446
80 | 595.00 0.03937515
81 | 597.50 0.04204741
82 | 600.00 0.04476005
83 | 602.50 0.04751181
84 | 605.00 0.05030147
85 | 607.50 0.05312786
86 | 610.00 0.05598985
87 | 612.50 0.05888636
88 | 615.00 0.06181636
89 | 617.50 0.06477884
90 | 620.00 0.06777286
91 | 622.50 0.07079749
92 | 625.00 0.07385184
93 | 627.50 0.07693508
94 | 630.00 0.08004637
95 | 632.50 0.08318495
96 | 635.00 0.08635004
97 | 637.50 0.08954094
98 | 640.00 0.09275694
99 | 642.50 0.09599737
100 | 645.00 0.0992616
101 | 647.50 0.10254899
102 | 650.00 0.10585896
103 | 652.50 0.10919093
104 | 655.00 0.11254436
105 | 657.50 0.11591871
106 | 660.00 0.11931348
107 | 662.50 0.12272818
108 | 665.00 0.12616233
109 | 667.50 0.12961549
110 | 670.00 0.13308723
111 | 672.50 0.13657711
112 | 675.00 0.14008475
113 | 677.50 0.14360975
114 | 680.00 0.14715175
115 | 682.50 0.15071038
116 | 685.00 0.1542853
117 | 687.50 0.15787619
118 | 690.00 0.16148272
119 | 692.50 0.1651046
120 | 695.00 0.16874151
121 | 697.50 0.1723932
122 | 700.00 0.17605937
--------------------------------------------------------------------------------
/raytracing/tests/shifts-ac254-200-A.txt:
--------------------------------------------------------------------------------
1 | #Wavelength (nm) Focal Length Shift (mm)
2 | 400.00 0.76143878
3 | 402.50 0.7147652
4 | 405.00 0.67061924
5 | 407.50 0.62886167
6 | 410.00 0.5893621
7 | 412.50 0.5519983
8 | 415.00 0.51665566
9 | 417.50 0.48322661
10 | 420.00 0.45161015
11 | 422.50 0.42171134
12 | 425.00 0.39344093
13 | 427.50 0.36671496
14 | 430.00 0.34145436
15 | 432.50 0.31758467
16 | 435.00 0.29503572
17 | 437.50 0.27374134
18 | 440.00 0.25363909
19 | 442.50 0.23467004
20 | 445.00 0.21677854
21 | 447.50 0.19991199
22 | 450.00 0.18402069
23 | 452.50 0.16905763
24 | 455.00 0.15497832
25 | 457.50 0.14174065
26 | 460.00 0.12930475
27 | 462.50 0.11763284
28 | 465.00 0.10668911
29 | 467.50 0.09643963
30 | 470.00 0.08685218
31 | 472.50 0.07789623
32 | 475.00 0.06954275
33 | 477.50 0.06176423
34 | 480.00 0.05453449
35 | 482.50 0.04782868
36 | 485.00 0.04162317
37 | 487.50 0.03589549
38 | 490.00 0.03062429
39 | 492.50 0.02578921
40 | 495.00 0.02137092
41 | 497.50 0.01735099
42 | 500.00 0.01371188
43 | 502.50 0.01043688
44 | 505.00 0.00751005
45 | 507.50 0.00491624
46 | 510.00 0.00264095
47 | 512.50 0.00067041
48 | 515.00 -0.00100856
49 | 517.50 -0.0024085
50 | 520.00 -0.0035414
51 | 522.50 -0.00441871
52 | 525.00 -0.00505133
53 | 527.50 -0.00544969
54 | 530.00 -0.00562377
55 | 532.50 -0.00558307
56 | 535.00 -0.00533668
57 | 537.50 -0.00489331
58 | 540.00 -0.00426126
59 | 542.50 -0.00344846
60 | 545.00 -0.00246253
61 | 547.50 -0.00131073
62 | 550.00 0
63 | 552.50 0.00146299
64 | 555.00 0.00307189
65 | 557.50 0.00482059
66 | 560.00 0.00670327
67 | 562.50 0.00871434
68 | 565.00 0.01084845
69 | 567.50 0.01310047
70 | 570.00 0.01546551
71 | 572.50 0.01793885
72 | 575.00 0.02051601
73 | 577.50 0.02319264
74 | 580.00 0.02596463
75 | 582.50 0.02882801
76 | 585.00 0.03177897
77 | 587.50 0.03481387
78 | 590.00 0.03792921
79 | 592.50 0.04112165
80 | 595.00 0.04438797
81 | 597.50 0.04772508
82 | 600.00 0.05113003
83 | 602.50 0.05459999
84 | 605.00 0.05813223
85 | 607.50 0.06172413
86 | 610.00 0.0653732
87 | 612.50 0.06907703
88 | 615.00 0.07283331
89 | 617.50 0.07663982
90 | 620.00 0.08049445
91 | 622.50 0.08439514
92 | 625.00 0.08833995
93 | 627.50 0.09232698
94 | 630.00 0.09635445
95 | 632.50 0.1004206
96 | 635.00 0.10452378
97 | 637.50 0.10866239
98 | 640.00 0.1128349
99 | 642.50 0.11703984
100 | 645.00 0.12127578
101 | 647.50 0.12554137
102 | 650.00 0.12983532
103 | 652.50 0.13415636
104 | 655.00 0.13850329
105 | 657.50 0.14287497
106 | 660.00 0.14727028
107 | 662.50 0.15168815
108 | 665.00 0.15612758
109 | 667.50 0.16058757
110 | 670.00 0.16506718
111 | 672.50 0.16956551
112 | 675.00 0.1740817
113 | 677.50 0.17861491
114 | 680.00 0.18316434
115 | 682.50 0.18772922
116 | 685.00 0.19230882
117 | 687.50 0.19690242
118 | 690.00 0.20150936
119 | 692.50 0.20612898
120 | 695.00 0.21076066
121 | 697.50 0.21540379
122 | 700.00 0.22005781
123 |
--------------------------------------------------------------------------------
/raytracing/tests/testDisplay.py:
--------------------------------------------------------------------------------
1 | import envtest # modifies path
2 | from raytracing import *
3 | from numpy import linspace, pi
4 | from raytracing.materials import *
5 |
6 | inf = float("+inf")
7 |
8 | def exampleCodeThatUsedToCrashe():
9 | # I created the same lens manually to see what it looked like
10 | # From the prescription, valid only at 405 nm.
11 | asl5040 = MatrixGroup()
12 |
13 | asl5040.append(DielectricInterface(R=17.37,n1=1.0, n2=materials.FusedSilica.n(0.405), diameter=50))
14 | asl5040.append(Space(d=29.85, n=materials.FusedSilica.n(0.405)))
15 | asl5040.append(DielectricInterface(R=float("+inf"),n1=materials.FusedSilica.n(0.405),n2=1.0,diameter=50))
16 |
17 | # The calculated focal length is not 40 mm, it is rather 36.99, probably
18 | # because I do not consider the real, complete surface I only approximate
19 | # with a spherical surface
20 | print("We expect f=40mm from the specs if we use the full surface")
21 | print("f_e = {0}".format(asl5040.effectiveFocalLengths()))
22 | print("FFL = {0}".format(asl5040.frontFocalLength()))
23 | print("BFL = {0}".format(asl5040.backFocalLength()))
24 |
25 |
26 | collimationPath = ImagingPath(label="Lens constructed manually")
27 | # Start at front focal spot where we would put the source to collimate
28 | collimationPath.append(Space(d=asl5040.frontFocalLength()))
29 | collimationPath.append(asl5040)
30 | collimationPath.append(Space(d=50))
31 | # Again, I fixed the bug and will release a new version today or tomorrow.
32 | # I included the graph in the email I sent you. It looks the same and behaves as expected
33 | collimationPath.display(ObjectRays(diameter=1, halfAngle=0.5,H=5,T=5))
34 |
35 | class TestDisplay(envtest.RaytracingTestCase):
36 | def testIfFixedDoesNotCrash(self):
37 | exampleCodeThatUsedToCrashe()
38 |
39 |
40 | if __name__ == '__main__':
41 | envtest.main()
42 |
43 |
--------------------------------------------------------------------------------
/raytracing/tests/testsAxicon.py:
--------------------------------------------------------------------------------
1 | import envtest
2 |
3 | from raytracing import *
4 | from numpy import random
5 | from numpy import *
6 |
7 | inf = float("+inf")
8 | degrees = math.pi/180
9 |
10 | class TestAxicon(envtest.RaytracingTestCase):
11 |
12 | def testAxicon(self):
13 | n = 1.5
14 | alpha = 2.6*degrees
15 | diameter = 100
16 | label = "Axicon"
17 | axicon = Axicon(alpha, n, diameter, label)
18 | self.assertEqual(axicon.n, n)
19 | self.assertEqual(axicon.alpha, alpha)
20 | self.assertEqual(axicon.apertureDiameter, diameter)
21 | self.assertEqual(axicon.label, label)
22 | self.assertEqual(axicon.frontIndex, 1.0)
23 | self.assertEqual(axicon.backIndex, 1.0)
24 |
25 | def testDeviationAngleIs0(self):
26 | n = 1
27 | alpha = random.randint(2000, 5000, 1).item() / 1000*degrees
28 | axicon = Axicon(alpha, n)
29 | self.assertEqual(axicon.deviationAngle(), 0)
30 |
31 | def testDeviationAngleIs0Too(self):
32 | n = random.randint(1000, 3000, 1).item() / 1000
33 | alpha = 0*degrees
34 | axicon = Axicon(alpha, n)
35 | self.assertEqual(axicon.deviationAngle(), 0)
36 |
37 | def testDeviationAngle(self):
38 | n = 1.33
39 | alpha = 4*degrees
40 | axicon = Axicon(alpha, n)
41 | self.assertAlmostEqual(axicon.deviationAngle(), 1.32*degrees, places=15)
42 |
43 | def testFocalLineLengthYIsNoneAndInfiniteDiameter(self):
44 | n = random.randint(1000, 3000, 1).item() / 1000
45 | alpha = random.randint(2000, 5000, 1).item() / 1000*degrees
46 | axicon = Axicon(alpha, n)
47 | self.assertEqual(axicon.focalLineLength(), inf)
48 |
49 | def testFocalLineLengthYIsNone(self):
50 | n = 1.5
51 | alpha = 2.6*degrees
52 | axicon = Axicon(alpha, n, 100)
53 | y = 50
54 | L = y/tan(axicon.deviationAngle())
55 | self.assertAlmostEqual(axicon.focalLineLength(), L,0)
56 |
57 | def testFocalLineLengthSignOfY(self):
58 | n = 1.43
59 | alpha = 1.95*degrees
60 | axicon = Axicon(alpha=alpha, n=n, diameter=100)
61 | self.assertAlmostEqual(axicon.focalLineLength(-2), axicon.focalLineLength(2))
62 |
63 | def testFocalLineLengthPositiveY(self):
64 | n = 1.43
65 | alpha = 1.95*degrees
66 | axicon = Axicon(alpha, n, 100)
67 | y = 2
68 | L = y/tan(axicon.deviationAngle())
69 | self.assertAlmostEqual(axicon.focalLineLength(y), L, 1)
70 |
71 | def testHighRayIsDeviatedDown(self):
72 | ray = Ray(10, 0)
73 | n = 1.1
74 | alpha = 2.56*degrees
75 | axicon = Axicon(alpha, n, 50)
76 | outputRay = axicon*ray
77 | self.assertEqual(outputRay.theta, -axicon.deviationAngle())
78 | self.assertTrue(outputRay.theta < 0)
79 |
80 | def testLowRayIsDeviatedUp(self):
81 | ray = Ray(-10, 0)
82 | n = 1.1
83 | alpha = 2.56*degrees
84 | axicon = Axicon(alpha, n, 50)
85 | outputRay = axicon*ray
86 | self.assertEqual(outputRay.theta, axicon.deviationAngle())
87 | self.assertTrue(outputRay.theta > 0)
88 |
89 | @envtest.expectedFailure
90 | def testMulMatrix(self):
91 | matrix = Matrix()
92 | axicon = Axicon(2.6543, 1.2*degrees)
93 | with self.assertRaises(TypeError):
94 | axicon.mul_matrix(matrix)
95 |
96 | @envtest.expectedFailure
97 | def testDifferentMultiplications(self):
98 | ray = Ray()
99 | beam = GaussianBeam(w=1, R=10, n=1.67)
100 | matrix = Matrix()
101 | axicon = Axicon(4.3, 1.67*degrees)
102 | self.assertIsNotNone(axicon * ray)
103 | with self.assertRaises(TypeError):
104 | axicon * beam
105 |
106 | with self.assertRaises(TypeError):
107 | axicon * matrix
108 |
109 | if __name__ == '__main__':
110 | envtest.main()
111 |
--------------------------------------------------------------------------------
/raytracing/tests/testsCallOtherScript.py:
--------------------------------------------------------------------------------
1 | import envtest
2 | import sys
3 | import subprocess
4 | import os
5 |
6 |
7 | class TestCallScript(envtest.RaytracingTestCase):
8 | def setUp(self):
9 | super().setUp()
10 | self.exec = sys.executable
11 | self.encoding = sys.stdout.encoding
12 | self.emptyFile = self.tempFilePath('script.py')
13 | open(self.emptyFile, "w").close()
14 | self.assertTrue(os.path.exists(self.emptyFile))
15 |
16 | self.printHelloWorld = self.tempFilePath('helloWorld.py')
17 | with open(self.printHelloWorld, "w") as helloWorld:
18 | helloWorld.write('''print("hello world")''')
19 |
20 | def testCallFileNotFound(self):
21 | file = " fileDoesNotExist.py" # Leading space important
22 |
23 | processReturn = subprocess.run([self.exec, file], capture_output=True)
24 | self.assertEqual(processReturn.returncode, 2)
25 |
26 | def testCallScript(self):
27 | processReturn = subprocess.run([self.exec, self.emptyFile])
28 | self.assertEqual(processReturn.returncode, 0)
29 |
30 | def testGetWhatIsPrinted(self):
31 | processCompleted = subprocess.run([self.exec, self.printHelloWorld], capture_output=True)
32 | printed = processCompleted.stdout.decode(self.encoding)
33 | self.assertEqual(processCompleted.stdout.strip(), b"hello world")
34 |
35 | def testGetWhatIsPrintedWithChildProcesses(self):
36 | code = """
37 | import multiprocessing
38 |
39 | def printHelloName(name):
40 | print(f"Hello {name}")
41 |
42 | if __name__ == "__main__":
43 | inputNames = ["Toto", "Toto Jr."]
44 | processes = 4
45 | with multiprocessing.Pool(processes=processes) as pool:
46 | outputValues = pool.map(printHelloName, inputNames)
47 | """.strip()
48 | with open(self.printHelloWorld, "w") as f:
49 | f.write(code)
50 | processCompleted = subprocess.run([self.exec, self.printHelloWorld], capture_output=True, universal_newlines=True)
51 |
52 | output = processCompleted.stdout
53 | possibility1 = "Hello Toto\nHello Toto Jr.\n"
54 | possibility2 = "Hello Toto Jr.\nHello Toto\n"
55 | self.assertTrue(output == possibility1 or output == possibility2)
56 |
57 |
58 | if __name__ == '__main__':
59 | envtest.main()
60 |
--------------------------------------------------------------------------------
/raytracing/tests/testsComponents.py:
--------------------------------------------------------------------------------
1 | import envtest # modifies path
2 | from raytracing import *
3 |
4 | inf = float("+inf")
5 |
6 |
7 | class Test4fSystem(envtest.RaytracingTestCase):
8 |
9 | def test4fSystem(self):
10 | elements = [Space(10), Lens(10), Space(15), Lens(5), Space(5)]
11 | mg = MatrixGroup(elements, label="4f system")
12 | system = System4f(10, 5, label="4f system")
13 | self.assertEqual(system.A, -0.5)
14 | self.assertEqual(system.B, 0)
15 | self.assertEqual(system.C, 0)
16 | self.assertEqual(system.D, -2)
17 | self.assertEqual(system.L, 30)
18 | self.assertEqual(mg.backIndex, system.backIndex)
19 | self.assertEqual(mg.frontIndex, system.frontIndex)
20 | self.assertEqual(mg.backVertex, system.backVertex)
21 | self.assertEqual(mg.frontVertex, system.frontVertex)
22 | self.assertEqual(mg.label, system.label)
23 | self.assertTrue(system.isImaging)
24 |
25 | def test2fSystem(self):
26 | elements = [Space(10), Lens(10), Space(10)]
27 | mg = MatrixGroup(elements, label="2f system")
28 | system = System2f(10, label="2f system")
29 | self.assertEqual(system.A, 0)
30 | self.assertEqual(system.B, 10)
31 | self.assertEqual(system.C, -1 / 10)
32 | self.assertEqual(system.D, 0)
33 | self.assertEqual(system.L, 20)
34 | self.assertEqual(mg.backIndex, system.backIndex)
35 | self.assertEqual(mg.frontIndex, system.frontIndex)
36 | self.assertEqual(mg.backVertex, system.backVertex)
37 | self.assertEqual(mg.frontVertex, system.frontVertex)
38 | self.assertEqual(mg.label, system.label)
39 | self.assertFalse(system.isImaging)
40 |
41 | def test4fIsTwo2f(self):
42 | f1, f2 = 10, 12
43 | system4f = System4f(f1=10, f2=12)
44 | system2f_1 = System2f(f1)
45 | system2f_2 = System2f(f2)
46 | composed4fSystem = MatrixGroup(system2f_1.elements + system2f_2.elements)
47 | self.assertEqual(composed4fSystem.A, system4f.A)
48 | self.assertEqual(composed4fSystem.B, system4f.B)
49 | self.assertEqual(composed4fSystem.C, system4f.C)
50 | self.assertEqual(composed4fSystem.D, system4f.D)
51 | self.assertEqual(composed4fSystem.L, system4f.L)
52 | self.assertEqual(composed4fSystem.backIndex, system4f.backIndex)
53 | self.assertEqual(composed4fSystem.frontIndex, system4f.frontIndex)
54 | self.assertEqual(composed4fSystem.backVertex, system4f.backVertex)
55 | self.assertEqual(composed4fSystem.frontVertex, system4f.frontVertex)
56 |
57 |
58 | if __name__ == '__main__':
59 | envtest.main()
60 |
--------------------------------------------------------------------------------
/raytracing/tests/testsDecorators.py:
--------------------------------------------------------------------------------
1 | import envtest
2 | from raytracing.utils import deprecated
3 |
4 |
5 | def simpleDecoratorReturnsTuple(func):
6 | def wrapper(*args, **kwargs):
7 | return func(*args, **kwargs), func(*args, **kwargs)
8 |
9 | return wrapper
10 |
11 |
12 | def decoratorWithOneArgument(arg):
13 | def simpleDecoratorReturnsTuple(func):
14 | def wrapper(*args, **kwargs):
15 | return arg, func(*args, **kwargs)
16 |
17 | return wrapper
18 |
19 | return simpleDecoratorReturnsTuple
20 |
21 |
22 | class TestDecorators(envtest.RaytracingTestCase):
23 |
24 | def testSimpleDecorator(self):
25 | @simpleDecoratorReturnsTuple
26 | def toto(x):
27 | return x * x
28 |
29 | self.assertTupleEqual(toto(2), (4, 4))
30 |
31 | def testDecoratorWithOneArgument(self):
32 | @decoratorWithOneArgument(0)
33 | def toto(a, b, c):
34 | return a, b, c
35 |
36 | self.assertTupleEqual(toto(1, 2, 3), (0, (1, 2, 3)))
37 |
38 |
39 | class TestDeprecatedDecorator(envtest.RaytracingTestCase):
40 |
41 | def testDeprecated(self):
42 | reason = "This is deprecated because it is a test."
43 |
44 | @deprecated(reason)
45 | def deprecatedFunction():
46 | return "This is deprecated"
47 |
48 | with self.assertWarns(DeprecationWarning) as deprec:
49 | retVal = deprecatedFunction()
50 |
51 | self.assertEqual(retVal, "This is deprecated")
52 | self.assertEqual(str(deprec.warning), reason)
53 |
--------------------------------------------------------------------------------
/raytracing/tests/testsDocTestModule.py:
--------------------------------------------------------------------------------
1 | import envtest
2 | import doctest
3 | import raytracing
4 | from unittest.mock import Mock, patch
5 |
6 | import io
7 | import contextlib
8 |
9 | ## if a new python file is added to the module, please add it in a new line
10 |
11 | ferr = io.StringIO()
12 | fout = io.StringIO()
13 | contextlib.redirect_stderr(ferr)
14 | contextlib.redirect_stdout(fout)
15 |
16 | with patch('matplotlib.pyplot.show', new=Mock()):
17 | doctest.testmod(m=raytracing.axicon,verbose=False)
18 | doctest.testmod(m=raytracing.components,verbose=False)
19 | doctest.testmod(m=raytracing.eo,verbose=False)
20 | doctest.testmod(m=raytracing.figure,verbose=False)
21 | doctest.testmod(m=raytracing.gaussianbeam,verbose=False)
22 | doctest.testmod(m=raytracing.imagingpath,verbose=False)
23 | doctest.testmod(m=raytracing.lasercavity,verbose=False)
24 | doctest.testmod(m=raytracing.laserpath,verbose=False)
25 | doctest.testmod(m=raytracing.materials,verbose=False)
26 | doctest.testmod(m=raytracing.matrix,verbose=False)
27 | doctest.testmod(m=raytracing.matrixgroup,verbose=False)
28 | doctest.testmod(m=raytracing.ray,verbose=False)
29 | doctest.testmod(m=raytracing.rays,verbose=False)
30 | doctest.testmod(m=raytracing.specialtylenses,verbose=False)
31 | doctest.testmod(m=raytracing.utils,verbose=False)
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/raytracing/tests/testsExamples.py:
--------------------------------------------------------------------------------
1 | import envtest # modifies path
2 | import subprocess
3 | import matplotlib as mpl
4 | mpl.use('Agg')
5 | from matplotlib import patches, transforms
6 | from unittest.mock import Mock, patch
7 |
8 | from raytracing import *
9 |
10 | class TestExamples(envtest.RaytracingTestCase):
11 |
12 | def testRegex(self):
13 | pattern = r'^(ex\d+|fig.+)\.py$'
14 | matchObj = re.match(pattern, "fig8-bla.py")
15 | self.assertIsNotNone(matchObj)
16 | self.assertIsNotNone(matchObj.group(1) == 'fig8-bla')
17 | matchObj = re.match(pattern, "ex08.py")
18 | self.assertIsNotNone(matchObj)
19 | self.assertIsNotNone(matchObj.group(1) == 'ex08')
20 |
21 | def testExamplesArePresent(self):
22 | import raytracing.examples as ex
23 | self.assertTrue(len(ex.short) > 0)
24 |
25 | @envtest.skipUnless(envtest.performanceTests, "Skipping long performance tests")
26 | @patch('matplotlib.pyplot.show', new=Mock())
27 | def testExamplesRun(self):
28 | import raytracing.examples as ex
29 | for ex in ex.short:
30 | self.assertTrue(len(ex["title"])!=0)
31 | self.assertTrue(len(ex["sourceCode"])!=0)
32 | print(".", end='', file=sys.stderr)
33 | print(ex["name"], end='', file=sys.stderr)
34 | with envtest.redirect_stdout(self.stdout):
35 | ex["code"]()
36 |
37 | def testExamplesHaveSrcCode(self):
38 | import raytracing.examples as ex
39 | for ex in ex.short:
40 | self.assertTrue(len(ex["sourceCode"])!=0)
41 |
42 | def testExamplesHaveBmpSrcCode(self):
43 | import raytracing.examples as ex
44 | for ex in ex.short:
45 | self.assertIsNotNone(ex["bmpSourceCode"])
46 |
47 | @envtest.skipUnless(envtest.performanceTests, "Skipping long performance tests")
48 | def testScriptsRun(self):
49 | import raytracing.examples as ex
50 | for scripts in ex.long:
51 | err = subprocess.run([sys.executable, scripts["path"]], capture_output=True)
52 | self.assertTrue(err == 0)
53 |
54 |
55 |
56 | if __name__ == '__main__':
57 | envtest.main()
58 |
--------------------------------------------------------------------------------
/raytracing/tests/testsGaussian.py:
--------------------------------------------------------------------------------
1 | import envtest # modifies path
2 | from raytracing import *
3 |
4 | inf = float("+inf")
5 |
6 |
7 | class TestBeam(envtest.RaytracingTestCase):
8 | def testBeam(self):
9 | beam = GaussianBeam(w=1)
10 | self.assertEqual(beam.w, 1)
11 | self.assertEqual(beam.R, float("+Inf"))
12 | self.assertEqual(beam.wavelength, 0.0006328)
13 |
14 | def testBeamWAndQGiven(self):
15 | w = 1
16 | q = 4.96459e3 * 1j
17 | self.assertDoesNotRaise(GaussianBeam, ValueError, q=q, w=w)
18 |
19 | def testBeamWAndQGivenMismatch(self):
20 | w = 1
21 | q = 4.96459e3 * 1j
22 | q += q * 0.007
23 | with self.assertRaises(ValueError):
24 | GaussianBeam(q, w)
25 |
26 | def testIsInFinite(self):
27 | beam = GaussianBeam(w=inf, R=2)
28 | self.assertFalse(beam.isFinite)
29 |
30 | def testIsFinite(self):
31 | beam = GaussianBeam(w=0.1, R=20)
32 | self.assertTrue(beam.isFinite)
33 |
34 | def testFiniteW(self):
35 | beam = GaussianBeam(w=0.1)
36 | self.assertAlmostEqual(beam.w, 0.1)
37 |
38 | def testInfiniteW(self):
39 | beam = GaussianBeam(w=inf, R=10)
40 | self.assertEqual(beam.w, inf)
41 |
42 | def testNull(self):
43 | beam = GaussianBeam(0)
44 | self.assertFalse(beam.isFinite)
45 | self.assertEqual(beam.w, float("+Inf"))
46 | self.assertEqual(beam.R, float("+Inf"))
47 | self.assertEqual(beam.wavelength, 0.0006328)
48 |
49 | def testZ0is0(self):
50 | beam = GaussianBeam(w=inf, R=1)
51 | self.assertEqual(beam.zo, 0)
52 |
53 | def testZ0isNot0(self):
54 | wavelength = 632.8e-6
55 | beam = GaussianBeam(w=1, R=1, wavelength=wavelength)
56 | a = wavelength / pi
57 | imag = a / (1 + a ** 2)
58 | self.assertAlmostEqual(beam.zo, imag)
59 |
60 | def testWo(self):
61 | beam = GaussianBeam(w=1)
62 | self.assertAlmostEqual(beam.w, beam.wo, 3)
63 | self.assertAlmostEqual(beam.w, beam.waist, 3)
64 |
65 | def testWoIsNone(self):
66 | beam = GaussianBeam(w=inf, R=1)
67 | self.assertIsNone(beam.wo)
68 |
69 | def testInvalidParameters(self):
70 | with self.assertRaises(ValueError) as context:
71 | beam = GaussianBeam()
72 | self.assertEqual(str(context.exception), "Please specify 'q' or 'w'.")
73 |
74 | with self.assertRaises(Exception) as context:
75 | beam = GaussianBeam(w=1, R=0)
76 |
77 | def testMultiplicationBeam(self):
78 | # No default parameters
79 | beamIn = GaussianBeam(w=0.01, R=1, n=1.5, wavelength=0.400e-3)
80 | beamOut = Space(d=0, n=1.5) * beamIn
81 | self.assertEqual(beamOut.q, beamIn.q)
82 | self.assertEqual(beamOut.w, beamIn.w)
83 | self.assertEqual(beamOut.R, beamIn.R)
84 | self.assertEqual(beamOut.n, beamIn.n)
85 | self.assertEqual(beamOut.wavelength, beamIn.wavelength)
86 |
87 | def testDielectricInterfaceBeam(self):
88 | # No default parameters
89 | beamIn = GaussianBeam(w=10, R=inf, n=1.5, wavelength=0.400e-3)
90 | beamOut = DielectricInterface(R=-20, n1=1.5, n2=1.0) * beamIn
91 | self.assertEqual(beamOut.w, beamIn.w)
92 | self.assertEqual(beamOut.n, 1.0)
93 | self.assertEqual(beamOut.wavelength, beamIn.wavelength)
94 |
95 | def testPointBeam(self):
96 | beamIn = GaussianBeam(w=0.0000001)
97 | beamOut = Space(d=100) * beamIn
98 | self.assertEqual(beamOut.R, 100)
99 | self.assertEqual(beamOut.zo, beamIn.zo)
100 | self.assertEqual(beamOut.confocalParameter, beamIn.confocalParameter)
101 | self.assertEqual(beamOut.rayleighRange, beamIn.rayleighRange)
102 |
103 | def testFocalSpot(self):
104 | beamIn = GaussianBeam(w=0.1)
105 | beamOut = Space(d=100) * beamIn
106 | self.assertEqual(beamOut.waistPosition, -100)
107 |
108 | def testStr(self):
109 | self.assertIsNotNone(GaussianBeam(w=0.1).__str__)
110 | self.assertTrue(len(GaussianBeam(w=0.1).__str__()) != 0)
111 |
112 | def testStrInvalidRadiusOfCurvature(self):
113 | beam = GaussianBeam(w=inf, R=1)
114 | self.assertFalse(beam.isFinite)
115 | self.assertEqual(str(beam), "Beam is not finite: q=(1+0j)")
116 |
117 | def testPerformance(self):
118 | path = LaserPath()
119 | path.append(Space(d=100))
120 | beamIn = GaussianBeam(w=0.01, R=1, wavelength=0.400e-3)
121 |
122 | path.trace(beamIn)
123 |
124 |
125 | if __name__ == '__main__':
126 | envtest.main()
127 |
--------------------------------------------------------------------------------
/raytracing/tests/testsLagrangeInvariantIdeas.py:
--------------------------------------------------------------------------------
1 | import envtest # modifies path
2 | from raytracing import *
3 | import numpy as np
4 |
5 | def distHist(rays):
6 | localRays = list(rays)
7 | lagrange = []
8 | while len(localRays) >= 2:
9 | ray1 = localRays.pop()
10 | ray2 = localRays.pop()
11 | lagrange.append(ray1.y*ray2.theta - ray1.theta*ray2.y)
12 | showHistogram(lagrange)
13 |
14 | def histogramValues(values):
15 | counts, binEdges = histogram(values, bins=40, density=True)
16 | ys = list(counts)
17 | xs = []
18 | for i in range(len(binEdges) - 1):
19 | xs.append((binEdges[i] + binEdges[i + 1]) / 2)
20 | return xs, ys
21 |
22 | def showHistogram(values,title=""):
23 | fig, axis1 = plt.subplots(1)
24 | fig.tight_layout(pad=3.0)
25 | xs, ys = histogramValues(values)
26 | axis1.set_title(title)
27 | axis1.plot(xs, (ys), 'ko')
28 | axis1.set_xlabel("Values")
29 | axis1.set_ylabel("Count")
30 | plt.show()
31 |
32 | def printTrace(trace):
33 | for r in trace:
34 | print(r.y, r.theta, r.z, r.isBlocked)
35 | print()
36 |
37 | def rayTraceFromCalculation(ray, principalTrace, axialTrace):
38 | principal = principalTrace[0]
39 | axial = axialTrace[0]
40 |
41 | I12 = principal.theta * axial.y - axial.theta * principal.y
42 | I13 = principal.theta * ray.y - ray.theta * principal.y
43 | I32 = ray.theta * axial.y - axial.theta * ray.y
44 | A = I32/I12
45 | B = I13/I12
46 | trace = []
47 | # print(A,B)
48 | for i in range(len(principalTrace)):
49 | r1 = principalTrace[i]
50 | r2 = axialTrace[i]
51 | r3 = Ray(y=A*r1.y + B*r2.y, theta = A*r1.theta + B*r2.theta)
52 | r3.z = r1.z
53 | trace.append(r3)
54 |
55 | return trace
56 |
57 | class TestLagrange(envtest.RaytracingTestCase):
58 | @envtest.skip
59 | def testLagrange(self):
60 | path = ImagingPath(label="4f system")
61 | path.objectHeight = 50
62 | path.append(System4f(f1=30, diameter1=60, f2=40, diameter2=100))
63 | path.append(Aperture(diameter=10, label='Camera'))
64 | # path.display()
65 | path.reportEfficiency(nRays=10000)
66 |
67 | @envtest.redirectStdOutToFile
68 | @envtest.patch('matplotlib.pyplot.show', new=envtest.Mock())
69 | def testObjective(self):
70 | path = ImagingPath(label="Objective")
71 | path.append(Aperture(diameter=22.5))
72 | path.append(Space(d=180))
73 | path.append(Lens(f=180, diameter=50, label='Tube lens'))
74 | path.append(Space(d=180))
75 | path.append(olympus.XLUMPlanFLN20X())
76 | path.flipOrientation()
77 | self.assertAlmostEqual( abs(path.magnification()[0]), 20)
78 | self.assertAlmostEqual( path.imageSize(), 22.5,3)
79 | path.reportEfficiency(nRays=10000)
80 | path.display(rays=ObjectRays(diameter=10))
81 |
82 | if __name__ == '__main__':
83 | envtest.main()
84 |
--------------------------------------------------------------------------------
/raytracing/tests/testsLaserPath.py:
--------------------------------------------------------------------------------
1 | import envtest # modifies path
2 | from raytracing import *
3 |
4 | inf = float("+inf")
5 |
6 | class TestLaserPath(envtest.RaytracingTestCase):
7 |
8 | def testLaserPathNoElements(self):
9 | lasPath = LaserPath()
10 | self.assertIsNone(lasPath.inputBeam)
11 | self.assertTrue(lasPath.showElementLabels)
12 | self.assertTrue(lasPath.showPointsOfInterest)
13 | self.assertTrue(lasPath.showPointsOfInterestLabels)
14 | self.assertTrue(lasPath.showPlanesAcrossPointsOfInterest)
15 | self.assertListEqual(lasPath.elements, [])
16 |
17 | def testLaserPath(self):
18 | elements = [Space(5), Lens(5), Space(20), Lens(15), Space(15)]
19 | lp = LaserPath(elements, "Laser Path")
20 | self.assertListEqual(lp.elements, elements)
21 | self.assertEqual(lp.label, "Laser Path")
22 |
23 | def testLaserPathIncorrectElements(self):
24 | elements = [Ray(), Lens(10)]
25 | with self.assertRaises(TypeError):
26 | LaserPath(elements)
27 |
28 | @envtest.skip("This test needs to be moved to Figure")
29 | def testRearrangeBeamTraceForPlotting(self):
30 | x = [x for x in range(1, 6)]
31 | y = [y for y in range(1, 6)]
32 | rayList = [GaussianBeam(w=x_, z=y_) for (x_, y_) in zip(x, y)]
33 | lp = LaserPath()
34 | self.assertTupleEqual(lp.rearrangeBeamTraceForPlotting(rayList), (x, y))
35 |
36 |
37 | class TestLaserCavity(envtest.RaytracingTestCase):
38 |
39 |
40 | def testEigenModesNoPower(self):
41 | lp = LaserCavity([Space(10)])
42 | self.assertTupleEqual(lp.eigenModes(), (None, None))
43 |
44 | def testEigenModes(self):
45 | lp = LaserCavity([Space(10), Lens(10)])
46 | beam1, beam2 = lp.eigenModes()
47 | self.assertEqual(beam1.q.real, -5)
48 | self.assertEqual(beam2.q.real, -5)
49 | self.assertAlmostEqual(beam1.q.imag, -5 * 3 ** 0.5)
50 | self.assertAlmostEqual(beam2.q.imag, 5 * 3 ** 0.5)
51 |
52 | def testEigenModesQIs0(self):
53 | lp = LaserCavity([Lens(10)])
54 | beam1, beam2 = lp.eigenModes()
55 | self.assertEqual(beam1.q, beam2.q)
56 | self.assertEqual(beam1.q, 0)
57 |
58 | def testLaserModesNoPower(self):
59 | lp = LaserCavity([Space(10)])
60 | self.assertListEqual(lp.laserModes(), [])
61 |
62 | def testLaserModesOneModeQ1isNone(self):
63 | lp = LaserCavity([Space(10), Lens(10)])
64 | laserModes = lp.laserModes()
65 | beam = laserModes[0]
66 | self.assertEqual(len(laserModes), 1)
67 | self.assertEqual(beam.q.real, -5)
68 | self.assertAlmostEqual(beam.q.imag, 5 * 3 ** 0.5)
69 |
70 | def testLaserModesOneModeQ2isNone(self):
71 | elements = [Space(1, 1.33), DielectricInterface(1.33, 1, 1), ThickLens(1.33, -10, -5, -20)]
72 | lp = LaserCavity(elements)
73 | laserModes = lp.laserModes()
74 | beam = laserModes[0]
75 | self.assertEqual(len(laserModes), 1)
76 | self.assertAlmostEqual(beam.q.real, -5.90770102)
77 | self.assertAlmostEqual(beam.q.imag, 1.52036515)
78 |
79 | def testLaserModesNoModeInfineElements(self):
80 | lp = LaserCavity([Space(10), CurvedMirror(5)])
81 | self.assertListEqual(lp.laserModes(), [])
82 |
83 | def testLaserModesNoModeNoElement(self):
84 | lp = LaserCavity()
85 | self.assertListEqual(lp.laserModes(), [])
86 |
87 | def testLaserUnstableCavity(self):
88 | laser = LaserCavity()
89 | laser.append(Space(d=10))
90 | laser.append(Lens(f=5))
91 | laser.append(Space(d=10))
92 |
93 | self.assertIsNotNone(laser)
94 | self.assertTrue(len(laser.laserModes()) == 0)
95 |
96 | def testLaserStableCavity(self):
97 | laser = LaserCavity()
98 | laser.append(Space(d=10))
99 | laser.append(Lens(f=20))
100 | laser.append(Space(d=10))
101 |
102 | self.assertIsNotNone(laser)
103 | self.assertTrue(len(laser.laserModes()) == 1)
104 |
105 | if __name__ == '__main__':
106 | envtest.main()
107 |
--------------------------------------------------------------------------------
/raytracing/tests/testsLimits.py:
--------------------------------------------------------------------------------
1 | import envtest # modifies path
2 | from raytracing import *
3 |
4 | inf = float("+inf")
5 |
6 |
7 | class TestLimits(envtest.RaytracingTestCase):
8 | def testLimitsThatExist(self):
9 | # Limits that exist
10 | self.assertEqual(inf, float('+inf'))
11 | self.assertEqual(0 / inf, 0)
12 | self.assertEqual(inf * inf, inf)
13 | self.assertEqual(inf * 10, inf)
14 | self.assertEqual(inf * (-10), -inf)
15 | self.assertEqual(inf + 10, inf)
16 | self.assertEqual(inf * (-inf), -inf)
17 | self.assertEqual((-inf) * (-inf), inf)
18 | self.assertEqual((inf) ** (inf), inf)
19 |
20 | # Dividing by zero is never allowed
21 | with self.assertRaises(Exception) as context:
22 | inf / 0
23 |
24 | def testLimitsThatDontExist(self):
25 | self.assertTrue(isnan(inf - inf))
26 | self.assertTrue(isnan(inf * 0))
27 |
28 | def testLimitsThatExistButPythonDoesNotConsider(self):
29 | self.assertEqual((1.0) ** (inf), 1)
30 |
31 | # This rounds off to 0.99999 on my computer and the test fails
32 | # a = 3.3
33 | # b = 0.1
34 | # c = 3.0
35 | # self.assertEqual(((a/c-b))**(inf), 1)
36 |
37 |
38 | if __name__ == '__main__':
39 | envtest.main()
40 |
--------------------------------------------------------------------------------
/raytracing/tests/testsMaterials.py:
--------------------------------------------------------------------------------
1 | import envtest # modifies path
2 | from raytracing import *
3 |
4 |
5 | class TestMaterial(envtest.RaytracingTestCase):
6 | def testMaterialInvalid(self):
7 | self.assertRaises(TypeError, Material.n, 5)
8 |
9 | class TestMaterialSubclasses(envtest.RaytracingTestCase):
10 | def setUp(self) -> None:
11 | self.materials = Material.__subclasses__()
12 | super().setUp()
13 |
14 | def testMaterialsList(self):
15 | self.assertTrue(len(self.materials) > 10)
16 |
17 | def testMaterialsList2(self):
18 | self.assertTrue(Material.all())
19 |
20 | def testFindMaterialByName(self):
21 | self.assertIsNotNone(Material.findByName('NSF10'))
22 |
23 | def testFindMaterialByIndex(self):
24 | match = Material.findByIndex(n=1.6, wavelength=0.5, tolerance=0.1)
25 | self.assertIsNotNone(match)
26 | self.assertTrue(len(match) > 2)
27 |
28 | match = Material.findByIndex(n=5.6, wavelength=0.5, tolerance=0.1)
29 | self.assertIsNotNone(match)
30 | self.assertTrue(len(match) == 0)
31 |
32 | def testMaterialAbbeNumber(self):
33 | for material in self.materials:
34 | self.assertIsNotNone(material().abbeNumber())
35 | self.assertIsNotNone(material().Vd())
36 |
37 | def testMaterialSubclassesTypeError(self):
38 | fails = []
39 | for material in self.materials:
40 | try:
41 | self.assertRaises(TypeError, material.n, None)
42 | self.assertRaises(TypeError, material.n, 'test')
43 | except AssertionError:
44 | fails.append('TypeError for subclass {}'.format(material.__name__))
45 | self.assertEqual([], fails)
46 |
47 | def testMaterialSubclassesValueErrors(self):
48 | fails = []
49 | for material in self.materials:
50 | try:
51 | self.assertRaises(ValueError, material.n, 100)
52 | self.assertRaises(ValueError, material.n, 0)
53 | self.assertRaises(ValueError, material.n, -100)
54 | except AssertionError:
55 | fails.append('ValueError for subclass {}'.format(material.__name__))
56 | self.assertEqual([], fails)
57 |
58 | @envtest.expectedFailure
59 | def testMaterialSubclasses(self):
60 | ''' These are sample values of refractive indices for each subclass of material for a wavelength of 0.6 micron.
61 | In case of a new category of material, make sure to add the sample value to the list. Indices can be found on :
62 | https://refractiveindex.info/ '''
63 | fails = []
64 | refractiveIndices = {'N_BK7': 1.5163, 'N_SF2': 1.6465, 'SF2': 1.6465, 'SF5': 1.6714, 'N_SF5': 1.6714,
65 | 'N_SF6HT': 1.8033, 'N_SF10': 1.7267, 'N_SF11': 1.7829, 'N_BAF10': 1.6692,
66 | 'E_BAF11': 1.6659, 'N_BAK1': 1.5719, 'N_BAK4': 1.5682, 'FK51A': 1.4862, 'LAFN7': 1.7482,
67 | 'N_LASF9': 1.8486,'N_LAK22': 1.6504, 'N_SSK5': 1.6576, 'E_FD10': 1.7267,
68 | 'FusedSilica': 1.4580,'N_SF6':1.7159, 'N_SF8':1.6876,'N_SF57':1.8445, 'Air':1.0}
69 |
70 | for material in self.materials:
71 | name = material.__name__
72 | n = material.n(0.6)
73 |
74 | if name not in refractiveIndices:
75 | fails.append('No test value for {0}'.format(name))
76 | elif areRelativelyNotEqual(n, refractiveIndices[name], 1e-4):
77 | fails.append('Wrong value for subclass {}, {} not a valid n value.'.format(name, n))
78 | self.assertEqual([], fails)
79 |
80 |
81 | if __name__ == '__main__':
82 | envtest.main()
83 |
84 |
85 |
86 |
--------------------------------------------------------------------------------
/raytracing/tests/testsMultiprocessor.py:
--------------------------------------------------------------------------------
1 | import envtest # modifies path
2 | from raytracing import *
3 | import itertools
4 | import matplotlib.pyplot as plt
5 |
6 | inf = float("+inf")
7 |
8 |
9 | def multiplyBy2(value) -> float:
10 | return 2 * value
11 |
12 |
13 | def mul(value1, value2) -> float:
14 | return value1 * value2
15 |
16 |
17 | class TestMultiProcessorSupport(envtest.RaytracingTestCase):
18 | @envtest.skipIf(sys.platform == 'darwin' and sys.version_info.major == 3 and sys.version_info.minor <= 7,
19 | "Endless loop on macOS")
20 | # Some information here: https://github.com/gammapy/gammapy/issues/2453
21 | def testMultiPool(self):
22 | processes = 8
23 | inputValues = [1, 2, 3, 4]
24 | with multiprocessing.Pool(processes=processes) as pool:
25 | outputValues = pool.map(multiplyBy2, inputValues)
26 | self.assertEqual(outputValues, list(map(multiplyBy2, inputValues)))
27 |
28 | @envtest.skipIf(sys.platform == 'darwin' and sys.version_info.major == 3 and sys.version_info.minor <= 7,
29 | "Endless loop on macOS")
30 | # Some information here: https://github.com/gammapy/gammapy/issues/2453
31 | def testMultiPoolWith2Args(self):
32 | processes = 4
33 | inputValues = [1, 2, 3, 4]
34 | with multiprocessing.Pool(processes=processes) as pool:
35 | outputValues = pool.starmap(mul, itertools.product(inputValues, [2]))
36 | func = lambda val1: mul(val1, 2)
37 | self.assertEqual(outputValues, list(map(func, inputValues)))
38 |
39 | class TestSingleAndMultiProcessorCalculation(envtest.RaytracingTestCase):
40 | #@envtest.skipUnless(envtest.performanceTests)
41 | # Some information here: https://github.com/gammapy/gammapy/issues/2453
42 | def testShortPath(self):
43 | path = ImagingPath()
44 | path.append(Space(d=100))
45 | path.append(Lens(f=50, diameter=25))
46 |
47 | import time
48 | photons = [10, 100, 1000, 3000, 10000, 30000]
49 | single = []
50 | for N in photons:
51 | rays = UniformRays(N=N)
52 | start = time.time()
53 | path.traceManyThrough(rays, progress=False)
54 | single.append(time.time()-start)
55 |
56 | multi = []
57 | for N in photons:
58 | rays = UniformRays(N=N)
59 | start = time.time()
60 | path.traceManyThroughInParallel(rays, progress=False)
61 | multi.append(time.time()-start)
62 |
63 | fig, axis = plt.subplots(1)
64 | axis.plot(photons, single,'k-',label="Single processor")
65 | axis.plot(photons, multi,'k-.',label="Multiprocessor")
66 | axis.set_title("Performance for short OpticalPath")
67 | axis.set_xlabel("Number of photons")
68 | axis.set_ylabel("Time [s]")
69 | axis.legend()
70 | plt.show()
71 |
72 | def testLongPath(self):
73 | path = ImagingPath()
74 | path.append(Space(d=100))
75 | path.append(Lens(f=50, diameter=25))
76 | path.append(Space(d=100))
77 | path.append(Lens(f=25, diameter=25))
78 | path.append(Space(d=100))
79 | path.append(Space(d=100))
80 | path.append(Lens(f=25, diameter=25))
81 | path.append(Space(d=100))
82 | path.append(Lens(f=25, diameter=25))
83 | path.append(thorlabs.AC254_100_A())
84 |
85 | import time
86 | photons = [10, 100, 1000, 3000]
87 | single = []
88 | for N in photons:
89 | rays = UniformRays(N=N)
90 | start = time.time()
91 | path.traceManyThrough(rays, progress=False)
92 | single.append(time.time()-start)
93 |
94 | multi = []
95 | for N in photons:
96 | rays = UniformRays(N=N)
97 | start = time.time()
98 | path.traceManyThroughInParallel(rays, progress=False)
99 | multi.append(time.time()-start)
100 |
101 | fig, axis = plt.subplots(1)
102 | axis.plot(photons, single,'k-',label="Single processor")
103 | axis.plot(photons, multi,'k-.',label="Multiprocessor")
104 | axis.set_title("Performance for Long OpticalPath")
105 | axis.set_xlabel("Number of photons")
106 | axis.set_ylabel("Time [s]")
107 | axis.legend()
108 | plt.show()
109 |
110 | if __name__ == '__main__':
111 | envtest.main()
112 |
--------------------------------------------------------------------------------
/raytracing/tests/testsPreferences.py:
--------------------------------------------------------------------------------
1 | import envtest # modifies path
2 | import os
3 | from raytracing.preferences import Preferences
4 |
5 |
6 | class TestPrefs(envtest.RaytracingTestCase):
7 | def setUp(self):
8 | super().setUp()
9 | self.savedPrefs = Preferences()
10 | self.savedPrefs.readFromDisk()
11 |
12 | def tearDown(self):
13 | self.savedPrefs.writeToDisk()
14 | super().tearDown()
15 |
16 | def testInitPrefs(self):
17 | p = Preferences()
18 | self.assertIsNotNone(p)
19 |
20 | def testReset(self):
21 | p = Preferences()
22 | p.resetPreferences()
23 | self.assertTrue(os.path.exists(p.path))
24 | self.assertFalse(p.keys()) # empty
25 |
26 | def testPathExists(self):
27 | p = Preferences()
28 | self.assertIsNotNone(p.path)
29 | self.assertTrue(os.path.exists(p.path))
30 |
31 | def testReadPrefs(self):
32 | p = Preferences()
33 | p.readFromDisk()
34 | self.assertIsNotNone(p)
35 |
36 | def testWritePrefs(self):
37 | p = Preferences()
38 | p["test"] = 123
39 | p.writeToDisk()
40 | p.readFromDisk()
41 | self.assertTrue(p["test"] == 123)
42 |
43 | def testPrefsAsDict(self):
44 | p = Preferences()
45 | p["test"] = 345
46 | self.assertEqual(p["test"], 345)
47 |
48 | # def testPrefsAsIter(self):
49 | # p = Preferences()
50 | # p["test"] = 1
51 | # for key in p:
52 | # self.assertEqual(key, "test")
53 |
54 | def testZZPrefsIncludesKey(self):
55 | p = Preferences()
56 | p["test"] = "ouch"
57 | self.assertTrue('test' in p)
58 |
59 | def testZZZPrefsLargeDict(self):
60 | p = Preferences()
61 | p["test"] = "ouch"
62 | p["test1"] = "ouch"
63 | p["test2"] = "ouch"
64 | p["test3"] = "ouch"
65 | self.assertTrue(len(p.keys()) >= 4)
66 |
67 | if __name__ == '__main__':
68 | envtest.main()
69 |
--------------------------------------------------------------------------------
/raytracing/tests/testsPreferencesRaytracing.py:
--------------------------------------------------------------------------------
1 | import envtest # modifies path
2 | from raytracing import *
3 | from raytracing.preferences import Preferences
4 |
5 | class TestRaytracingPrefs(envtest.RaytracingTestCase):
6 | def setUp(self):
7 | super().setUp()
8 | self.savedPrefs = Preferences()
9 | self.savedPrefs.readFromDisk()
10 |
11 | def tearDown(self):
12 | self.savedPrefs.writeToDisk()
13 | super().tearDown()
14 |
15 | def testVersionCheckPrefs(self):
16 | p = Preferences()
17 | self.assertIsNotNone(p)
18 | self.assertTrue("lastVersionCheck" in p.keys())
19 |
20 | def testSaveBeginnerMode(self):
21 | beginnerMode(saveToPrefs=True)
22 | p = Preferences()
23 | self.assertIsNotNone(p)
24 | self.assertTrue("mode" in p.keys())
25 | self.assertEqual(p["mode"], "beginner")
26 | expertMode(saveToPrefs=False)
27 | silentMode(saveToPrefs=False)
28 | self.assertEqual(p["mode"], "beginner")
29 |
30 | def testSaveSilentMode(self):
31 | silentMode(saveToPrefs=True)
32 | p = Preferences()
33 | self.assertIsNotNone(p)
34 | self.assertTrue("mode" in p.keys())
35 | self.assertEqual(p["mode"], "silent")
36 | expertMode(saveToPrefs=False)
37 | beginnerMode(saveToPrefs=False)
38 | self.assertEqual(p["mode"], "silent")
39 |
40 | def testSaveExpertMode(self):
41 | expertMode(saveToPrefs=True)
42 | p = Preferences()
43 | self.assertIsNotNone(p)
44 | self.assertTrue("mode" in p.keys())
45 | self.assertEqual(p["mode"], "expert")
46 | silentMode(saveToPrefs=False)
47 | beginnerMode(saveToPrefs=False)
48 | self.assertEqual(p["mode"], "expert")
49 |
50 | def testWarningsFormat(self):
51 | beginnerMode()
52 | message = "This is a test."
53 | filename = "test.py"
54 | lineno = 10
55 | category = UserWarning
56 | warningsMessage = warningLineFormat(message, category, filename, lineno)
57 | self.assertEqual(warningsMessage, "UserWarning [in test.py]: This is a test.\n")
58 |
59 | if __name__ == '__main__':
60 | envtest.main()
61 |
--------------------------------------------------------------------------------
/raytracing/tests/testsReport.py:
--------------------------------------------------------------------------------
1 | import envtest # modifies path
2 | from raytracing import *
3 | from raytracing.zemax import ZMXReader
4 | from numpy import linspace, pi
5 | from raytracing.materials import *
6 | import io
7 | from contextlib import redirect_stdout
8 |
9 | inf = float("+inf")
10 |
11 | class TestReport(envtest.RaytracingTestCase):
12 | stdout = io.StringIO()
13 |
14 | def setUp(self):
15 | self.stdout = io.StringIO()
16 | super().setUp()
17 |
18 | def testCreationNoASNoFS(self):
19 | f = 50
20 | path = ImagingPath()
21 | path.append(Space(d=f))
22 | path.append(Lens(f=f))
23 | path.append(Space(d=f))
24 | with redirect_stdout(self.stdout):
25 | path.reportEfficiency()
26 |
27 | self.assertTrue(len(self.stdout.getvalue()) < 100)
28 | position, diameter = path.fieldStop()
29 | self.assertIsNone(position)
30 | self.assertTrue(diameter == float("+inf"))
31 |
32 | @envtest.patchMatplotLib()
33 | def testCreationASNoFS(self):
34 | f = 50
35 | path = ImagingPath()
36 | path.append(Space(d=f))
37 | path.append(Lens(f=f,diameter=10))
38 | path.append(Space(d=f))
39 | with redirect_stdout(self.stdout):
40 | path.reportEfficiency()
41 |
42 | self.assertTrue(len(self.stdout.getvalue()) < 100)
43 | position, diameter = path.fieldStop()
44 | self.assertIsNone(position)
45 | self.assertTrue(diameter == float("+inf"))
46 |
47 | @envtest.patchMatplotLib()
48 | def testCreationASFS(self):
49 | f = 50
50 | path = ImagingPath()
51 | path.append(Space(d=f))
52 | path.append(Lens(f=f, diameter=6))
53 | path.append(Space(d=f))
54 | path.append(Lens(f=f, diameter=6))
55 | with redirect_stdout(self.stdout):
56 | path.reportEfficiency()
57 |
58 | self.assertTrue(len(self.stdout.getvalue()) > 100)
59 | position, diameter = path.fieldStop()
60 | self.assertIsNotNone(position)
61 | self.assertTrue(diameter != float("+inf"))
62 |
63 |
64 | if __name__ == '__main__':
65 | envtest.main()
66 |
--------------------------------------------------------------------------------
/raytracing/tests/testsSpecialLenses.py:
--------------------------------------------------------------------------------
1 | import envtest # modifies path
2 |
3 | from raytracing import *
4 |
5 | inf = float("+inf")
6 |
7 |
8 | class TestSpecialLenses(envtest.RaytracingTestCase):
9 |
10 | def testOlympusLens(self):
11 | self.assertIsNotNone(olympus.LUMPlanFL40X())
12 | self.assertIsNotNone(olympus.XLUMPlanFLN20X())
13 | self.assertIsNotNone(olympus.MVPlapo2XC())
14 | self.assertIsNotNone(olympus.UMPLFN20XW())
15 |
16 | def testThorlabsLenses(self):
17 | l = thorlabs.ACN254_100_A()
18 | l = thorlabs.ACN254_075_A()
19 | l = thorlabs.ACN254_050_A()
20 | l = thorlabs.ACN254_040_A()
21 | l = thorlabs.AC254_030_A()
22 | l = thorlabs.AC254_035_A()
23 | l = thorlabs.AC254_045_A()
24 | l = thorlabs.AC254_050_A()
25 | l = thorlabs.AC254_060_A()
26 | l = thorlabs.AC254_075_A()
27 | l = thorlabs.AC254_080_A()
28 | l = thorlabs.AC254_100_A()
29 | l = thorlabs.AC254_125_A()
30 | l = thorlabs.AC254_200_A()
31 | l = thorlabs.AC254_250_A()
32 | l = thorlabs.AC254_300_A()
33 | l = thorlabs.AC254_400_A()
34 | l = thorlabs.AC254_500_A()
35 |
36 | l = thorlabs.AC508_075_B()
37 | l = thorlabs.AC508_080_B()
38 | l = thorlabs.AC508_100_B()
39 | l = thorlabs.AC508_150_B()
40 | l = thorlabs.AC508_200_B()
41 | l = thorlabs.AC508_250_B()
42 | l = thorlabs.AC508_300_B()
43 | l = thorlabs.AC508_400_B()
44 | l = thorlabs.AC508_500_B()
45 | l = thorlabs.AC508_750_B()
46 | l = thorlabs.AC508_1000_B()
47 |
48 | def testEdmundLens(self):
49 | l = eo.PN_33_921()
50 |
51 |
52 | if __name__ == '__main__':
53 | envtest.main()
54 |
--------------------------------------------------------------------------------
/raytracing/tests/testsUtils.py:
--------------------------------------------------------------------------------
1 | import envtest # modifies path # fixme: requires path to raytracing/tests
2 | from raytracing.utils import checkLatestVersion
3 |
4 | import io
5 | import contextlib
6 |
7 | class TestUtils(envtest.RaytracingTestCase):
8 | def testCheckOldVersion(self):
9 | f = io.StringIO()
10 | with contextlib.redirect_stdout(f):
11 | self.assertTrue(checkLatestVersion(currentVersion="1.3.0"))
12 | self.assertTrue(len(f.getvalue()) != 0)
13 |
14 | def testCheckNewVersion(self):
15 | f = io.StringIO()
16 | with contextlib.redirect_stdout(f):
17 | self.assertFalse(checkLatestVersion(currentVersion="1.4.0"))
18 | self.assertTrue(len(f.getvalue()) == 0)
19 |
20 | if __name__ == '__main__':
21 | envtest.main()
22 |
--------------------------------------------------------------------------------
/raytracing/tests/testsWarnings.py:
--------------------------------------------------------------------------------
1 | import envtest # modifies path
2 | from raytracing import *
3 |
4 | inf = float("+inf")
5 |
6 |
7 | class TestMatrix(envtest.RaytracingTestCase):
8 | def testMatrix(self):
9 | m = Matrix()
10 | self.assertIsNotNone(m)
11 |
12 | def testThorlabsLensesWarning(self):
13 | l = thorlabs.AC254_030_A()
14 |
15 |
16 | if __name__ == '__main__':
17 | envtest.main()
18 |
--------------------------------------------------------------------------------
/raytracing/tests/testsZemax.py:
--------------------------------------------------------------------------------
1 | import envtest # modifies path
2 | from raytracing import *
3 | from raytracing.zemax import ZMXReader
4 | from numpy import linspace, pi
5 | from raytracing.materials import *
6 |
7 | inf = float("+inf")
8 |
9 | class TestZemax(envtest.RaytracingTestCase):
10 | zmx = ZMXReader(r"../specifications/AC254-100-A-Zemax(ZMX).zmx")
11 |
12 | def testCreation(self):
13 | self.assertIsNotNone(self.zmx)
14 |
15 | def testSurface0Raw(self):
16 | self.assertIsNotNone(self.zmx)
17 | surface = self.zmx.rawSurfaceInfo(index=0)
18 | self.assertTrue( surface["SURF"] == 0)
19 | self.assertAlmostEqual( float(surface["CURV"][0]), 0.0)
20 | self.assertAlmostEqual( float(surface["DISZ"][0]), float("+inf"))
21 |
22 | def testSurface1Raw(self):
23 | self.assertIsNotNone(self.zmx)
24 | surface = self.zmx.rawSurfaceInfo(index=1)
25 | self.assertTrue( surface["SURF"] == 1)
26 | self.assertAlmostEqual( float(surface["CURV"][0]), 1.593625498007969800E-002)
27 | self.assertAlmostEqual( float(surface["DISZ"][0]), 4.0)
28 |
29 | def testSurface1Info(self):
30 | self.assertIsNotNone(self.zmx)
31 | surface = self.zmx.surfaceInfo(index=1)
32 | self.assertTrue( surface.number == 1)
33 | self.assertAlmostEqual( surface.R, 62.75, 1)
34 | self.assertAlmostEqual( surface.spacing, 4.0)
35 |
36 | def testSurfaces(self):
37 | self.assertIsNotNone(self.zmx)
38 | self.assertTrue(len(self.zmx.surfaces()) == 5)
39 |
40 | def testMatrixGroupLensProperties(self):
41 | design = self.zmx.matrixGroup()
42 | self.assertIsNotNone(design)
43 | lens = thorlabs.AC254_100_A()
44 |
45 | self.assertAlmostEqual(design.effectiveFocalLengths().f1, lens.effectiveFocalLengths().f1, 3)
46 | self.assertAlmostEqual(design.effectiveFocalLengths().f2, lens.effectiveFocalLengths().f1, 3)
47 | self.assertAlmostEqual(design.backFocalLength(), lens.backFocalLength(), 3)
48 | self.assertAlmostEqual(design.frontFocalLength(), lens.frontFocalLength(), 3)
49 | self.assertAlmostEqual(design.L, lens.L, 2)
50 |
51 | def testMatrixGroup2(self):
52 | design = self.zmx.matrixGroup()
53 | self.assertIsNotNone(design)
54 | lens = thorlabs.AC254_100_A()
55 |
56 | self.assertAlmostEqual(design.effectiveFocalLengths().f1, lens.effectiveFocalLengths().f1, 3)
57 | self.assertAlmostEqual(design.effectiveFocalLengths().f2, lens.effectiveFocalLengths().f1, 3)
58 | self.assertAlmostEqual(design.backFocalLength(), lens.backFocalLength(), 3)
59 | self.assertAlmostEqual(design.frontFocalLength(), lens.frontFocalLength(), 3)
60 |
61 | def testPrescription(self):
62 | self.assertIsNotNone(self.zmx.prescription())
63 |
64 | def testWAVM(self):
65 | self.assertTrue(self.zmx.designWavelengths() == [4.861E-1, 5.876E-1, 6.563E-1])
66 | self.assertAlmostEqual(self.zmx.designWavelength, 0.5876)
67 |
68 | def testEdmundFile(self):
69 | zmx = ZMXReader("../specifications/zmax_49270.zmx")
70 | self.assertIsNotNone(zmx)
71 | # print(zmx.prescription())
72 |
73 | def testEdmundFile(self):
74 | zmx = ZMXReader("../specifications/AC254-125-A-Zemax(ZMX).zmx")
75 | self.assertIsNotNone(zmx)
76 | #print(zmx.prescription())
77 | #print(zmx.matrixGroup().effectiveFocalLengths())
78 |
79 | if __name__ == '__main__':
80 | envtest.main()
81 |
--------------------------------------------------------------------------------
/raytracing/tests/traceManyThroughInParallelNoOutput.py:
--------------------------------------------------------------------------------
1 | import envtest
2 |
3 | from raytracing import *
4 |
5 | if __name__ == '__main__':
6 | rays = [Ray(y, y) for y in range(20_000)]
7 | m = Matrix(physicalLength=1)
8 | m.traceManyThroughInParallel(rays, processes=2, progress=False)
9 |
--------------------------------------------------------------------------------
/raytracing/tests/traceManyThroughInParallelWithOutput.py:
--------------------------------------------------------------------------------
1 | import envtest
2 |
3 | from raytracing import *
4 |
5 | if __name__ == '__main__':
6 | rays = [Ray(y, y) for y in range(20_000)]
7 | m = Matrix(physicalLength=1)
8 | m.traceManyThroughInParallel(rays, processes=2, progress=True)
9 |
--------------------------------------------------------------------------------
/raytracing/ui/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DCC-Lab/RayTracing/9609b55f16c8ce4499c85a2622619f60b7675fa3/raytracing/ui/__init__.py
--------------------------------------------------------------------------------