├── .gitignore
├── .readthedocs.yaml
├── Figures
├── BeamTiltShift Graphic
│ └── beamtiltshift.svg
├── Code Example
│ ├── code_graphic.svg
│ ├── matplotlib_code_example.pdf
│ └── pyqtgraph_code_example.pdf
├── Gui Graphic
│ └── GUI_graphic.svg
├── Matplotlib Graphic
│ └── matplotlib_graphic.svg
├── Matrix Graphic
│ ├── matrix_graphic_first_edit.svg
│ ├── matrix_graphic_second_edit.pdf
│ ├── matrix_graphic_second_edit.ps
│ └── matrix_graphic_second_edit.svg
├── Raw Images
│ ├── All_Components_3D_PYQT.PNG
│ ├── Beam_tilt_shift_advanced_PYQT.PNG
│ ├── Condenser_Aperture_2D_PYQT.PNG
│ ├── Condenser_Astigmatism_2D_PYQT.PNG
│ ├── Condenser_Astigmatism_3D_PYQT.PNG
│ ├── Def_Matrix.svg
│ ├── Double_Def_Matrix.svg
│ ├── Lens_Matrix.svg
│ ├── Quad_Matrix.svg
│ ├── SEM.svg
│ ├── SEM_PYQT.PNG
│ ├── TEM_PYQT.PNG
│ ├── all_components.svg
│ ├── basic_biprism.svg
│ ├── beam_shift_basic.svg
│ ├── beam_tilt.svg
│ ├── beam_tilt_basic.svg
│ ├── model_sem.svg
│ ├── model_tem.svg
│ ├── split_biprism_PYQT.PNG
│ └── split_condenser_biprism.svg
├── TEM & SEM Graphic
│ └── TEM&SEMGraphic.svg
├── all_components.svg
├── beam_shift_basic.svg
├── beam_tilt.svg
├── beam_tilt_basic.svg
├── biprism_model.svg
├── condenser_aperture.svg
├── condenser_astigmatism.svg
├── model_sem.svg
├── model_tem.svg
└── split_condenser_biprism.svg
├── LICENSE.md
├── README.md
├── desktop.ini
├── dist
├── temgymbasic-0.5.9.4-py2.py3-none-any.whl
└── temgymbasic-0.5.9.4.tar.gz
├── docs
├── Makefile
├── _templates
│ └── layout.html
├── conf.py
├── img
│ ├── GUI_graphic.svg
│ ├── all_components_example.png
│ ├── beam_shift_basic.svg
│ ├── beam_tilt_basic.svg
│ ├── beam_tilt_shift_base.png
│ ├── beam_tilt_shift_wobble_circled.png
│ ├── biprism_model.png
│ ├── condenser_aperture_after_lens_adjust.png
│ ├── condenser_aperture_before_lens_adjust.png
│ ├── condenser_asigmatism_base.png
│ ├── condenser_asigmatism_corrected.png
│ ├── model_sem_example.png
│ ├── model_tem.svg
│ ├── model_tem_example.png
│ ├── simple_lens.png
│ ├── simple_lens_aligned.png
│ ├── simple_lens_annotated.png
│ ├── simple_lens_annotated.pptx
│ ├── simple_lens_introductory.png
│ ├── simple_lens_wobble_on.png
│ └── simple_lens_wobble_on_top_of_sinewave.png
├── index.rst
├── make.bat
└── source
│ ├── bibliography.rst
│ ├── documentation.rst
│ ├── license.rst
│ ├── matplotlib.rst
│ ├── showcases.rst
│ ├── tutorials.rst
│ └── usage.rst
├── fourdstem_overfocused.zip
├── live_calibration_examples
├── fourdstem_example_no_gui.py
├── fourdstem_example_no_gui_notebook.ipynb
├── fourdstem_example_pyqt_large_scale.py
└── fourdstem_example_pyqt_micron_scale.py
├── matplotlib_examples
├── all_components_example_matplotlib.py
├── basic_biprism_example_matplotlib.py
├── beam_tilt_shift_advanced_example_matplotlib.py
├── beam_tilt_shift_basic_example_matplotlib.py
├── condenser_aperture_example_matplotlib.py
├── condenser_astigmatism_example_matplotlib.py
├── model_sem_example_matplotlib.py
├── model_tem_example_matplotlib.py
└── split_condenser_example_matplotlib.py
├── pyproject.toml
├── pyqtgraph_examples
├── 4DStem_example_no_gui.py
├── __pycache__
│ ├── all_components_example_pyqt.cpython-39.pyc
│ ├── beam_tilt_shift_advanced_example_pyqt.cpython-310.pyc
│ ├── beam_tilt_shift_advanced_example_pyqt.cpython-39.pyc
│ ├── beam_tilt_shift_basic_example_pyqt.cpython-310.pyc
│ ├── beam_tilt_shift_basic_example_pyqt.cpython-39.pyc
│ ├── biprism_example_pyqt.cpython-310.pyc
│ ├── biprism_example_pyqt.cpython-39.pyc
│ ├── condenser_aperture_example_pyqt.cpython-310.pyc
│ ├── condenser_aperture_example_pyqt.cpython-39.pyc
│ ├── condenser_astigmatism_example_pyqt.cpython-310.pyc
│ ├── condenser_astigmatism_example_pyqt.cpython-39.pyc
│ ├── example_exe.cpython-310.pyc
│ ├── example_exe.cpython-39.pyc
│ ├── lens_example_pyqt.cpython-310.pyc
│ ├── lens_example_pyqt.cpython-39.pyc
│ ├── model_sem_example_pyqt.cpython-310.pyc
│ ├── model_sem_example_pyqt.cpython-39.pyc
│ ├── model_tem_example_pyqt.cpython-310.pyc
│ └── model_tem_example_pyqt.cpython-39.pyc
├── all_components_example_pyqt.py
├── beam_tilt_shift_advanced_example_pyqt.py
├── beam_tilt_shift_basic_example_pyqt.py
├── biprism_example_pyqt.py
├── condenser_aperture_example_pyqt.py
├── condenser_astigmatism_example_pyqt.py
├── example_exe.py
├── lens_example_pyqt.py
├── make_exe.bat.bat
├── model_sem_example_pyqt.py
└── model_tem_example_pyqt.py
├── requirements-doc.txt
├── requirements.txt
├── setup.cfg
└── src
└── temgymbasic
├── __init__.py
├── components.py
├── functions.py
├── gui.py
├── model.py
├── run.py
└── shapes.py
/.gitignore:
--------------------------------------------------------------------------------
1 | *.swp
2 | *.pyc
3 | *.bib.bak
4 | *.bib.sav
5 | __pycache__
6 | .tox
7 | docs/build/
8 | .ipynb_checkpoints/
9 | *.egg-info
10 | htmlcov
11 | coverage.xml
12 | .coverage*
13 | .pytest_cache
14 | node_modules
15 | build
16 | dist
17 | *.AppImage
18 | dask-worker-space
19 | AppDir
20 | Miniconda3-latest-Linux-x86_64.sh
21 | MANIFEST
22 | .vscode
23 | pip-wheel-metadata
24 | data
25 | *Thumbs.db
26 | *.DS_Store
27 | *.idea
28 | .mypy_cache/
29 | .benchmarks
30 | junit.xml
31 | .vscode-server
32 | *.sif
33 | examples/generated/
34 |
--------------------------------------------------------------------------------
/.readthedocs.yaml:
--------------------------------------------------------------------------------
1 | version: 2
2 |
3 | build:
4 | os: "ubuntu-22.04"
5 | tools:
6 | python: "3.9"
7 |
8 | sphinx:
9 | configuration: docs/conf.py
10 | formats:
11 | - epub
12 | - pdf
13 |
14 | python:
15 | install:
16 | - requirements: requirements-doc.txt
17 |
18 |
--------------------------------------------------------------------------------
/Figures/Code Example/matplotlib_code_example.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AMCLab/TemGymBasic/35f2cf0a3f96263bda7576dbd8b68847ee76c4bb/Figures/Code Example/matplotlib_code_example.pdf
--------------------------------------------------------------------------------
/Figures/Code Example/pyqtgraph_code_example.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AMCLab/TemGymBasic/35f2cf0a3f96263bda7576dbd8b68847ee76c4bb/Figures/Code Example/pyqtgraph_code_example.pdf
--------------------------------------------------------------------------------
/Figures/Matrix Graphic/matrix_graphic_second_edit.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AMCLab/TemGymBasic/35f2cf0a3f96263bda7576dbd8b68847ee76c4bb/Figures/Matrix Graphic/matrix_graphic_second_edit.pdf
--------------------------------------------------------------------------------
/Figures/Raw Images/All_Components_3D_PYQT.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AMCLab/TemGymBasic/35f2cf0a3f96263bda7576dbd8b68847ee76c4bb/Figures/Raw Images/All_Components_3D_PYQT.PNG
--------------------------------------------------------------------------------
/Figures/Raw Images/Beam_tilt_shift_advanced_PYQT.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AMCLab/TemGymBasic/35f2cf0a3f96263bda7576dbd8b68847ee76c4bb/Figures/Raw Images/Beam_tilt_shift_advanced_PYQT.PNG
--------------------------------------------------------------------------------
/Figures/Raw Images/Condenser_Aperture_2D_PYQT.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AMCLab/TemGymBasic/35f2cf0a3f96263bda7576dbd8b68847ee76c4bb/Figures/Raw Images/Condenser_Aperture_2D_PYQT.PNG
--------------------------------------------------------------------------------
/Figures/Raw Images/Condenser_Astigmatism_2D_PYQT.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AMCLab/TemGymBasic/35f2cf0a3f96263bda7576dbd8b68847ee76c4bb/Figures/Raw Images/Condenser_Astigmatism_2D_PYQT.PNG
--------------------------------------------------------------------------------
/Figures/Raw Images/Condenser_Astigmatism_3D_PYQT.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AMCLab/TemGymBasic/35f2cf0a3f96263bda7576dbd8b68847ee76c4bb/Figures/Raw Images/Condenser_Astigmatism_3D_PYQT.PNG
--------------------------------------------------------------------------------
/Figures/Raw Images/Def_Matrix.svg:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
437 |
--------------------------------------------------------------------------------
/Figures/Raw Images/Lens_Matrix.svg:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
442 |
--------------------------------------------------------------------------------
/Figures/Raw Images/Quad_Matrix.svg:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
521 |
--------------------------------------------------------------------------------
/Figures/Raw Images/SEM_PYQT.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AMCLab/TemGymBasic/35f2cf0a3f96263bda7576dbd8b68847ee76c4bb/Figures/Raw Images/SEM_PYQT.PNG
--------------------------------------------------------------------------------
/Figures/Raw Images/TEM_PYQT.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AMCLab/TemGymBasic/35f2cf0a3f96263bda7576dbd8b68847ee76c4bb/Figures/Raw Images/TEM_PYQT.PNG
--------------------------------------------------------------------------------
/Figures/Raw Images/split_biprism_PYQT.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AMCLab/TemGymBasic/35f2cf0a3f96263bda7576dbd8b68847ee76c4bb/Figures/Raw Images/split_biprism_PYQT.PNG
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Tem Gym Basic
2 |
3 | Please cite the following paper if you use this software in your research:
4 |
5 |
6 | An interactive first order ray tracing software for electron microscopes written in python.
7 | See [documentation](https://temgymbasic.readthedocs.io/en/latest/) on readthedocs for more info.
8 |
9 | Please cite the following article when using this software:
10 | Landers, D., Clancy, I., Weber, D., Dunin-Borkowski, R. & Stewart, A. (2023). J. Appl. Cryst. 56, https://doi.org/10.1107/S1600576723005174
11 |
--------------------------------------------------------------------------------
/desktop.ini:
--------------------------------------------------------------------------------
1 | [ViewState]
2 | Mode=
3 | Vid=
4 | FolderType=Documents
5 |
--------------------------------------------------------------------------------
/dist/temgymbasic-0.5.9.4-py2.py3-none-any.whl:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AMCLab/TemGymBasic/35f2cf0a3f96263bda7576dbd8b68847ee76c4bb/dist/temgymbasic-0.5.9.4-py2.py3-none-any.whl
--------------------------------------------------------------------------------
/dist/temgymbasic-0.5.9.4.tar.gz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AMCLab/TemGymBasic/35f2cf0a3f96263bda7576dbd8b68847ee76c4bb/dist/temgymbasic-0.5.9.4.tar.gz
--------------------------------------------------------------------------------
/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 = source
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/layout.html:
--------------------------------------------------------------------------------
1 | {% extends "!layout.html" %}
2 |
3 | {% block menu %}
4 | {{ super() }}
5 |
6 | {% if sidebar_external_links %}
7 |
8 |
9 | {% if sidebar_external_links_caption %}
10 | {{ sidebar_external_links_caption }}
11 | {% else %}
12 | External links
13 | {% endif %}
14 |
15 |
16 |
17 | {% for text, link in sidebar_external_links %}
18 | {% if hasdoc(link) %}
19 | - {{ text }}
20 | {% else %}
21 | - {{ text }}
22 | {% endif %}
23 | {% endfor %}
24 |
25 | {% endif %}
26 |
27 | {% endblock %}
--------------------------------------------------------------------------------
/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 | sys.path.insert(0, os.path.abspath('../src'))
16 |
17 | # -- Project information -----------------------------------------------------
18 |
19 | project = 'TemGym'
20 | copyright = '2022, David Landers'
21 | author = 'David Landers'
22 |
23 | # -- General configuration ---------------------------------------------------
24 |
25 | # Add any Sphinx extension module names here, as strings. They can be
26 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
27 | # ones.
28 | extensions = [
29 | 'sphinx.ext.autodoc',
30 | 'sphinx.ext.mathjax',
31 | 'sphinx.ext.todo',
32 | 'sphinx.ext.autosummary',
33 | 'sphinx.ext.napoleon',
34 | 'sphinx.ext.viewcode',
35 | 'sphinx_licenseinfo']
36 |
37 | templates_path = ["_templates"]
38 | html_context = {
39 | "sidebar_external_links_caption": "Links",
40 | "sidebar_external_links": [
41 | (
42 | ' Source code',
43 | "https://github.com/AMCLab/TemGymBasic",
44 | ),
45 | (
46 | ' Zenodo',
47 | "https://zenodo.org/communities/amcl/?page=1&size=20",
48 | )
49 | ],
50 | }
51 |
52 | # Add any paths that contain templates here, relative to this directory.
53 | templates_path = ['_templates']
54 |
55 | # List of patterns, relative to source directory, that match files and
56 | # directories to ignore when looking for source files.
57 | # This pattern also affects html_static_path and html_extra_path.
58 | exclude_patterns = []
59 |
60 | # The suffix of source filenames.
61 | source_suffix = '.rst'
62 |
63 | # The master toctree document.
64 | master_doc = 'index'
65 |
66 | # -- Options for HTML output -------------------------------------------------
67 |
68 | # The theme to use for HTML and HTML Help pages. See the documentation for
69 | # a list of builtin themes.
70 | #
71 | html_theme = 'sphinx_rtd_theme'
72 |
73 | napoleon_numpy_docstring = True
74 |
75 | # on_rtd is whether we are on readthedocs.org
76 | on_rtd = os.environ.get('READTHEDOCS', None) == 'True'
77 |
78 | if not on_rtd: # only import and set the theme if we're building docs locally
79 | import sphinx_rtd_theme
80 | html_theme = 'sphinx_rtd_theme'
81 | html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
82 |
83 | # Output file base name for HTML help builder.
84 | htmlhelp_basename = project+'doc'
--------------------------------------------------------------------------------
/docs/img/all_components_example.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AMCLab/TemGymBasic/35f2cf0a3f96263bda7576dbd8b68847ee76c4bb/docs/img/all_components_example.png
--------------------------------------------------------------------------------
/docs/img/beam_tilt_shift_base.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AMCLab/TemGymBasic/35f2cf0a3f96263bda7576dbd8b68847ee76c4bb/docs/img/beam_tilt_shift_base.png
--------------------------------------------------------------------------------
/docs/img/beam_tilt_shift_wobble_circled.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AMCLab/TemGymBasic/35f2cf0a3f96263bda7576dbd8b68847ee76c4bb/docs/img/beam_tilt_shift_wobble_circled.png
--------------------------------------------------------------------------------
/docs/img/biprism_model.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AMCLab/TemGymBasic/35f2cf0a3f96263bda7576dbd8b68847ee76c4bb/docs/img/biprism_model.png
--------------------------------------------------------------------------------
/docs/img/condenser_aperture_after_lens_adjust.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AMCLab/TemGymBasic/35f2cf0a3f96263bda7576dbd8b68847ee76c4bb/docs/img/condenser_aperture_after_lens_adjust.png
--------------------------------------------------------------------------------
/docs/img/condenser_aperture_before_lens_adjust.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AMCLab/TemGymBasic/35f2cf0a3f96263bda7576dbd8b68847ee76c4bb/docs/img/condenser_aperture_before_lens_adjust.png
--------------------------------------------------------------------------------
/docs/img/condenser_asigmatism_base.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AMCLab/TemGymBasic/35f2cf0a3f96263bda7576dbd8b68847ee76c4bb/docs/img/condenser_asigmatism_base.png
--------------------------------------------------------------------------------
/docs/img/condenser_asigmatism_corrected.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AMCLab/TemGymBasic/35f2cf0a3f96263bda7576dbd8b68847ee76c4bb/docs/img/condenser_asigmatism_corrected.png
--------------------------------------------------------------------------------
/docs/img/model_sem_example.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AMCLab/TemGymBasic/35f2cf0a3f96263bda7576dbd8b68847ee76c4bb/docs/img/model_sem_example.png
--------------------------------------------------------------------------------
/docs/img/model_tem_example.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AMCLab/TemGymBasic/35f2cf0a3f96263bda7576dbd8b68847ee76c4bb/docs/img/model_tem_example.png
--------------------------------------------------------------------------------
/docs/img/simple_lens.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AMCLab/TemGymBasic/35f2cf0a3f96263bda7576dbd8b68847ee76c4bb/docs/img/simple_lens.png
--------------------------------------------------------------------------------
/docs/img/simple_lens_aligned.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AMCLab/TemGymBasic/35f2cf0a3f96263bda7576dbd8b68847ee76c4bb/docs/img/simple_lens_aligned.png
--------------------------------------------------------------------------------
/docs/img/simple_lens_annotated.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AMCLab/TemGymBasic/35f2cf0a3f96263bda7576dbd8b68847ee76c4bb/docs/img/simple_lens_annotated.png
--------------------------------------------------------------------------------
/docs/img/simple_lens_annotated.pptx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AMCLab/TemGymBasic/35f2cf0a3f96263bda7576dbd8b68847ee76c4bb/docs/img/simple_lens_annotated.pptx
--------------------------------------------------------------------------------
/docs/img/simple_lens_introductory.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AMCLab/TemGymBasic/35f2cf0a3f96263bda7576dbd8b68847ee76c4bb/docs/img/simple_lens_introductory.png
--------------------------------------------------------------------------------
/docs/img/simple_lens_wobble_on.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AMCLab/TemGymBasic/35f2cf0a3f96263bda7576dbd8b68847ee76c4bb/docs/img/simple_lens_wobble_on.png
--------------------------------------------------------------------------------
/docs/img/simple_lens_wobble_on_top_of_sinewave.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AMCLab/TemGymBasic/35f2cf0a3f96263bda7576dbd8b68847ee76c4bb/docs/img/simple_lens_wobble_on_top_of_sinewave.png
--------------------------------------------------------------------------------
/docs/index.rst:
--------------------------------------------------------------------------------
1 | .. TemGym documentation master file, created by
2 | sphinx-quickstart on Thu Sep 22 15:08:17 2022.
3 | You can adapt this file completely to your liking, but it should at least
4 | contain the root `toctree` directive.
5 |
6 | =============
7 | TemGym Basic
8 | =============
9 |
10 | TemGym Basic is a ray-tracing software that models and visualises the first order behaviour of
11 | components inside a transmission electron microscope.
12 |
13 | .. image:: /img/GUI_graphic.svg
14 | :width: 900px
15 |
16 | The interactive models we generate are designed with a focus
17 | on educating new users on how the basic alignments work inside a TEM.
18 | Our code is also capable of producing publication
19 | quality ray diagrams with a single function call.
20 |
21 | Features
22 | --------
23 |
24 | * Interactive TEM models generated with pyqtgraph & PyQt5.
25 |
26 | * Ready-made example direct alignment models that enable users to learn at their own pace in an offline manner.
27 |
28 | * Generate interactive models of a transmission & scanning electron microscope.
29 |
30 | * Easy to use python code which allows users to create their own models by naming components inside a python list.
31 |
32 | * Generate publication quality ray diagrams of electron microscope experimental setups with one function call.
33 |
34 | Component Overview
35 | ------------------
36 | Our python code consists of 8 different electron microscope components which can be combined
37 | to create a model microscope.
38 |
39 | * Lens
40 | * Astigmatic Lens
41 | * Deflector
42 | * Double Deflector
43 | * Aperture
44 | * Stigmator
45 | * Biprism
46 | * Sample
47 |
48 | We can easily create an example model containing all of these components by first importing the required packages
49 | into a python script.
50 |
51 | .. literalinclude:: /../pyqtgraph_examples/all_components_example_pyqt.py
52 | :language: python
53 | :lines: 1-6
54 |
55 | Then we add the components into a list, and specify their position inside the microscope on the z-axis.
56 |
57 | .. literalinclude:: /../pyqtgraph_examples/all_components_example_pyqt.py
58 | :language: python
59 | :lines: 8-19
60 |
61 | Then input the model into our pyqt function that creates the interactive 3D viewer and automatically populates
62 | the GUI.
63 |
64 | .. literalinclude:: /../pyqtgraph_examples/all_components_example_pyqt.py
65 | :language: python
66 | :lines: 21-22
67 |
68 | which generates an interactive window on your PC.
69 |
70 | .. image:: /img/all_components_example.png
71 |
72 | Contents
73 | --------
74 |
75 | .. toctree::
76 | :maxdepth: 2
77 | :caption: Tutorials
78 |
79 | source/usage
80 |
81 | .. toctree::
82 | :maxdepth: 2
83 | :caption: Examples
84 |
85 | source/tutorials
86 | source/showcases
87 | source/matplotlib
88 |
89 | .. toctree::
90 | :maxdepth: 1
91 | :caption: Reference
92 |
93 | source/documentation
94 | source/bibliography
95 | source/license
96 |
97 |
98 |
99 |
--------------------------------------------------------------------------------
/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=source
11 | set BUILDDIR=build
12 |
13 | %SPHINXBUILD% >NUL 2>NUL
14 | if errorlevel 9009 (
15 | echo.
16 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
17 | echo.installed, then set the SPHINXBUILD environment variable to point
18 | echo.to the full path of the 'sphinx-build' executable. Alternatively you
19 | echo.may add the Sphinx directory to PATH.
20 | echo.
21 | echo.If you don't have Sphinx installed, grab it from
22 | echo.https://www.sphinx-doc.org/
23 | exit /b 1
24 | )
25 |
26 | if "%1" == "" goto help
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/source/bibliography.rst:
--------------------------------------------------------------------------------
1 | ============
2 | Bibliography
3 | ============
4 |
5 | Papers
6 | ^^^^^^
7 |
8 | Papers mentioned accross the documentation are referenced here.
9 |
10 | - Toshiaki Tanigaki, Yoshikatsu Inada, Shinji Aizawa, Takahiro Suzuki, Hyun Soon Park,
11 | Tsuyoshi Matsuda, Akira Taniyama, Daisuke Shindo, and Akira Tonomura , "Split-illumination electron holography",
12 | Appl. Phys. Lett. 101, 043101 (2012) https://doi.org/10.1063/1.4737152
13 |
14 |
--------------------------------------------------------------------------------
/docs/source/documentation.rst:
--------------------------------------------------------------------------------
1 | =============
2 | Documentation
3 | =============
4 |
5 | model.py
6 | --------
7 | .. automodule:: temgymbasic.model
8 | :members:
9 | :special-members: __init__
10 |
11 | components.py
12 | -------------
13 | .. automodule:: temgymbasic.components
14 | :members:
15 | :special-members: __init__
16 |
17 | run.py
18 | ------
19 | .. automodule:: temgymbasic.run
20 | :members:
21 | :special-members: __init__
22 |
23 | shapes.py
24 | ---------
25 | .. automodule:: temgymbasic.shapes
26 | :members:
27 | :special-members: __init__
28 |
29 |
30 | functions.py
31 | ------------
32 | .. automodule:: temgymbasic.functions
33 | :members:
34 | :special-members: __init__
35 |
36 | gui.py
37 | ------
38 | .. automodule:: temgymbasic.gui
39 | :members:
40 | :special-members: __init__
41 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/docs/source/license.rst:
--------------------------------------------------------------------------------
1 | =======
2 | License
3 | =======
4 |
5 | ``TemGym Basic`` is licensed under the :choosealicense:`gpl-3.0`
6 |
7 | .. license-info:: GPL-3.0
8 |
9 | .. license::
10 | :file: ../LICENSE.md
--------------------------------------------------------------------------------
/docs/source/matplotlib.rst:
--------------------------------------------------------------------------------
1 | ============================
2 | Ray Diagrams with Matplotlib
3 | ============================
4 |
5 | Model TEM
6 | ---------
7 |
8 | The same code which creates an interactive model via pyqtgraph of the specified microscope components
9 | can also be used to create a static ray diagram in matplotlib. The highlighted lines show the changes that need
10 | to me made to create a matplotlib diagram rather than in an interactive pyqtgraph model.
11 |
12 | .. literalinclude:: /../matplotlib_examples/model_tem_example_matplotlib.py
13 | :language: python
14 | :emphasize-lines: 31-34
15 |
16 | which produces this model:
17 |
18 | .. image:: /img/model_tem.svg
19 | :width: 600px
20 |
21 |
--------------------------------------------------------------------------------
/docs/source/showcases.rst:
--------------------------------------------------------------------------------
1 | ===========================
2 | Showcase Interactive Models
3 | ===========================
4 |
5 | The models presented below showcase some of the other modelling capabilities of our software.
6 |
7 | Biprism
8 | -------
9 | We can combine three biprism components created in our software to make an interactive
10 | version of the microscope setup presented in this paper from tonamura in 2012 (See Bibliography).
11 |
12 | .. image:: /img/biprism_model.png
13 | :width: 600px
14 |
15 | Model TEM
16 | ---------
17 | Adopting a qualitative schematic found here for a JEOL 2010F Transmission Electron Microscope,
18 | we can recreate an interactive alignment model of this microscope using the components we have created.
19 | The code which creates this model is located in the python script ''model_tem_example_pyqt.py''
20 |
21 | .. literalinclude:: /../pyqtgraph_examples/model_tem_example_pyqt.py
22 | :language: python
23 |
24 | .. image:: /img/model_tem_example.png
25 | :width: 600px
26 | :alt: project
27 |
28 | Model SEM
29 | ---------
30 | Also simply using a SEM schematic found on wikipedia, we can also easily recreate an alignment model of
31 | showing how the beam inside a scanning electron microscope operates.
32 |
33 | .. literalinclude:: /../pyqtgraph_examples/model_sem_example_pyqt.py
34 | :language: python
35 |
36 | .. image:: /img/model_sem_example.png
37 | :width: 600px
38 |
--------------------------------------------------------------------------------
/docs/source/tutorials.rst:
--------------------------------------------------------------------------------
1 | ==================
2 | Example Alignments
3 | ==================
4 |
5 | Lens & Rotation Centre Alignment
6 | --------------------------------
7 | Let's begin with a tutorial using the simplest model our programme can produce: A beam, a single lens and a detector.
8 | Run the model "lens_example_pyqt" (via the `.exe `_ , or the `python script `_), and become familiar with the interface.
9 | The important gui components are annotated in the image below.
10 |
11 | .. image:: /img/simple_lens_annotated.png
12 | :width: 600px
13 |
14 |
15 | With this basic model, we can demonstrate
16 | the principle of one of the direct alignments of a TEM, the rotation centre alignment. This alignment
17 | oscillates the current to the objective lens in a TEM, which in turn oscillates the focal length of the
18 | objective lens.
19 |
20 | Using the information of how the spot responds, we can then adjust the tilt of the beam before the lens
21 | so that it lies paralell to the optic axis. In a real TEM, this is performed with an image of a sample or
22 | grid inside the TEM. We cannot easily model a sample in our model, but by visualising the beam spot we
23 | can recreate the behaviour of this alignment.
24 |
25 | Tilt the beam off axis and turn on the lens wobble by ticking the check box "Wobble Lens Current".
26 |
27 | .. figure:: /img/simple_lens_wobble_on.png
28 | :width: 600px
29 |
30 | Rotation centre is misaligned, as the beam is away from the optic axis.
31 |
32 |
33 | Notice how the beam moves accross the screen as the focal length changes. We wish to align the rotation centre
34 | by tilting the beam so that the spot no longer moves, and so that it only contracts in and out when the
35 | focal length changes. Bring the beam back onto the centre of the lens and thus paralell to the optic axis
36 | by adjusting the tilt of the beam. When the beam spot stops moving, the rotation centre is now aligned.
37 |
38 | .. figure:: /img/simple_lens_aligned.png
39 | :width: 600px
40 |
41 | Rotation centre aligned.
42 |
43 |
44 | Beam Tilt/Shift Alignment
45 | -------------------------
46 | In this basic model of the beam shift/tilt alignment, we use a pair of deflectors, a lens, and a detector
47 | to make the model interactive. For the beam shift and beam tilt alignment, the goal is to find the
48 | "deflector ratio" setting such that the beam purely shifts or purely tilts in the detector plane.
49 | The deflector ratio value is a multiplier which dictates how the lower deflector responds to a deflection
50 | provided by the upper deflector.
51 |
52 | For example, if the upper deflector adds a deflection of 0.5 radians to the beam, and the deflector ratio
53 | is set to -1, the lower deflector will add a deflection of -0.5 radians to the beam, cancelling out the
54 | deflection from the upper deflector. This will then shift the beam over the sample and keep the beam paralell to
55 | the optic axis.
56 |
57 | Another layer of complication is added because alignment manuals typically explain this alignment
58 | in terms of "Pivot Points", and there is a seperate pivot point for both beam tilt and beam shift.
59 | Pivot points are simply where the beam pivots as a result of the settings of the deflector, and the
60 | location and focal length of a lens after it.
61 |
62 | |pic1| |pic2|
63 |
64 | .. |pic1| image:: /img/beam_shift_basic.svg
65 | :width: 45%
66 | :class: with-border
67 | .. |pic2| image:: /img/beam_tilt_basic.svg
68 | :class: with-border
69 | :width: 45%
70 |
71 | For a pure beam shift setting, the beam needs to go over the sample and into the lens paralell to the optic axis,
72 | and this will cause all rays to converge or "pivot" on the focal point of this lens (a.k.a the back focal plane).
73 | For beam tilt, the beam needs to pivot about a point on the sample before the lens, and this requires that
74 | our deflector ratio is set so that all rays go through the front focal plane, which is where the pivot point
75 | for beam tilt is located.
76 |
77 | Also note that the deflector ratio that we need to find is a
78 | function of the distances between each component and of the focal length of the lens.
79 | When creating this model, we placed the components at convenient distances so that the deflector ratio for
80 | beam shift and beam tilt are convenient values.
81 |
82 | Beam Shift Alignment
83 | ^^^^^^^^^^^^^^^^^^^^
84 | Run the basic Beam Shift/Tilt model and click the "Wobble Upper Deflector X" checkbox.
85 |
86 | .. image:: /img/beam_tilt_shift_wobble_circled.png
87 | :width: 500px
88 |
89 | Set the deflector ratio to -1 and see that the spot on the detector is now stationary.
90 | You have correctly aligned the beam shift pivot point.
91 |
92 | Beam Tilt Alignment
93 | ^^^^^^^^^^^^^^^^^^^
94 | Now adjust the deflector ratio so that it is set to -2, and see that the beam tilts about a single point
95 | in the sample plane on the 3D viewer. Note that the beam will still oscillate on the detector, as another lens needs
96 | to be added to the model to image the beam tilt pivot point.
97 |
98 | Condenser Astigmatism Alignment
99 | -------------------------------
100 | In this alignment we introduce two new components, an astigmatic lens, and a stigmator. In our model, an
101 | astigmatic lens is simply a lens where the focal length can be adjusted on each axis. This captures the behaviour
102 | of a real lens in a TEM, which cannot perfectly focus in both x & y. This is because a real lens cannot be
103 | manufactured to be perfectly circular, and will thus have two different focal lengths
104 | on each axis. The component which is used to correct for this is a stigmator. This is composed of two
105 | quadrupole magnets which when the current to each is adjusted, can correct for astigmatism in a lens.
106 |
107 | .. figure:: /img/condenser_asigmatism_base.png
108 | :width: 500px
109 |
110 | Elliptical beam indicates condenser asigmatism is misaligned
111 |
112 |
113 | Load the "Condenser Asigmatism" alignment and adjust the axial width of the beam to make the spot larger.
114 | You can also adjust the number of rays so that the beam spot appears filled in, if your PC can handle a larger
115 | amount of rays. Adjust the focal length of the astigmatic lens until the beam appears elliptical. Note that in practise
116 | you as a user would not have control of the astigmatism of the lens!
117 | Use the condenser astigmatism sliders to correct for the astigmatism in the lens by making the beam
118 | appear round again.
119 |
120 | .. figure:: /img/condenser_asigmatism_corrected.png
121 | :width: 500px
122 |
123 | Circular beam means we have corrected the condenser astigmatism
124 |
125 | Condenser Aperture Alignment
126 | ----------------------------
127 | Similar to almost every other alignment in the microscope,
128 | this alignment requires that the aperture is centred on the optic axis. Run the alignment "Condenser Aperture",
129 | and adjust the strength of the condenser lens focal length.
130 |
131 | .. figure:: /img/condenser_aperture_before_lens_adjust.png
132 | :width: 500px
133 |
134 | Beamspot position before adjusting focal length.
135 |
136 | Notice how as you do this, the beam spot appears
137 | to move accross the screen.
138 |
139 | .. figure:: /img/condenser_aperture_after_lens_adjust.png
140 | :width: 500px
141 |
142 | Beamspot position after adjusting focal length.
143 |
144 | This happens because the aperture is not centred on the optic axis. Adjust the position
145 | of the aperture so that it is centred on the column, and adjust the focal length once more. Notice that the beam
146 | only changes size, and does not move accross the screen.
147 |
148 |
--------------------------------------------------------------------------------
/docs/source/usage.rst:
--------------------------------------------------------------------------------
1 | ============
2 | Installation
3 | ============
4 |
5 | Quickstart Executables
6 | ----------------------
7 | Download and run the example executable for your plafrom (Windows, MACOS or Ubuntu) from `Zenodo `_ to
8 | instantly access example models of microscope alignments without any coding knowledge.
9 |
10 | Python
11 | ------
12 | To run our interactive models via python, we recommend you use `miniconda `_ .
13 |
14 | After downloading and installing, create a new environment in a terminal::
15 |
16 | $ conda create --name temgymbasic python=3.9.6
17 |
18 | activate the environment, and install pip::
19 |
20 | $ conda activate temgymbasic
21 |
22 | $ conda install pip
23 |
24 | then install `temgymbasic `_::
25 |
26 | $ pip install temgymbasic
27 |
28 | To download and run the python code of examples, see our `GitHub `_ and the folder `pyqtgraph_examples `_.
29 |
30 | Creating Executables
31 | --------------------
32 |
33 | To create the .exe files that we have shared in the zenodo,
34 | install the package pyinstaller inside your temgymbasic environment::
35 |
36 | $ pip install pyinstaller
37 |
38 | then run the appropiate terminal script, make_exe.bat (Windows) or make_exe.sh (Linux, MACOS).
39 | This process may take some time to create the executables for all 9 examples (~30 minutes)
40 |
41 |
--------------------------------------------------------------------------------
/fourdstem_overfocused.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AMCLab/TemGymBasic/35f2cf0a3f96263bda7576dbd8b68847ee76c4bb/fourdstem_overfocused.zip
--------------------------------------------------------------------------------
/live_calibration_examples/fourdstem_example_no_gui.py:
--------------------------------------------------------------------------------
1 |
2 | from temgymbasic import components as comp
3 | from temgymbasic.model import Model
4 | from temgymbasic.functions import make_test_sample, get_image_from_rays
5 |
6 | import matplotlib.pyplot as plt
7 | import numpy as np
8 |
9 | #Load the fourdstem_overfocused sample to use as comparison
10 | fourdstem_overfocused = np.load(r"C:\Users\User\Documents\Code\src\temgymbasic\fourdstem_overfocused.npz")['arr_0']
11 | sample = make_test_sample()
12 |
13 | #Set number of detector pixels and detector size. For now keeping it simple
14 | #and assuming a square detector.
15 | detector_pixels = 256 #Detector pixels
16 | detector_pixel_size = 0.000050 #pixel size in metres
17 | detector_width = detector_pixels * detector_pixel_size #detector size
18 |
19 | #We need to estimate the size of the sample in the sample plane,
20 | #so we've used the scan pixel size and the number of pixels in the sample to do so.
21 | #If we want to sub sample the scan at a lower number of position while keeping the same sample size,
22 | #we can easily change the number of scan pixels later before running an experiment.
23 | scan_pixels = sample.shape[0] #for now we match the number of pixels in the sample image
24 | scan_pixel_size = 0.000001 #m
25 | sample_width = scan_pixels * scan_pixel_size
26 |
27 | #Set experiment parameters
28 | camera_length = 0.15
29 | overfocus = 0.001
30 | semiconv = 0.020
31 | scan_rotation = 0
32 |
33 | #Create a list of components to model a simplified 4DSTEM experiment
34 | components = [comp.DoubleDeflector(name = 'Scan Coils', z_up = 0.3, z_low = 0.25),
35 | comp.Lens(name = 'Lens', z = 0.20),
36 | comp.Sample(name = 'Sample', sample = sample, z = camera_length, width = sample_width),
37 | comp.DoubleDeflector(name = 'Descan Coils', z_up = 0.1, z_low = 0.05, scan_rotation = scan_rotation)
38 | ]
39 |
40 | #Create the model Electron microscope. Initially we can create a parallel circular beam
41 | #with 32000 rays to view a sample image
42 | model = Model(components, beam_z = 0.4, beam_type = 'paralell', num_rays = 2**15,
43 | experiment = '4DSTEM', detector_pixels = detector_pixels,
44 | detector_size = detector_width)
45 |
46 |
47 | '''Now we run the first experiment, which is to try and recreate a single 4DSTEM image in the
48 | fourdstem_overfocused dataset obtained in the adjustment -- screen capture state.ipynb
49 | Note we have performed this calculation with 0 scan rotation firstly to simplify comparison'''
50 |
51 | #Set the scan_pixel_size and scan_pixels of the model experiment
52 | model.scan_pixel_size = scan_pixel_size
53 |
54 | #Since the parameters such as overfocus and semi convergence angle are derived parameters from the settings
55 | #of the components inside the electron microscope, we need to update the parameters of the model with the following functions
56 | model.set_obj_lens_f_from_overfocus(overfocus)
57 | model.set_beam_radius_from_semiconv(semiconv)
58 |
59 | #For a basic circular beam use this function to make the model rays: note that the beam_type = 'parallel' initialised in the creation of the
60 | #model class triggers the creation of a circular beam (parallel is not a great name I know). The number of rays that fill this
61 | #circular beam is defined by num_rays also initialised in the "Model" class.
62 | model.generate_rays()
63 |
64 | #model.step() is where the matrix multiplication between the position matrix of each ray, stored in the variable (model.r),
65 | #and the component transfer matrix occurs.
66 | model.step()
67 |
68 | #Note below: model.r is the ray matrix that stores positions and slopes of each ray.
69 | #This matrix is of shape (steps, 5, num rays), where
70 | #steps is defined by the number of components. The middle shape index of 5 is so because each ray position matrix
71 | #is of shape (5, 1): [x, slope_x, y, slope_y, 1]. The 1 is neccessary at the end to facilitate the addition of a "slope"
72 | #to the ray which can be performed by a deflector component.
73 | #We can index the positions of rays at each component if we know the index of that component in the ray matrix.
74 | #We have created a special variable to index the ray positions of the sample called model.sample_r_idx
75 | sample_rays_x = model.r[model.sample_r_idx, 0, :]
76 | sample_rays_y = model.r[model.sample_r_idx, 2, :]
77 |
78 | #Detector ray positions. Detector is always at the bottom, so -1 as the component index finds it's
79 | #ray positions.
80 | detector_rays_x = model.r[-1, 0, :]
81 | detector_rays_y = model.r[-1, 2, :]
82 |
83 | #Function to obtain the sample_image
84 | detector_ray_image, detector_sample_image, sample_pixel_coords, _ = get_image_from_rays(
85 | detector_rays_x, detector_rays_y,
86 | sample_rays_x, sample_rays_y,
87 | model.detector_size,
88 | model.detector_pixels,
89 | model.components[model.sample_idx].sample_size,
90 | model.components[model.sample_idx].sample_pixels,
91 | model.components[model.sample_idx].sample
92 | )
93 |
94 | #Show results
95 | plt.figure()
96 | plt.imshow(detector_sample_image)
97 |
98 | plt.figure()
99 | plt.imshow(fourdstem_overfocused[64, 64, :, :])
100 |
101 | plt.figure()
102 | plt.imshow(abs(fourdstem_overfocused[64, 64, :, :] - detector_sample_image))
103 |
104 | #Comparison between two is nearly right, but it's not exact yet. This could be because of how my circular beam is created
105 | #which tries to evenly fill a radius with the chosen amount of rays, thus I may not be sampling the same pixels perfectly
106 | #like in Dieter's code. I also worry that there might be an accidental sampling error
107 | #of pixels in the sample (maybe rounding the ray coordinate to the nearest pixel is wrong?. Will investigate further soon.
108 |
109 |
110 | '''For a custom number of rays (say 3), we can use the following code. Note, that in order to
111 | achieve the desired semi convergence angle before the sample, we have called the function
112 | "model.set_beam_radius_from_semiconv" earlier, which will set the correct beam_radius at the "gun" (initial ray position)
113 | . The beam radius this function finds should be used to
114 | set the outer radius of the ray bundle, which will achieve the desired semi_angle of the beam at crossover after the lens.'''
115 |
116 | #Creation of the ray matrix which is of shape (steps in model, 5, number of rays)
117 | model.r = np.zeros((model.steps, 5, 3))
118 |
119 | #Due to how we have set up the model to add a deflection, the 5th entry in each
120 | #ray matrix should be 1
121 | model.r[:, 4, :] = np.ones(3)
122 |
123 | #Create a ray that starts with a position of 45 degrees above the x-axis, and
124 | #a radius of the model.beam_radius that gives the desired semi convergence angle
125 | model.r[:, 0, 1] = model.beam_radius*np.cos(np.pi/4) #x position
126 | model.r[:, 2, 1] = model.beam_radius*np.sin(np.pi/4) #y position
127 |
128 | #Create a ray that starts with a position of -135 degrees below the x-axis, and
129 | #a radius of the model.beam_radius that gives the desired semi convergence angle
130 | model.r[:, 0, 2] = model.beam_radius*np.cos(-3*np.pi/4) #x position
131 | model.r[:, 2, 2] = model.beam_radius*np.sin(-3*np.pi/4) #y position
132 |
133 | #Note that this time we don't call model.generate_rays() because
134 | #that would overwrite out custom ray matrix of 3 rays
135 | model.step()
136 |
137 | sample_rays_x = model.r[model.sample_r_idx, 0, :]
138 | sample_rays_y = model.r[model.sample_r_idx, 2, :]
139 |
140 | detector_rays_x = model.r[-1, 0, :]
141 | detector_rays_y = model.r[-1, 2, :]
142 |
143 | detector_ray_image, detector_sample_image, sample_pixel_coords, _ = get_image_from_rays(
144 | detector_rays_x, detector_rays_y,
145 | sample_rays_x, sample_rays_y,
146 | model.detector_size,
147 | model.detector_pixels,
148 | model.components[model.sample_idx].sample_size,
149 | model.components[model.sample_idx].sample_pixels,
150 | model.components[model.sample_idx].sample
151 | )
152 |
153 | #Plot an image of 3 rays
154 | plt.figure()
155 | plt.imshow(detector_sample_image)
156 |
157 | '''We can also plot a number of scan positions on the sample, to see which parts of the sample
158 | are projected forward to the detector'''
159 | #We can set a lower amount of scan_pixels if we want to sample at a different spacing
160 | model.scan_pixels = 4
161 | num_sample_positions = model.scan_pixels**2
162 |
163 | #go back to the circular beam:
164 | model.generate_rays()
165 |
166 | fig, ax = plt.subplots()
167 |
168 | for _ in range(num_sample_positions):
169 |
170 | #We update the scan coil ratio to index the first scan position. This function
171 | #finds the deflector settings which will go through the beam pivot point that will perform perfect
172 | #beam_shift on the sample. Initial model.scan_pixel_x and model.scan_pixel_y are both 0, which means that the first beam
173 | #position is in the top left corner of the sample
174 | model.update_scan_coil_ratio()
175 | model.step()
176 |
177 | #Obtain the scan position of the beam in pixel coordinates on the sample
178 | px_x = scan_pixels/(2*model.scan_pixels)+(model.scan_pixel_x/model.scan_pixels)*sample.shape[0]-1
179 | px_y = scan_pixels/(2*model.scan_pixels)+(model.scan_pixel_y/model.scan_pixels)*sample.shape[0]-1
180 |
181 | #Move to the next scan position
182 | model.update_scan_position()
183 |
184 | sample_rays_x = model.r[model.sample_r_idx, 0, :]
185 | sample_rays_y = model.r[model.sample_r_idx, 2, :]
186 |
187 | #Detector ray positions
188 | detector_rays_x = model.r[-1, 0, :]
189 | detector_rays_y = model.r[-1, 2, :]
190 |
191 | #Create detector image of rays: get_image_from_rays converts sample and detector positions
192 | #to sample and detector coordinates
193 | detector_ray_image, detector_sample_image, sample_pixel_coords, _ = get_image_from_rays(
194 | detector_rays_x, detector_rays_y,
195 | sample_rays_x, sample_rays_y,
196 | model.detector_size,
197 | model.detector_pixels,
198 | model.components[model.sample_idx].sample_size,
199 | model.components[model.sample_idx].sample_pixels,
200 | model.components[model.sample_idx].sample
201 | )
202 |
203 | #Plot the beam pixel coordinates on the sample to see which part of the sample we are imaging.
204 | ax.plot(sample_pixel_coords[:, 1], sample_pixel_coords[:, 0], '.r', alpha = 0.1, zorder = 1)
205 | ax.plot(px_y, px_x, '.b', zorder = 2)
206 |
207 | ax.imshow(sample, zorder = 0)
208 | plt.show()
209 |
210 |
211 |
212 |
213 |
214 |
--------------------------------------------------------------------------------
/live_calibration_examples/fourdstem_example_pyqt_large_scale.py:
--------------------------------------------------------------------------------
1 |
2 | from temgymbasic import components as comp
3 | from temgymbasic.model import Model
4 | from temgymbasic.run import run_pyqt
5 | from temgymbasic.functions import make_test_sample
6 | from PyQt5.QtWidgets import QApplication
7 | import os
8 | import sys
9 |
10 | def main():
11 | sample = make_test_sample()
12 |
13 | camera_length = 0.15
14 |
15 | components = [comp.DoubleDeflector(name = 'Scan Coils', z_up = 0.8, z_low = 0.7),
16 | comp.Lens(name = 'Lens', z = 0.30, f = -0.05),
17 | comp.Sample(name = 'Sample', sample = sample, z = camera_length),
18 | comp.DoubleDeflector(name = 'Descan Coils', z_up = 0.1, z_low = 0.09)
19 | ]
20 |
21 | model = Model(components, beam_z = 1, beam_type = 'paralell', num_rays = 2**12,
22 | experiment = '4DSTEM')
23 |
24 | viewer = run_pyqt(model)
25 |
26 | return viewer
27 |
28 | if __name__ == '__main__':
29 | AppWindow = QApplication(sys.argv)
30 | viewer = main()
31 | viewer.show()
32 |
33 | AppWindow.exec_()
34 |
--------------------------------------------------------------------------------
/live_calibration_examples/fourdstem_example_pyqt_micron_scale.py:
--------------------------------------------------------------------------------
1 |
2 | from temgymbasic import components as comp
3 | from temgymbasic.model import Model
4 | from temgymbasic.run import run_pyqt
5 | from temgymbasic.functions import make_test_sample
6 | from PyQt5.QtWidgets import QApplication
7 | import os
8 | import sys
9 |
10 | def main():
11 | sample = make_test_sample()
12 |
13 | detector_pixels = 256
14 | detector_pixel_size = 0.000050 #pixel size in metres
15 | detector_width = detector_pixels * detector_pixel_size
16 |
17 | scan_pixels = 256
18 | scan_pixel_size = 0.000001 #for now assume square sample
19 | sample_width = scan_pixels * scan_pixel_size
20 |
21 | camera_length = 0.15
22 | overfocus = 0.001
23 | semiconv = 0.020
24 |
25 | components = [comp.DoubleDeflector(name = 'Scan Coils', z_up = 0.3, z_low = 0.25),
26 | comp.Lens(name = 'Lens', z = 0.20, f = -0.05),
27 | comp.Sample(name = 'Sample', sample = sample, z = camera_length, width = sample_width),
28 | comp.DoubleDeflector(name = 'Descan Coils', z_up = 0.1, z_low = 0.09)
29 | ]
30 |
31 | model = Model(components, beam_z = 0.4, beam_type = 'paralell', num_rays = 2**12,
32 | experiment = '4DSTEM', detector_pixels = detector_pixels,
33 | detector_size = detector_width)
34 |
35 | model.set_beam_radius_from_semiconv(semiconv)
36 | model.set_obj_lens_f_from_overfocus(overfocus)
37 |
38 | viewer = run_pyqt(model)
39 |
40 | return viewer
41 |
42 | if __name__ == '__main__':
43 | AppWindow = QApplication(sys.argv)
44 | viewer = main()
45 | viewer.show()
46 |
47 | AppWindow.exec_()
48 |
--------------------------------------------------------------------------------
/matplotlib_examples/all_components_example_matplotlib.py:
--------------------------------------------------------------------------------
1 |
2 | from temgymbasic import components as comp
3 | from temgymbasic.model import Model
4 | from temgymbasic.run import show_matplotlib
5 | import numpy as np
6 |
7 | #Create List of Components
8 | components = [comp.Lens(name='Lens', z=0.85),
9 | comp.AstigmaticLens(name='Astigmatic Lens', z=0.7),
10 | comp.Quadrupole(name='Quadrupole', z=0.63),
11 | comp.DoubleDeflector(name='Double Deflector', z_up=0.5, z_low=0.45),
12 | comp.Deflector(name='Single Deflector', z=0.3, defx=0, defy=0),
13 | comp.Biprism(name='Biprism', z=0.2, theta = np.pi/2, deflection = 0.2),
14 | comp.Aperture(name='Aperture', z=0.10, aperture_radius_inner=0.05)]
15 |
16 | #Generate TEM Model
17 | model_ = Model(components, beam_z=1, beam_type='x_axial',
18 | num_rays=32, gun_beam_semi_angle=0.15)
19 |
20 | #Save Figure with Matplotlib
21 | fig, ax = show_matplotlib(model_, name = 'all_components.svg', label_fontsize = 18)
22 | fig.savefig('all_components.svg', dpi = 500)
23 |
--------------------------------------------------------------------------------
/matplotlib_examples/basic_biprism_example_matplotlib.py:
--------------------------------------------------------------------------------
1 | from temgymbasic import components as comp
2 | from temgymbasic.model import Model
3 | from temgymbasic.run import show_matplotlib
4 | import numpy as np
5 |
6 | components = [comp.Biprism(name='Biprism', z=0.6, theta=np.pi/2, width=0.01),
7 | comp.Lens(name='Lens', z=0.5, f=-0.1),
8 | comp.Aperture(name='Aperture', z=0.25, aperture_radius_inner = 0.10)]
9 |
10 | axis_view = 'x_axial'
11 | model_ = Model(components, beam_z=1.0, beam_type='x_axial',
12 | num_rays=32, gun_beam_semi_angle=0.15)
13 |
14 | name = 'biprism_model.svg'
15 | fig, ax = show_matplotlib(model_, name = name)
16 | fig.suptitle('Basic Biprism', fontsize=32)
17 | fig.savefig(name, dpi = 500)
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/matplotlib_examples/beam_tilt_shift_advanced_example_matplotlib.py:
--------------------------------------------------------------------------------
1 | from temgymbasic import components as comp
2 | from temgymbasic.model import Model
3 | from temgymbasic.run import show_matplotlib
4 |
5 |
6 | components = [comp.Lens(name = 'Condenser Lens', z = 1.2, f = -0.1),
7 | comp.DoubleDeflector(name = 'Double Deflector', z_up = 0.9, z_low = 0.8, updefx = 1, lowdefx = -1),
8 | comp.Lens(name = 'Objective Lens', z = 0.6, f = -0.1),
9 | comp.Sample(name = 'Sample', z = 0.5),
10 | comp.Lens(name = 'Intermediate Lens', z = 0.4, f = -0.075),
11 | comp.Lens(name = 'Projector Lens', z = 0.1, f = -0.1)]
12 |
13 | axis_view = 'x_axial'
14 | model_ = Model(components, beam_z=1.5, beam_type='x_axial',
15 | num_rays=32, gun_beam_semi_angle=0.15)
16 |
17 | name = 'beam_tilt.svg'
18 | fig, ax = show_matplotlib(model_, name = name)
19 | fig.suptitle('Beam Pivot Points', fontsize=32)
20 | fig.savefig(name, dpi = 500)
21 |
--------------------------------------------------------------------------------
/matplotlib_examples/beam_tilt_shift_basic_example_matplotlib.py:
--------------------------------------------------------------------------------
1 | from temgymbasic import components as comp
2 | from temgymbasic.model import Model
3 | from temgymbasic.run import show_matplotlib
4 |
5 |
6 | #Showing Beam Shift Pivot Point
7 | components = [comp.DoubleDeflector(name='Double Deflector', z_up=0.80, z_low=0.60, updefx=0.4, lowdefx=-0.4),
8 | comp.Sample(name='Sample', z=0.4),
9 | comp.Lens(name='Lens', z=0.20, f=-0.2),
10 | ]
11 |
12 | axis_view = 'x_axial'
13 | model_ = Model(components, beam_z=1.0, beam_type='x_axial',
14 | num_rays=64, gun_beam_semi_angle=0.0001)
15 |
16 | fig, ax = show_matplotlib(model_, name='beam_shift_basic.svg')
17 | fig.suptitle('Beam Shift', fontsize = 40)
18 | fig.savefig('beam_shift_basic.svg')
19 |
20 | #Showing Beam Tilt Pivot Point
21 | components = [comp.DoubleDeflector(name='Double Deflector', z_up=0.80, z_low=0.60, updefx=0.4, lowdefx=-0.8),
22 | comp.Sample(name='Sample', z=0.4),
23 | comp.Lens(name='Lens', z=0.20, f=-0.2),
24 | ]
25 |
26 | axis_view = 'x_axial'
27 | model_ = Model(components, beam_z=1.0, beam_type='x_axial',
28 | num_rays=64, gun_beam_semi_angle=0.0001)
29 |
30 | fig,ax = show_matplotlib(model_, name='beam_tilt_basic.svg')
31 | fig.suptitle('Beam Tilt', fontsize = 40)
32 | fig.savefig('beam_tilt_basic.svg')
--------------------------------------------------------------------------------
/matplotlib_examples/condenser_aperture_example_matplotlib.py:
--------------------------------------------------------------------------------
1 | import sys
2 | sys.path.append(r"..")
3 | from components import Lens, Aperture
4 | from model import Model
5 | import numpy as np
6 | from run import show_matplotlib
7 |
8 | components = [Aperture(name = 'Condenser Aperture', z = 0.3, x = 0, y = 0, aperture_radius_inner = 0.075),
9 | Lens(name = 'Condenser Lens', z = 0.2, f = -0.5)]
10 |
11 |
12 | model_ = Model(components, beam_z=1.0, beam_type='x_axial',
13 | num_rays=32, gun_beam_semi_angle=0.15)
14 |
15 | name = 'condenser_aperture.svg'
16 | fig, ax = show_matplotlib(model_, name = name)
17 | fig.suptitle('Condenser Aperture', fontsize=32)
18 | fig.savefig(name, dpi = 500)
19 |
20 |
21 |
--------------------------------------------------------------------------------
/matplotlib_examples/condenser_astigmatism_example_matplotlib.py:
--------------------------------------------------------------------------------
1 | from temgymbasic import components as comp
2 | from temgymbasic.model import Model
3 | from temgymbasic.run import show_matplotlib
4 |
5 | components = [
6 | comp.AstigmaticLens(name='Condenser Lens', z=0.7, fx=-0.4, fy=-0.6),
7 | comp.Quadrupole(name='Condenser Stigmator', z=0.5)
8 | ]
9 |
10 | axis_view = 'x_axial'
11 | model_ = Model(components, beam_z=1.0, beam_type='x_axial',
12 | num_rays=32, gun_beam_semi_angle=0.15)
13 |
14 | name = 'condenser_astigmatism.svg'
15 | fig, ax = show_matplotlib(model_, name = name)
16 | fig.suptitle('Condenser Astigmatism', fontsize=32)
17 | fig.savefig(name, dpi = 500)
18 |
--------------------------------------------------------------------------------
/matplotlib_examples/model_sem_example_matplotlib.py:
--------------------------------------------------------------------------------
1 | from temgymbasic import components as comp
2 | from temgymbasic.model import Model
3 | from temgymbasic.run import show_matplotlib
4 |
5 | components = [comp.Lens(name = '1st Condenser Lens', z = 1.5, f = -0.05),
6 | comp.Aperture(name = 'Spray Aperture', z = 1.2, aperture_radius_inner = 0.05),
7 | comp.Lens(name = '2nd Condenser Lens', z = 1.0, f = -0.15),
8 | comp.DoubleDeflector(name = 'Deflection Coils', z_up = 0.8, z_low = 0.7),
9 | comp.Lens(name = 'Objective Lens', z = 0.5, f = -0.3),
10 | comp.Aperture(name = 'Objective Aperture', z = 0.4, aperture_radius_inner = 0.05),
11 | comp.Sample(name = 'Sample', z = 0.1)
12 | ]
13 |
14 | axis_view = 'x_axial'
15 | model_ = Model(components, beam_z=1.7, beam_type=axis_view,
16 | num_rays=65, gun_beam_semi_angle=0.15)
17 |
18 | name = 'model_sem.svg'
19 | fig, ax = show_matplotlib(model_, name = name)
20 | fig.suptitle('SEM Model', fontsize=32)
21 | fig.savefig(name, dpi = 500)
22 |
--------------------------------------------------------------------------------
/matplotlib_examples/model_tem_example_matplotlib.py:
--------------------------------------------------------------------------------
1 | from temgymbasic import components as comp
2 | from temgymbasic.model import Model
3 | from temgymbasic.run import show_matplotlib
4 |
5 | components = [comp.Lens(name = 'Electrostatic Lens', z = 3, f = -0.2),
6 | comp.DoubleDeflector(name = 'Gun Beam Deflectors', z_up = 2.8, z_low = 2.7),
7 | comp.Lens(name = '1st Condenser Lens', z = 2.6, f = -0.2),
8 | comp.Lens(name = '2nd Condenser Lens', z = 2.5, f = -0.2),
9 | comp.Aperture(name = 'Condenser Aperture', z = 2.3, aperture_radius_inner=0.05),
10 | comp.Quadrupole(name = 'Condenser Stig', z = 2.2),
11 | comp.DoubleDeflector(name = 'Condenser Deflectors', z_up = 2.1, z_low = 2.0),
12 | comp.Lens(name = 'Condenser Mini Lens', z = 1.8, f = -0.2),
13 | comp.Aperture(name = 'Objective Aperture', z = 1.7, aperture_radius_inner=0.05),
14 | comp.Lens(name = 'Objective Lens', z = 1.5, f = -0.2),
15 | comp.Quadrupole(name = 'Objective Stig', z = 1.4),
16 | comp.Lens(name = 'Objective Mini Lens', z = 1.3, f = -0.2),
17 | comp.DoubleDeflector(name = 'Image Shifts', z_up = 1.1, z_low = 1.0),
18 | comp.Aperture(name = 'Selected Area Aperture', z = 0.9, aperture_radius_inner=0.05),
19 | comp.Quadrupole(name = 'Intermediate Lens Stigmator', z = 0.8),
20 | comp.Lens(name = '1st Intermediate Lens', z = 0.7, f = -0.2),
21 | comp.Lens(name = '2nd Intermediate Lens', z = 0.6, f = -0.2),
22 | comp.Lens(name = '3rd Intermediate Lens', z = 0.5, f = -0.2),
23 | comp.DoubleDeflector(name = 'Projector Lens Deflectors', z_up = 0.4, z_low = 0.3),
24 | comp.Lens(name = 'Projector Lens', z =0.2, f = -0.2)
25 | ]
26 |
27 | axis_view = 'x_axial'
28 | model_ = Model(components, beam_z=3.5, beam_type='x_axial',
29 | num_rays=32, gun_beam_semi_angle=0.15)
30 |
31 | name = 'model_tem.svg'
32 | fig, ax = show_matplotlib(model_, name = name, label_fontsize = 14)
33 | fig.suptitle('TEM Model', fontsize=32)
34 | fig.savefig(name, dpi = 500)
35 |
36 |
--------------------------------------------------------------------------------
/matplotlib_examples/split_condenser_example_matplotlib.py:
--------------------------------------------------------------------------------
1 | from temgymbasic import components as comp
2 | from temgymbasic.model import Model
3 | from temgymbasic.run import show_matplotlib
4 |
5 | components = [comp.Biprism(name = 'Condenser Biprism', z = 0.7, theta=np.pi/2, width = 0.01),
6 | comp.Sample(name = 'Sample', z = 0.5, width = 0.2, x = -0.1),
7 | comp.Biprism(name = 'Biprism 1', z = 0.4, theta=np.pi/2, width = 0.01),
8 | comp.Biprism(name = 'Biprism 2', z = 0.2, theta=np.pi/2, width = 0.01)]
9 |
10 | axis_view = 'x_axial'
11 | model_ = Model(components, beam_z=1.0, beam_type='x_axial',
12 | num_rays=32, gun_beam_semi_angle=0.15)
13 |
14 |
15 | name = 'split_condenser_biprism.svg'
16 | fig, ax = show_matplotlib(model_, name = name)
17 | fig.suptitle('Split Condenser Biprism', fontsize=32)
18 | fig.savefig(name, dpi = 500)
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------
1 | [build-system]
2 | requires = ["hatchling"]
3 | build-backend = "hatchling.build"
4 |
5 | [project]
6 | name = "temgymbasic"
7 | version = "0.6.0.0"
8 | authors = [
9 | { name="David Landers", email="davidlanders2009@hotmail.com" },
10 | ]
11 | description = "An educational TEM ray tracing matrix package"
12 | readme = "README.md"
13 | Requires-Python = "3.9.6"
14 | classifiers = [
15 | "Programming Language :: Python :: 3",
16 | "License :: OSI Approved :: GNU General Public License v3 (GPLv3)",
17 | "Operating System :: OS Independent",
18 | ]
19 | dependencies = [
20 | 'pyqtgraph',
21 | 'pyopengl',
22 | 'numpy',
23 | 'triangle',
24 | 'PyQt5',
25 | 'matplotlib'
26 | ]
27 |
28 | [project.urls]
29 | "Homepage" = "https://github.com/AMCLab/TemGymBasic"
30 |
--------------------------------------------------------------------------------
/pyqtgraph_examples/4DStem_example_no_gui.py:
--------------------------------------------------------------------------------
1 |
2 | from temgymbasic import components as comp
3 | from temgymbasic.model import Model
4 | from temgymbasic.run import run_pyqt
5 | from temgymbasic.functions import make_test_sample
6 | from PyQt5.QtWidgets import QApplication
7 | import os
8 | import sys
9 |
10 | sample = make_test_sample()
11 |
12 |
13 | components = [comp.DoubleDeflector(name = 'Scan Coils', z_up = 0.81, z_low = 0.70),
14 | comp.Lens(name = 'Lens', z = 0.60, f = -0.05),
15 | comp.Sample(name = 'Sample', sample = sample, z = 0.5),
16 | comp.DoubleDeflector(name = 'Descan Coils', z_up = 0.4, z_low = 0.3)
17 | ]
18 |
19 | model_ = Model(components, beam_z = 1.0, beam_type = 'paralell', num_rays = 2**12, gun_beam_semi_angle = 0.05, beam_radius = 0.02, experiment = '4DSTEM')
20 |
21 |
22 |
--------------------------------------------------------------------------------
/pyqtgraph_examples/__pycache__/all_components_example_pyqt.cpython-39.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AMCLab/TemGymBasic/35f2cf0a3f96263bda7576dbd8b68847ee76c4bb/pyqtgraph_examples/__pycache__/all_components_example_pyqt.cpython-39.pyc
--------------------------------------------------------------------------------
/pyqtgraph_examples/__pycache__/beam_tilt_shift_advanced_example_pyqt.cpython-310.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AMCLab/TemGymBasic/35f2cf0a3f96263bda7576dbd8b68847ee76c4bb/pyqtgraph_examples/__pycache__/beam_tilt_shift_advanced_example_pyqt.cpython-310.pyc
--------------------------------------------------------------------------------
/pyqtgraph_examples/__pycache__/beam_tilt_shift_advanced_example_pyqt.cpython-39.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AMCLab/TemGymBasic/35f2cf0a3f96263bda7576dbd8b68847ee76c4bb/pyqtgraph_examples/__pycache__/beam_tilt_shift_advanced_example_pyqt.cpython-39.pyc
--------------------------------------------------------------------------------
/pyqtgraph_examples/__pycache__/beam_tilt_shift_basic_example_pyqt.cpython-310.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AMCLab/TemGymBasic/35f2cf0a3f96263bda7576dbd8b68847ee76c4bb/pyqtgraph_examples/__pycache__/beam_tilt_shift_basic_example_pyqt.cpython-310.pyc
--------------------------------------------------------------------------------
/pyqtgraph_examples/__pycache__/beam_tilt_shift_basic_example_pyqt.cpython-39.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AMCLab/TemGymBasic/35f2cf0a3f96263bda7576dbd8b68847ee76c4bb/pyqtgraph_examples/__pycache__/beam_tilt_shift_basic_example_pyqt.cpython-39.pyc
--------------------------------------------------------------------------------
/pyqtgraph_examples/__pycache__/biprism_example_pyqt.cpython-310.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AMCLab/TemGymBasic/35f2cf0a3f96263bda7576dbd8b68847ee76c4bb/pyqtgraph_examples/__pycache__/biprism_example_pyqt.cpython-310.pyc
--------------------------------------------------------------------------------
/pyqtgraph_examples/__pycache__/biprism_example_pyqt.cpython-39.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AMCLab/TemGymBasic/35f2cf0a3f96263bda7576dbd8b68847ee76c4bb/pyqtgraph_examples/__pycache__/biprism_example_pyqt.cpython-39.pyc
--------------------------------------------------------------------------------
/pyqtgraph_examples/__pycache__/condenser_aperture_example_pyqt.cpython-310.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AMCLab/TemGymBasic/35f2cf0a3f96263bda7576dbd8b68847ee76c4bb/pyqtgraph_examples/__pycache__/condenser_aperture_example_pyqt.cpython-310.pyc
--------------------------------------------------------------------------------
/pyqtgraph_examples/__pycache__/condenser_aperture_example_pyqt.cpython-39.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AMCLab/TemGymBasic/35f2cf0a3f96263bda7576dbd8b68847ee76c4bb/pyqtgraph_examples/__pycache__/condenser_aperture_example_pyqt.cpython-39.pyc
--------------------------------------------------------------------------------
/pyqtgraph_examples/__pycache__/condenser_astigmatism_example_pyqt.cpython-310.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AMCLab/TemGymBasic/35f2cf0a3f96263bda7576dbd8b68847ee76c4bb/pyqtgraph_examples/__pycache__/condenser_astigmatism_example_pyqt.cpython-310.pyc
--------------------------------------------------------------------------------
/pyqtgraph_examples/__pycache__/condenser_astigmatism_example_pyqt.cpython-39.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AMCLab/TemGymBasic/35f2cf0a3f96263bda7576dbd8b68847ee76c4bb/pyqtgraph_examples/__pycache__/condenser_astigmatism_example_pyqt.cpython-39.pyc
--------------------------------------------------------------------------------
/pyqtgraph_examples/__pycache__/example_exe.cpython-310.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AMCLab/TemGymBasic/35f2cf0a3f96263bda7576dbd8b68847ee76c4bb/pyqtgraph_examples/__pycache__/example_exe.cpython-310.pyc
--------------------------------------------------------------------------------
/pyqtgraph_examples/__pycache__/example_exe.cpython-39.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AMCLab/TemGymBasic/35f2cf0a3f96263bda7576dbd8b68847ee76c4bb/pyqtgraph_examples/__pycache__/example_exe.cpython-39.pyc
--------------------------------------------------------------------------------
/pyqtgraph_examples/__pycache__/lens_example_pyqt.cpython-310.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AMCLab/TemGymBasic/35f2cf0a3f96263bda7576dbd8b68847ee76c4bb/pyqtgraph_examples/__pycache__/lens_example_pyqt.cpython-310.pyc
--------------------------------------------------------------------------------
/pyqtgraph_examples/__pycache__/lens_example_pyqt.cpython-39.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AMCLab/TemGymBasic/35f2cf0a3f96263bda7576dbd8b68847ee76c4bb/pyqtgraph_examples/__pycache__/lens_example_pyqt.cpython-39.pyc
--------------------------------------------------------------------------------
/pyqtgraph_examples/__pycache__/model_sem_example_pyqt.cpython-310.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AMCLab/TemGymBasic/35f2cf0a3f96263bda7576dbd8b68847ee76c4bb/pyqtgraph_examples/__pycache__/model_sem_example_pyqt.cpython-310.pyc
--------------------------------------------------------------------------------
/pyqtgraph_examples/__pycache__/model_sem_example_pyqt.cpython-39.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AMCLab/TemGymBasic/35f2cf0a3f96263bda7576dbd8b68847ee76c4bb/pyqtgraph_examples/__pycache__/model_sem_example_pyqt.cpython-39.pyc
--------------------------------------------------------------------------------
/pyqtgraph_examples/__pycache__/model_tem_example_pyqt.cpython-310.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AMCLab/TemGymBasic/35f2cf0a3f96263bda7576dbd8b68847ee76c4bb/pyqtgraph_examples/__pycache__/model_tem_example_pyqt.cpython-310.pyc
--------------------------------------------------------------------------------
/pyqtgraph_examples/__pycache__/model_tem_example_pyqt.cpython-39.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AMCLab/TemGymBasic/35f2cf0a3f96263bda7576dbd8b68847ee76c4bb/pyqtgraph_examples/__pycache__/model_tem_example_pyqt.cpython-39.pyc
--------------------------------------------------------------------------------
/pyqtgraph_examples/all_components_example_pyqt.py:
--------------------------------------------------------------------------------
1 | from temgymbasic import components as comp
2 | from temgymbasic.model import Model
3 | from temgymbasic.run import run_pyqt
4 | from PyQt5.QtWidgets import QApplication
5 | import sys
6 |
7 | def main():
8 | #Create List of Components
9 | components = [comp.AstigmaticLens(name='Astigmatic Lens', z=1.2),
10 | comp.Lens(name='Lens', z=1.0),
11 | comp.Quadrupole(name='Quadrupole', z=0.9),
12 | comp.Deflector(name='Deflector', z=0.6, defx=0, defy=0),
13 | comp.DoubleDeflector(name='Double Deflector', z_up=0.50, z_low=0.45),
14 | comp.Biprism(name='Biprism', z=0.4),
15 | comp.Aperture(name='Aperture', z=0.1, aperture_radius_inner=0.05)]
16 |
17 | #Generate TEM Model
18 | model_ = Model(components, beam_z=1.5, beam_type='point',
19 | num_rays=32, gun_beam_semi_angle=0.03)
20 |
21 | viewer = run_pyqt(model_)
22 |
23 | return viewer
24 |
25 | if __name__ == '__main__':
26 |
27 | AppWindow = QApplication(sys.argv)
28 | viewer = main()
29 | viewer.show()
30 | AppWindow.exec_()
31 |
--------------------------------------------------------------------------------
/pyqtgraph_examples/beam_tilt_shift_advanced_example_pyqt.py:
--------------------------------------------------------------------------------
1 | from temgymbasic import components as comp
2 | from temgymbasic.model import Model
3 | from temgymbasic.run import run_pyqt
4 | from PyQt5.QtWidgets import QApplication
5 | from temgymbasic.functions import make_test_sample
6 | import sys
7 |
8 | def main():
9 |
10 | sample = make_test_sample()
11 |
12 | #Create List of Components
13 | components = [comp.Lens(name = 'Condenser Lens', z = 1.2, f = -0.1),
14 | comp.DoubleDeflector(name = 'Double Deflector', z_up = 0.9, z_low = 0.8),
15 | comp.Lens(name = 'Objective Lens', z = 0.6, f = -0.1),
16 | comp.Sample(name = 'Sample', sample = sample, z = 0.5),
17 | comp.Lens(name = 'Intermediate Lens', z = 0.4, f = -0.5),
18 | comp.Lens(name = 'Projector Lens', z = 0.1, f = -0.5)]
19 |
20 | #Generate Model
21 | model_ = Model(components, beam_z = 1.5, beam_type = 'point', num_rays = 128, gun_beam_semi_angle = 0.03)
22 |
23 | viewer = run_pyqt(model_)
24 |
25 | return viewer
26 |
27 | if __name__ == '__main__':
28 |
29 | AppWindow = QApplication(sys.argv)
30 | viewer = main()
31 | viewer.show()
32 | AppWindow.exec_()
33 |
--------------------------------------------------------------------------------
/pyqtgraph_examples/beam_tilt_shift_basic_example_pyqt.py:
--------------------------------------------------------------------------------
1 | from temgymbasic import components as comp
2 | from temgymbasic.model import Model
3 | from temgymbasic.run import run_pyqt
4 | from PyQt5.QtWidgets import QApplication
5 | from temgymbasic.functions import make_test_sample
6 | import sys
7 |
8 |
9 | def main():
10 |
11 | sample = make_test_sample()
12 |
13 | #Create List of Components
14 | components = [comp.DoubleDeflector(name = 'Double Deflector', z_up = 0.4, z_low = 0.3),
15 | comp.Sample(name = 'Sample', sample = sample, z = 0.2),
16 | comp.Lens(name = 'Objective Lens', z = 0.1, f = -0.1)]
17 |
18 | #Generate Model
19 | model_ = Model(components, beam_z = 0.6, beam_type = 'paralell', num_rays = 128)
20 |
21 | viewer = run_pyqt(model_)
22 |
23 | return viewer
24 |
25 | if __name__ == '__main__':
26 |
27 | AppWindow = QApplication(sys.argv)
28 | viewer = main()
29 | viewer.show()
30 | AppWindow.exec_()
--------------------------------------------------------------------------------
/pyqtgraph_examples/biprism_example_pyqt.py:
--------------------------------------------------------------------------------
1 |
2 | from temgymbasic import components as comp
3 | from temgymbasic.model import Model
4 | from temgymbasic.run import run_pyqt
5 | from PyQt5.QtWidgets import QApplication
6 | import sys
7 |
8 | def main():
9 | components = [comp.Biprism(name = 'Condenser Biprism', z = 0.7),
10 | comp.Sample(name = 'Sample', z = 0.5, width = 0.2),
11 | comp.Biprism(name = 'Biprism 1', z = 0.4),
12 | comp.Biprism(name = 'Biprism 2', z = 0.2)]
13 |
14 | model_ = Model(components, beam_z = 1.0, beam_type = 'point', num_rays = 4096, gun_beam_semi_angle = 0.1)
15 |
16 | viewer = run_pyqt(model_)
17 |
18 | return viewer
19 |
20 | if __name__ == '__main__':
21 |
22 | AppWindow = QApplication(sys.argv)
23 | viewer = main()
24 | viewer.show()
25 | AppWindow.exec_()
26 |
27 |
--------------------------------------------------------------------------------
/pyqtgraph_examples/condenser_aperture_example_pyqt.py:
--------------------------------------------------------------------------------
1 | from temgymbasic import components as comp
2 | from temgymbasic.model import Model
3 | from temgymbasic.run import run_pyqt
4 | from PyQt5.QtWidgets import QApplication
5 | import sys
6 |
7 | def main():
8 | components = [comp.Aperture(name = 'Condenser Aperture', z = 0.3, x = 0.04, y = 0, aperture_radius_inner = 0.055),
9 | comp.Lens(name = 'Condenser Lens', z = 0.2, f = -0.5)]
10 |
11 | model_ = Model(components, beam_z = 0.8, beam_type = 'point', num_rays = 1024, gun_beam_semi_angle = 0.21)
12 |
13 | viewer = run_pyqt(model_)
14 |
15 | return viewer
16 |
17 | if __name__ == '__main__':
18 |
19 | AppWindow = QApplication(sys.argv)
20 | viewer = main()
21 | viewer.show()
22 | AppWindow.exec_()
--------------------------------------------------------------------------------
/pyqtgraph_examples/condenser_astigmatism_example_pyqt.py:
--------------------------------------------------------------------------------
1 | from temgymbasic import components as comp
2 | from temgymbasic.model import Model
3 | from temgymbasic.run import run_pyqt
4 | from PyQt5.QtWidgets import QApplication
5 | import sys
6 |
7 | def main():
8 | components = [
9 | comp.AstigmaticLens(name='Condenser Lens', z=0.7, fx=-0.4, fy=-0.6),
10 | comp.Quadrupole(name='Condenser Stigmator', z=0.5)
11 | ]
12 |
13 | model_ = Model(components, beam_z = 1.0, beam_type = 'point', num_rays = 32, gun_beam_semi_angle = 0.03)
14 |
15 | viewer = run_pyqt(model_)
16 |
17 | return viewer
18 |
19 | if __name__ == '__main__':
20 |
21 | AppWindow = QApplication(sys.argv)
22 | viewer = main()
23 | viewer.show()
24 | AppWindow.exec_()
25 |
--------------------------------------------------------------------------------
/pyqtgraph_examples/example_exe.py:
--------------------------------------------------------------------------------
1 | from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout, QPushButton
2 | from collections import OrderedDict
3 | import sys
4 |
5 | import beam_tilt_shift_basic_example_pyqt
6 | import beam_tilt_shift_advanced_example_pyqt
7 | import condenser_aperture_example_pyqt
8 | import lens_example_pyqt
9 | import model_sem_example_pyqt
10 | import model_tem_example_pyqt
11 | import condenser_astigmatism_example_pyqt
12 | import biprism_example_pyqt
13 |
14 | # sys.path.insert(1, r"G:\My Drive\Davids Research\LinearTEM\LINEARTEMGYM-master_\LINEARTEMGYM-master\temgym\src")
15 |
16 | examples = OrderedDict([
17 | ('Basic Beam Tilt/Shift', beam_tilt_shift_basic_example_pyqt),
18 | ('Advanced Beam Tilt/Shift', beam_tilt_shift_advanced_example_pyqt),
19 | ('Simple Lens', lens_example_pyqt),
20 | ('Split Biprism', biprism_example_pyqt),
21 | ('Model SEM', model_sem_example_pyqt),
22 | ('Model TEM', model_tem_example_pyqt),
23 | ('Condenser Aperture', condenser_aperture_example_pyqt),
24 | ('Condenser Astigmatism', condenser_astigmatism_example_pyqt)
25 | ])
26 |
27 | #Code to make a .exe that can run scripts in the same folder with the push of a button.
28 | class MainWindow(QWidget):
29 | def __init__(self):
30 | super().__init__()
31 |
32 | self.viewer = None
33 |
34 | #make the GUI layout
35 | self.layout = QVBoxLayout(self)
36 | self.createbuttons()
37 |
38 | def createbuttons(self):
39 | #loop through list of example items, and connect them to a button.
40 | for idx, (key, val) in enumerate(examples.items()):
41 | button = QPushButton(key, self)
42 | button.clicked.connect(lambda ch, val=val: self.runfile(val))
43 | self.layout.addWidget(button)
44 |
45 | def runfile(self, file):
46 | if self.viewer is None:
47 | self.viewer = file.main()
48 | self.viewer.show()
49 | else:
50 | self.viewer.close() # Close windoviewer.
51 | self.viewer = None # Discard reference.
52 |
53 |
54 | if __name__ == "__main__":
55 | mainapp = MainWindow()
56 | mainapp.show()
57 |
58 |
59 |
60 |
61 |
--------------------------------------------------------------------------------
/pyqtgraph_examples/lens_example_pyqt.py:
--------------------------------------------------------------------------------
1 |
2 | from temgymbasic import components as comp
3 | from temgymbasic.model import Model
4 | from temgymbasic.run import run_pyqt
5 | from PyQt5.QtWidgets import QApplication
6 | import os
7 | import sys
8 |
9 | def main():
10 | components = [comp.Lens(name = 'Lens', z = 0.5, f = -0.5)]
11 |
12 | model_ = Model(components, beam_z = 1.0, beam_type = 'point', num_rays = 32, gun_beam_semi_angle = 0.15)
13 |
14 | viewer = run_pyqt(model_)
15 |
16 | return viewer
17 |
18 | if __name__ == '__main__':
19 | AppWindow = QApplication(sys.argv)
20 | viewer = main()
21 | viewer.show()
22 | AppWindow.exec_()
--------------------------------------------------------------------------------
/pyqtgraph_examples/make_exe.bat.bat:
--------------------------------------------------------------------------------
1 | pyinstaller --onefile lens_example_pyqt.py &
2 | pyinstaller --onefile beam_tilt_shift_advanced_example_pyqt.py &
3 | pyinstaller --onefile beam_tilt_shift_basic_example_pyqt.py &
4 | pyinstaller --onefile biprism_example_pyqt.py &
5 | pyinstaller --onefile condenser_aperture_example_pyqt.py &
6 | pyinstaller --onefile condenser_astigmatism_example_pyqt.py &
7 | pyinstaller --onefile model_sem_example_pyqt.py &
8 | pyinstaller --onefile model_tem_example_pyqt.py &
9 | pyinstaller --onefile all_components_example_pyqt.py
--------------------------------------------------------------------------------
/pyqtgraph_examples/model_sem_example_pyqt.py:
--------------------------------------------------------------------------------
1 | from temgymbasic import components as comp
2 | from temgymbasic.model import Model
3 | from temgymbasic.run import run_pyqt
4 | from PyQt5.QtWidgets import QApplication
5 | import sys
6 |
7 | def main():
8 | components = [comp.Lens(name = '1st Condenser Lens', z = 1.5, f = -0.1),
9 | comp.Aperture(name = 'Spray Aperture', z = 1.2, aperture_radius_inner = 0.05),
10 | comp.Lens(name = '2nd Condenser Lens', z = 1.0, f = -0.15),
11 | comp.DoubleDeflector(name = 'Deflection Coils', z_up = 0.8, z_low = 0.7),
12 | comp.Lens(name = 'Objective Lens', z = 0.5, f = -0.25),
13 | comp.Aperture(name = 'Objective Aperture', z = 0.4, aperture_radius_inner = 0.05),
14 | comp.Sample(name = 'Sample', z = 0.1)
15 | ]
16 |
17 | model_ = Model(components, beam_z = 1.7, beam_type = 'point',
18 | num_rays = 256, gun_beam_semi_angle = 0.15)
19 |
20 | viewer = run_pyqt(model_)
21 |
22 | return viewer
23 |
24 | if __name__ == '__main__':
25 |
26 | AppWindow = QApplication(sys.argv)
27 |
28 | viewer = main()
29 | viewer.show()
30 | AppWindow.exec_()
--------------------------------------------------------------------------------
/pyqtgraph_examples/model_tem_example_pyqt.py:
--------------------------------------------------------------------------------
1 | import sys
2 | from PyQt5.QtWidgets import QApplication
3 | from temgymbasic.run import run_pyqt
4 | from temgymbasic.model import Model
5 | from temgymbasic import components as comp
6 | def main():
7 | components = [comp.Lens(name='Electrostatic Lens', z=3, f=-0.2),
8 | comp.DoubleDeflector(name='Gun Beam Deflectors',
9 | z_up=2.8, z_low=2.7),
10 | comp.Lens(name='1st Condenser Lens', z=2.6, f=-0.1),
11 | comp.Lens(name='2nd Condenser Lens', z=2.5, f=-0.1),
12 | comp.Aperture(name='Condenser Aperture', z=2.3,
13 | aperture_radius_inner=0.05),
14 | comp.Quadrupole(name='Condenser Stig', z=2.2),
15 | comp.DoubleDeflector(
16 | name='Condenser Deflectors', z_up=2.1, z_low=2.0),
17 | comp.Lens(name='Condenser Mini Lens', z=1.8, f=-0.2),
18 | comp.Aperture(name='Objective Aperture', z=1.7,
19 | aperture_radius_inner=0.1),
20 | comp.Lens(name='Objective Lens', z=1.5, f=-0.2),
21 | comp.Quadrupole(name='Objective Stig', z=1.4),
22 | comp.Lens(name='Objective Mini Lens', z=1.3, f=-0.2),
23 | comp.DoubleDeflector(name='Image Shifts', z_up=1.1, z_low=1.0),
24 | comp.Aperture(name='Selected Area Aperture',
25 | z=0.9, aperture_radius_inner=0.1),
26 | comp.Quadrupole(name='Intermediate Lens Stigmator', z=0.8),
27 | comp.Lens(name='1st Intermediate Lens', z=0.7, f=-0.1),
28 | comp.Lens(name='2nd Intermediate Lens', z=0.6, f=-0.1),
29 | comp.Lens(name='3rd Intermediate Lens', z=0.5, f=-0.1),
30 | comp.DoubleDeflector(
31 | name='Projector Lens Deflectors', z_up=0.4, z_low=0.3),
32 | comp.Lens(name='Projector Lens', z=0.2, f=-0.1)
33 | ]
34 |
35 | model_ = Model(components, beam_z=3.5, beam_type='point',
36 | num_rays=256, gun_beam_semi_angle=0.25)
37 |
38 | viewer = run_pyqt(model_)
39 |
40 | return viewer
41 |
42 | if __name__ == '__main__':
43 |
44 | AppWindow = QApplication(sys.argv)
45 | viewer = main()
46 | viewer.show()
47 | AppWindow.exec_()
48 |
--------------------------------------------------------------------------------
/requirements-doc.txt:
--------------------------------------------------------------------------------
1 | sphinx==4.3.2
2 | sphinx_licenseinfo
3 | pyqtgraph==0.13.1
4 | pyopengl==3.1.6
5 | numpy==1.23.3
6 | triangle
7 | PyQt5==5.15.2
8 | matplotlib==3.6.1
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | numpy
2 | pyqt5
3 | pyopengl
4 | triangle
5 | matplotlib
6 | pyqtgraph
--------------------------------------------------------------------------------
/setup.cfg:
--------------------------------------------------------------------------------
1 | [metadata]
2 | license_files = LICENSE.md
3 |
4 | [flake8]
5 | max-line-length = 100
6 | ignore = E24,E121,E123,E126,E128,E133,E226,E241,E242,E704,W503
7 | exclude = .git,__pycache__,.tox,build,dist,node_modules,TOXENV
8 |
9 | [coverage:run]
10 | branch = True
11 | include = src/
12 |
13 | [coverage:report]
14 | # Regexes for lines to exclude from consideration
15 | exclude_lines =
16 | # Have to re-enable the standard pragma
17 | pragma: no cover
18 |
19 | # Don't complain about missing debug-only code:
20 | def __repr__
21 | if self\.debug
22 |
23 | # Don't complain about typing branches:
24 | if TYPE_CHECKING
25 | if typing.TYPE_CHECKING
26 |
27 | # Don't complain if tests don't hit defensive assertion code:
28 | raise AssertionError
29 | raise NotImplementedError
30 |
31 | # Don't complain if non-runnable code isn't run:
32 | if 0:
33 | if False:
34 | if __name__ == .__main__.:
35 |
--------------------------------------------------------------------------------
/src/temgymbasic/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AMCLab/TemGymBasic/35f2cf0a3f96263bda7576dbd8b68847ee76c4bb/src/temgymbasic/__init__.py
--------------------------------------------------------------------------------
/src/temgymbasic/functions.py:
--------------------------------------------------------------------------------
1 |
2 | import numpy as np
3 |
4 |
5 | def make_test_sample(size=256):
6 | # Code From Dieter Weber
7 | obj = np.ones((size, size), dtype=np.complex64)
8 | y, x = np.ogrid[-size//2:size//2, -size//2:size//2]
9 |
10 | outline = (
11 | ((y*1.2)**2 + x**2) > (110/256*size)**2
12 | ) & (
13 | (((y*1.2)**2 + x**2) < (120/256*size)**2)
14 | )
15 | obj[outline] = 0.0
16 |
17 | left_eye = ((y + 40/256*size)**2 + (x + 40/256*size)**2) < (20/256*size)**2
18 | obj[left_eye] = 0
19 | right_eye = (np.abs(y + 40/256*size) < 15/256*size) & (np.abs(x - 40/256*size) < 30/256*size)
20 | obj[right_eye] = 0
21 |
22 | nose = (y + 20/256*size + x > 0) & (x < 0) & (y < 10/256*size)
23 |
24 | obj[nose] = (0.05j * x + 0.05j * y)[nose]
25 |
26 | mouth = (
27 | ((y*1)**2 + x**2) > (50/256*size)**2
28 | ) & (
29 | (((y*1)**2 + x**2) < (70/256*size)**2)
30 | ) & (
31 | y > 20/256*size
32 | )
33 |
34 | obj[mouth] = 0
35 |
36 | tongue = (
37 | ((y - 50/256*size)**2 + (x - 50/256*size)**2) < (20/256*size)**2
38 | ) & (
39 | (y**2 + x**2) > (70/256*size)**2
40 | )
41 | obj[tongue] = 0
42 |
43 | # This wave modulation introduces a strong signature in the diffraction pattern
44 | # that allows to confirm the correct scale and orientation.
45 | signature_wave = np.exp(1j*(3 * y + 7 * x) * 2*np.pi/size)
46 |
47 | obj += 0.3*signature_wave - 0.3
48 |
49 | return np.abs(obj)
50 |
51 |
52 | # FIXME resolve code duplication between circular_beam() and point_beam()
53 | def circular_beam(r, outer_radius):
54 | '''Generates a circular paralell initial beam
55 |
56 | Parameters
57 | ----------
58 | r : ndarray
59 | Ray position and slope matrix
60 | outer_radius : float
61 | Outer radius of the circular beam
62 |
63 | Returns
64 | -------
65 | r : ndarray
66 | Updated ray position & slope matrix which create a circular beam
67 | num_points_kth_ring: ndarray
68 | Array of the number of points on each ring of our circular beam
69 | '''
70 | num_rays = r.shape[2]
71 |
72 | # Use the equation from stack overflow about ukrainian graves from 2014
73 | # to calculate the number of even rings including decimal remainder
74 | num_circles_dec = (-1+np.sqrt(1+4*(num_rays)/(np.pi)))/2
75 |
76 | # Get the number of integer rings
77 | num_circles_int = int(np.floor(num_circles_dec))
78 |
79 | # Calculate the number of points per ring with the integer amoung of rings
80 | num_points_kth_ring = np.round(
81 | 2*np.pi*(np.arange(0, num_circles_int+1))).astype(np.int32)
82 |
83 | # get the remainding amount of rays
84 | remainder_rays = num_rays - np.sum(num_points_kth_ring)
85 |
86 | # Get the proportion of points in each rung
87 | proportion = num_points_kth_ring/np.sum(num_points_kth_ring)
88 |
89 | # resolve this proportion to an integer value, and reverse it
90 | num_rays_to_each_ring = np.ceil(proportion*remainder_rays)[::-1].astype(np.int32)
91 |
92 | # We need to decide on where to stop adding the remainder of rays to the
93 | # rest of the rings. We find this point by summing the rays in each ring
94 | # from outside to inside, and then getting the index where it is greater
95 | # than or equal to the remainder
96 | index_to_stop_adding_rays = np.where(
97 | np.cumsum(num_rays_to_each_ring) >= remainder_rays)[0][0]
98 |
99 | # We then get the total number of rays to add
100 | rays_to_add = np.cumsum(num_rays_to_each_ring)[
101 | index_to_stop_adding_rays].astype(np.int32)
102 |
103 | # The number of rays to add isn't always matching the remainder, so we
104 | # collect them here with this line
105 | final_sub = rays_to_add - remainder_rays
106 |
107 | # Here we take them away so we get the number of rays we want
108 | num_rays_to_each_ring[index_to_stop_adding_rays] -= final_sub
109 |
110 | # Then we add all of these rays to the correct ring
111 | num_points_kth_ring[::-1][:index_to_stop_adding_rays+1] += num_rays_to_each_ring[
112 | :index_to_stop_adding_rays+1
113 | ]
114 |
115 | # Add one point for the centre, and take one away from the end
116 | num_points_kth_ring[0] = 1
117 | num_points_kth_ring[-1] = num_points_kth_ring[-1] - 1
118 |
119 | # Make get the radii for the number of circles of rays we need
120 | radii = np.linspace(0, outer_radius, num_circles_int+1)
121 |
122 | # fill in the x and y coordinates to our ray array
123 | idx = 0
124 | for i in range(len(radii)):
125 | for j in range(num_points_kth_ring[i]):
126 | radius = radii[i]
127 | t = j*(2 * np.pi / num_points_kth_ring[i])
128 | r[0, 0, idx] = radius*np.cos(t)
129 | r[0, 2, idx] = radius*np.sin(t)
130 | idx += 1
131 |
132 | return r, num_points_kth_ring
133 |
134 |
135 | def point_beam(r, gun_beam_semi_angle):
136 | '''Generates a point initial beam that spreads out with semi angle 'gun_beam_semi_angle'
137 |
138 | Parameters
139 | ----------
140 | r : ndarray
141 | Ray position and slope matrix
142 | gun_beam_semi_angle : float
143 | Beam semi angle in radians
144 |
145 | Returns
146 | -------
147 | r : ndarray
148 | Updated ray position & slope matrix which create a circular beam
149 | num_points_kth_ring: ndarray
150 | Array of the number of points on each ring of our circular beam
151 | '''
152 | num_rays = r.shape[2]
153 |
154 | # Use the equation from stack overflow about ukrainian graves
155 | # to calculate the number of even rings including decimal remainder
156 | num_circles_dec = (-1+np.sqrt(1+4*(num_rays)/(np.pi)))/2
157 |
158 | # Get the number of integer rings
159 | num_circles_int = int(np.floor(num_circles_dec))
160 |
161 | # Calculate the number of points per ring with the integer amoung of rings
162 | num_points_kth_ring = np.round(
163 | 2*np.pi*(np.arange(0, num_circles_int+1))).astype(np.int32)
164 |
165 | # get the remainding amount of rays
166 | remainder_rays = num_rays - np.sum(num_points_kth_ring)
167 |
168 | # Get the proportion of points in each rung
169 | proportion = num_points_kth_ring/np.sum(num_points_kth_ring)
170 |
171 | # resolve this proportion to an integer value, and reverse it
172 | num_rays_to_each_ring = np.ceil(proportion*remainder_rays)[::-1].astype(np.int32)
173 |
174 | # We need to decide on where to stop adding the remainder of rays to the
175 | # rest of the rings. We find this point by summing the rays in each ring
176 | # from outside to inside, and then getting the index where it is greater
177 | # than or equal to the remainder
178 | index_to_stop_adding_rays = np.where(
179 | np.cumsum(num_rays_to_each_ring) >= remainder_rays)[0][0]
180 |
181 | # We then get the total number of rays to add
182 | rays_to_add = np.cumsum(num_rays_to_each_ring)[
183 | index_to_stop_adding_rays].astype(np.int32)
184 |
185 | # The number of rays to add isn't always matching the remainder, so we
186 | # collect them here with this line
187 | final_sub = rays_to_add - remainder_rays
188 |
189 | # Here we take them away so we get the number of rays we want
190 | num_rays_to_each_ring[index_to_stop_adding_rays] -= final_sub
191 |
192 | # Then we add all of these rays to the correct ring
193 | num_points_kth_ring[::-1][:index_to_stop_adding_rays+1] += num_rays_to_each_ring[
194 | :index_to_stop_adding_rays+1
195 | ]
196 |
197 | # Add one point for the centre, and take one away from the end
198 | num_points_kth_ring[0] = 1
199 | num_points_kth_ring[-1] = num_points_kth_ring[-1] - 1
200 |
201 | # Make get the radii for the number of circles of rays we need
202 | radii = np.linspace(0, 1, num_circles_int+1)
203 |
204 | # fill in the x and y coordinates to our ray array
205 | idx = 0
206 | for i in range(len(radii)):
207 | for j in range(num_points_kth_ring[i]):
208 | radius = radii[i]
209 | t = j*(2 * np.pi / num_points_kth_ring[i])
210 | r[0, 1, idx] = np.tan(gun_beam_semi_angle*radius)*np.cos(t)
211 | r[0, 3, idx] = np.tan(gun_beam_semi_angle*radius)*np.sin(t)
212 | idx += 1
213 |
214 | return r, num_points_kth_ring
215 |
216 |
217 | def axial_point_beam(r, gun_beam_semi_angle):
218 | '''Generates a cross shaped initial beam on the x and y axis
219 | that spreads out with semi angle 'gun_beam_semi_angle'
220 |
221 | Parameters
222 | ----------
223 | r : ndarray
224 | Ray position and slope matrix
225 | gun_beam_semi_angle : float
226 | Beam semi angle in radians
227 |
228 | Returns
229 | -------
230 | r : ndarray
231 | Updated ray position & slope matrix which create a circular beam
232 | '''
233 | num_rays = r.shape[2]
234 |
235 | x_rays = int(round(num_rays/2))
236 | x_angles = np.linspace(-gun_beam_semi_angle, gun_beam_semi_angle, x_rays, endpoint=True)
237 | y_rays = num_rays-x_rays
238 | y_angles = np.linspace(-gun_beam_semi_angle, gun_beam_semi_angle, y_rays, endpoint=True)
239 |
240 | for idx, angle in enumerate(x_angles):
241 | r[0, 1, idx] = np.tan(angle)
242 |
243 | for idx, angle in enumerate(y_angles):
244 | i = idx + y_rays
245 | r[0, 3, i] = np.tan(angle)
246 |
247 | return r
248 |
249 |
250 | def x_axial_point_beam(r, gun_beam_semi_angle):
251 | '''Generates a cross shaped initial beam on the x axis
252 | that spreads out with semi angle 'gun_beam_semi_angle'
253 |
254 | Parameters
255 | ----------
256 | r : ndarray
257 | Ray position and slope matrix
258 | gun_beam_semi_angle : float
259 | Beam semi angle in radians
260 |
261 | Returns
262 | -------
263 | r : ndarray
264 | Updated ray position & slope matrix which create a circular beam
265 | '''
266 | num_rays = r.shape[2]
267 |
268 | x_rays = int(round(num_rays))
269 | x_angles = np.linspace(-gun_beam_semi_angle, gun_beam_semi_angle, x_rays, endpoint=True)
270 |
271 | for idx, angle in enumerate(x_angles):
272 | r[0, 1, idx] = np.tan(angle)
273 |
274 | return r
275 |
276 |
277 | def _flip_y():
278 | # From libertem.corrections.coordinates v0.11.1
279 | return np.array([
280 | (-1, 0),
281 | (0, 1)
282 | ])
283 |
284 |
285 | def _identity():
286 | # From libertem.corrections.coordinates v0.11.1
287 | return np.eye(2)
288 |
289 |
290 | def _rotate(radians):
291 | # From libertem.corrections.coordinates v0.11.1
292 | # https://en.wikipedia.org/wiki/Rotation_matrix
293 | # y, x instead of x, y
294 | return np.array([
295 | (np.cos(radians), np.sin(radians)),
296 | (-np.sin(radians), np.cos(radians))
297 | ])
298 |
299 |
300 | def _rotate_deg(degrees):
301 | # From libertem.corrections.coordinates v0.11.1
302 | return _rotate(np.pi/180*degrees)
303 |
304 |
305 | def get_pixel_coords(rays_x, rays_y, size, pixels, flip_y=False, scan_rotation=0.):
306 | if flip_y:
307 | transform = _flip_y()
308 | else:
309 | transform = _identity()
310 |
311 | # Transformations are applied right to left
312 | transform = _rotate_deg(scan_rotation) @ transform
313 |
314 | y_transformed, x_transformed = (np.array((rays_y, rays_x)).T @ transform).T
315 |
316 | pixel_coords_x = x_transformed / size * pixels + pixels/2 - 1
317 | pixel_coords_y = y_transformed / size * pixels + pixels/2 - 1
318 |
319 | return (pixel_coords_x, pixel_coords_y)
320 |
321 |
322 | def get_image_from_rays(
323 | rays_x, rays_y, sample_rays_x, sample_rays_y, detector_size,
324 | detector_pixels, sample_size, sample_pixels, sample_image,
325 | flip_y=True):
326 | '''From an image of rays that hit the detector at the base of the TEM
327 |
328 | Parameters
329 | ----------
330 | rays_x : ndarray
331 | X position of rays that hit the detector
332 | rays_y : ndarray
333 | X position of rays that hit the detector
334 | sample_rays_x : ndarray
335 | X position of rays that hit the sample
336 | sample_rays_y : ndarray
337 | Y position of rays that hit the sample
338 | detector_size : float
339 | Real size of the detector in the model. Single value that describes it's edge length.
340 | Note that the detector is always square
341 | detector_pixels : int
342 | Pixel resolution of the detector
343 | sample_size : float
344 | Real size of the sample in the model. Single value that describes it's edge length.
345 | Note that the sample is always square
346 | sample_pixels : int
347 | Pixel resolution of the the sample
348 | sample_image : ndarray
349 | image intensities of the sample. Used to form an image on the detector
350 | Returns
351 | -------
352 | detector_ray_image : ndarray
353 | Ray image of where rays have hit the detector
354 | detector_sample_image : ndarray
355 | Sample image obtained by transferring ray which have hit the detector
356 | sample_pixel_coords : ndarray
357 | Coordinates of where each ray has hit the sample
358 | detector_pixel_coords : ndarray
359 | Coordinates of where each ray has hit the detector
360 | '''
361 | detector_ray_image = np.zeros((detector_pixels, detector_pixels), dtype=np.uint8)
362 | detector_sample_image = np.zeros((detector_pixels, detector_pixels))
363 |
364 | # Convert rays from sample positions to pixel positions
365 | sample_pixel_coords_x, sample_pixel_coords_y = np.round(get_pixel_coords(
366 | rays_x=sample_rays_x,
367 | rays_y=sample_rays_y,
368 | size=sample_size,
369 | pixels=sample_pixels,
370 | flip_y=flip_y
371 | )).astype(np.int32)
372 |
373 | sample_pixel_coords = np.vstack([sample_pixel_coords_x, sample_pixel_coords_y]).T
374 |
375 | # Convert rays from detector positions to pixel positions
376 | detector_pixel_coords_x, detector_pixel_coords_y = np.round(get_pixel_coords(
377 | rays_x=rays_x,
378 | rays_y=rays_y,
379 | size=detector_size,
380 | pixels=detector_pixels,
381 | flip_y=flip_y
382 | )).astype(np.int32)
383 |
384 | detector_pixel_coords = np.vstack([detector_pixel_coords_x, detector_pixel_coords_y]).T
385 | sample_rays_inside = np.all(
386 | (sample_pixel_coords > 0) & (sample_pixel_coords < sample_pixels), axis=1
387 | ).T
388 | detector_rays_inside = np.all(
389 | (detector_pixel_coords > 0) & (detector_pixel_coords < detector_pixels), axis=1
390 | ).T
391 | rays_that_hit_sample_and_detector = (sample_rays_inside & detector_rays_inside)
392 | rays_that_hit_detector_but_not_sample = (~sample_rays_inside & detector_rays_inside)
393 |
394 | sample_pixel_intensities = sample_image[
395 | sample_pixel_coords[rays_that_hit_sample_and_detector, 1],
396 | sample_pixel_coords[rays_that_hit_sample_and_detector, 0]
397 | ]
398 |
399 | # Return this image for the case when we want to just plot the beam on the detector
400 | detector_ray_image[
401 | detector_pixel_coords[rays_that_hit_detector_but_not_sample, 1],
402 | detector_pixel_coords[rays_that_hit_detector_but_not_sample, 0],
403 | ] += 1
404 |
405 | # Obtain sample image intensitions
406 | detector_sample_image[
407 | detector_pixel_coords[rays_that_hit_sample_and_detector, 1],
408 | detector_pixel_coords[rays_that_hit_sample_and_detector, 0]
409 | ] = sample_pixel_intensities
410 | detector_sample_image[
411 | detector_pixel_coords[rays_that_hit_detector_but_not_sample, 1],
412 | detector_pixel_coords[rays_that_hit_detector_but_not_sample, 0]
413 | ] = 0
414 |
415 | return detector_ray_image, detector_sample_image, sample_pixel_coords, detector_pixel_coords
416 |
417 |
418 | def convert_rays_to_line_vertices(model):
419 | '''Converts a ray position matrix of size [(steps, 5, num rays)] -
420 | (where steps is defined by the number of components + 2 - the two being
421 | included to add the gun and detector, which are not components chosen by the user)'
422 | to a line matrix of shape [(steps)*2-2)*num rays, 3], which is of the correct shape
423 | to be readily plot ray positions in the column as lines.
424 |
425 | Parameters
426 | ----------
427 | model : class
428 | Microscope model that stores all associated ray position data
429 | gun_beam_semi_angle : float
430 | Beam semi angle in radians
431 |
432 | Returns
433 | -------
434 | r : ndarray
435 | Updated ray position & slope matrix which create a circular beam
436 | '''
437 | ray_z = np.tile(model.z_positions, [model.num_rays, 1, 1]).T
438 |
439 | # Stack with the z coordinates
440 | ray_xyz = np.hstack((model.r[:, [0, 2], :], ray_z))
441 |
442 | # Repeat vertices so we can create lines. The shape of this array is [Num Steps*2, 3, Num Rays]
443 | lines_repeated = np.repeat(ray_xyz[:, :, :], repeats=2, axis=0)[1:-1]
444 |
445 | # Index the total number of rays in the model initially which are by default
446 | # allowed through all components
447 | allowed_rays = range(model.num_rays)
448 |
449 | for component in model.components:
450 | if len(component.blocked_ray_idcs) != 0:
451 | # Find the difference between blocked rays and original amount of allowed rays
452 | allowed_rays = list(set(allowed_rays).difference(set(component.blocked_ray_idcs)))
453 | # Convert from ray position indexing, to line indexing
454 | idx = component.index*2+2
455 | # Get the coordinates of all rays which hit the aperture.
456 | pts_blocked = lines_repeated[idx, :, component.blocked_ray_idcs]
457 | # Do really funky array manipulation to create a copy of all of
458 | # these points that is the same shape as the vertices of remaining
459 | # lines after the aperture (I'm sorry to myself and anyone in the
460 | # future who has to read this)
461 | lines_aperture = np.broadcast_to(
462 | pts_blocked[..., None], pts_blocked.shape+(lines_repeated.shape[0]-(idx),)
463 | ).transpose(2, 1, 0)
464 |
465 | # Copy the coordinate of all rays that hit the aperture, to all line
466 | # vertices after this, so we don't visualise them.
467 | lines_repeated[idx:, :, component.blocked_ray_idcs] = lines_aperture
468 |
469 | # Then restack each line so that we end up with a long list of lines, from
470 | # [Num Steps*2, 3, Num Rays] > [(Num Steps*2-2)*Num rays, 3]
471 | # see > https://stackoverflow.com/questions/38509196/efficiently-re-stacking-a-numpy-ndarray
472 | lines_paired = lines_repeated.transpose(2, 0, 1).reshape(
473 | lines_repeated.shape[0]*model.num_rays, 3)
474 |
475 | return lines_paired, allowed_rays
476 |
--------------------------------------------------------------------------------
/src/temgymbasic/model.py:
--------------------------------------------------------------------------------
1 |
2 | import numpy as np
3 | from temgymbasic.functions import circular_beam, point_beam, axial_point_beam, x_axial_point_beam
4 | from temgymbasic.gui import ModelGui, ExperimentGui
5 |
6 | '''This class create the model composed of the specified components, and handles all of the computation
7 | that transmits the rays through each component.'''
8 |
9 | class Model():
10 | '''Generates a model electron microscope. This class generates performs the matrix
11 | multiplication and function updates to calculate their positions throughout
12 | the column.
13 | '''
14 | def __init__(self, components, beam_z=1, num_rays=16, beam_type='point',
15 | gun_beam_semi_angle=0, beam_tilt_x=0, beam_tilt_y=0, beam_radius = 0.125,
16 | detector_size = 0.5, detector_pixels = 128, experiment = None):
17 | '''
18 | Parameters
19 | ----------
20 | components : list
21 | List of components to electron microscope component to input into the model
22 | beam_z : int, optional
23 | Sets the initial height of the beam, by default 1
24 | num_rays : int, optional
25 | Sets the number of rays generated at the beam_z position. A large
26 | number of rays will in general slow down the programme quite a lot. , by default 256
27 | beam_type : str, optional
28 | Choose the type of beam:
29 | -'point' beam creates a set of rays that start from a single point and
30 | spread out like a cone.
31 | -'paralell' beam creates a set of rays that start from the same position, but
32 | each have the same angle.
33 | - 'axial' creates a beam which is only visible on the x and y axis.
34 | - 'x_axial' creates a beam which is only visible on the x-axis. This is only used
35 | for matplotlib diagrams, by default 'point'
36 | gun_beam_semi_angle : float, optional
37 | Set the semi angle of the beam in radians., by default np.pi/4
38 | beam_tilt_x : int, optional
39 | Set the tilt of the beam in the x direction, by default 0
40 | beam_tilt_y : int, optional
41 | Set the tilt of the beam in the y direction, by default 0
42 | beam_radius : float, optional
43 | Set the width of the beam - only matters if "paralell" beam type is selected
44 | detector_size : float, optional
45 | Set the size of the detector, by default 0.5
46 | detector_pixels : int, optional
47 | Set the number of pixels in the detector. A large number of pixels will
48 | probably considerably hinder performance, by default 128
49 | experiment : str or None, optional
50 | Choose a specific experiment type:
51 | - None leaves the default behaviour of TEMGYMBasic and does not enforce a certain order of components
52 | in the model.
53 | - '4DSTEM' sets up the conditions for a basic 4DSTEM experiment with an overfocused beam
54 | projecting an image of the sample at each scan position.
55 |
56 | '''
57 | self.components = components
58 | self.num_rays = num_rays
59 |
60 | self.beam_radius = beam_radius
61 |
62 | #store an initial variable also to scale the GUI Slider
63 | self.beam_radius_init = beam_radius
64 |
65 | self.beam_z = beam_z
66 | self.beam_type = beam_type
67 | self.gun_beam_semi_angle = gun_beam_semi_angle
68 |
69 | self.beam_tilt_x = beam_tilt_x
70 | self.beam_tilt_y = beam_tilt_y
71 | self.experiment = experiment
72 |
73 | if self.experiment == '4DSTEM':
74 |
75 | if self.components[0].type != 'Double Deflector':
76 | assert('Warning: First component in list is not a double deflector. If 4DSTEM experiment is chosen, \
77 | the first component in the list must be a pair of scan coils - i.e a double deflector')
78 | if len(self.components) != 4:
79 | assert('Warning: There must be four components for now in a model 4DSTEM experiment: Scan coils, lens, sample and \
80 | descan coils.')
81 |
82 | if self.components[0].type == 'Double Deflector':
83 |
84 | self.scan_coils = self.components[0]
85 | self.obj_lens = self.components[1]
86 | self.sample = self.components[2]
87 | self.descan_coils = self.components[3]
88 |
89 | self.overfocus = 0.1
90 | self.semiconv = 0.01
91 | self.set_beam_radius_from_semiconv(self.semiconv)
92 | self.set_obj_lens_f_from_overfocus(self.overfocus)
93 |
94 | self.scan_pixel_x = 0
95 | self.scan_pixel_y = 0
96 | self.scan_pixels = 128
97 |
98 | #Need a special function for creating the z_positions of each component because and
99 | #double deflector is composed of two components, so we need to account for that.
100 | self.set_z_positions()
101 |
102 | self.z_distances = np.diff(self.z_positions)
103 |
104 | #Make the matrix of rays that depends on the beam conditions input into the model.
105 | self.generate_rays()
106 | self.update_component_matrix()
107 | self.allowed_ray_idcs = np.arange(self.num_rays)
108 |
109 | self.detector_size = detector_size
110 | self.detector_pixels = detector_pixels
111 |
112 |
113 | def set_z_positions(self):
114 | '''Create the z position list of all components in the model
115 | '''
116 | self.z_positions = []
117 |
118 | #Input the initial beam_z as the first z_position
119 | self.z_positions.append(self.beam_z)
120 |
121 | #We need to loop through all components and where there is a double deflector,
122 | #we need to add an extra z_position to the matrix
123 | double_deflectors = 0
124 | for idx, component in enumerate(self.components):
125 | if component.type == 'Double Deflector':
126 | self.z_positions.append(component.z_up)
127 | component.index = idx
128 | self.z_positions.append(component.z_low)
129 | component.index = idx + 1
130 | double_deflectors += 1
131 | else:
132 | self.z_positions.append(component.z)
133 | component.index = idx + double_deflectors
134 |
135 | if component.type == 'Sample':
136 | self.sample_r_idx = idx + double_deflectors + 1
137 | self.sample_idx = idx
138 |
139 | #Add the position of the detector
140 | self.z_positions.append(0)
141 |
142 | def set_obj_lens_f_from_overfocus(self, overfocus):
143 | if overfocus <= 0:
144 | assert('For now, we only allow positive overfocus values (crossover above sample).')
145 |
146 | self.overfocus = overfocus
147 |
148 | #Lens f is always a negative number, and overfocus for now is always a positive number
149 | self.obj_lens.f = -1*(self.obj_lens.z-self.sample.z-self.overfocus)
150 | self.obj_lens.set_matrix()
151 |
152 | def set_beam_radius_from_semiconv(self, semiconv):
153 |
154 | self.semiconv = semiconv
155 | self.beam_radius = abs(self.obj_lens.f)*np.tan(self.semiconv)
156 |
157 | #This is used for the GUI Slider
158 | self.beam_radius_init = abs(self.obj_lens.f)*np.tan(self.semiconv)
159 |
160 | #Create the ModelGUI if we are running pyqtgraph
161 | def create_gui(self):
162 | '''Create the GUI
163 | '''
164 | self.gui = ModelGui(self.num_rays, self.beam_type,
165 | self.gun_beam_semi_angle, self.beam_tilt_x, self.beam_tilt_y, self.beam_radius)
166 |
167 | if self.experiment == '4DSTEM':
168 | self.experiment_gui = ExperimentGui()
169 | self.scan_pixels = self.experiment_gui.scanpixelsslider.value()
170 |
171 | def generate_rays(self):
172 | '''Generate electron rays
173 | '''
174 | #Make our 3D matrix of rays. This matrix is of shape (steps, 5, num rays), where
175 | #steps is defined by the number of components.
176 |
177 | self.steps = len(self.z_positions)
178 |
179 | self.r = np.zeros((self.steps, 5, self.num_rays),
180 | dtype=np.float64) # x, theta_x, y, theta_y, 1
181 |
182 | self.r[:, 4, :] = np.ones(self.num_rays)
183 |
184 | if self.beam_type == 'paralell':
185 | self.r, self.spot_indices = circular_beam(self.r, self.beam_radius)
186 | elif self.beam_type == 'point':
187 | self.r, self.spot_indices = point_beam(self.r, self.gun_beam_semi_angle)
188 | elif self.beam_type == 'axial':
189 | self.r = axial_point_beam(self.r, self.gun_beam_semi_angle)
190 | elif self.beam_type == 'x_axial':
191 | self.r = x_axial_point_beam(self.r, self.gun_beam_semi_angle)
192 |
193 | self.r[:, 1, :] += self.beam_tilt_x
194 | self.r[:, 3, :] += self.beam_tilt_y
195 |
196 | #Add the matrices of each component to a list
197 | def update_component_matrix(self):
198 | '''Update the list of all component matrices, each matrix of which has
199 | been set by the component upon it's creation.
200 | '''
201 | self.components_matrix = []
202 | for idx, component in enumerate(self.components):
203 | if component.type == 'Double Deflector':
204 | self.components_matrix.append(component.up_matrix)
205 | self.components_matrix.append(component.low_matrix)
206 | else:
207 | self.components_matrix.append(component.matrix)
208 |
209 | #Perform the matrix multiplication of the rays with each component in the model
210 | def update_rays_stepwise(self):
211 | '''Perform the neccessary matrix multiplications and function multiplications
212 | to propagate the beam through the column
213 | '''
214 | #Do the matrix multiplication of the first rays with the distance between the initial beam z
215 | #and the first component
216 | self.r[1, :, :] = np.matmul(self.propagate(self.z_distances[0]), self.r[0, :, :])
217 |
218 | idx = 1
219 |
220 | #For every component, loop through it and perform the matrix multiplication
221 | for component in self.components:
222 | if component.type == 'Biprism':
223 | x = abs(self.r[idx, 0, :])
224 | y = abs(self.r[idx, 2, :])
225 |
226 | if component.theta != 0:
227 | x_hit_biprism = np.where(x < component.width)[0]
228 | y_hit_biprism = np.where(y < component.radius)[0]
229 |
230 | elif component.theta == 0:
231 | x_hit_biprism = np.where(x < component.radius)[0]
232 | y_hit_biprism = np.where(y < component.width)[0]
233 |
234 | blocked_idcs = list(set(x_hit_biprism).intersection(y_hit_biprism))
235 |
236 | component.blocked_ray_idcs = blocked_idcs
237 |
238 | self.r[idx, 1, :] = self.r[idx, 1, :] + \
239 | np.sign(self.r[idx, 0, :])*component.matrix[1, 4]
240 | self.r[idx, 3, :] = self.r[idx, 3, :] + \
241 | np.sign(self.r[idx, 2, :])*component.matrix[3, 4]
242 | self.r[idx+1, :, :] = np.matmul(self.propagate(self.z_distances[idx]), self.r[idx, :, :])
243 | idx += 1
244 |
245 | elif component.type == 'Aperture':
246 |
247 | #Special vectorised function for the aperture
248 | xp, yp = self.r[idx, 0, :], self.r[idx, 2, :]
249 | xc, yc = component.x, component.y
250 | distance = np.sqrt((xp-xc)**2 + (yp-yc)**2)
251 |
252 | blocked_ray_bools = np.logical_and(
253 | distance >= component.aperture_radius_inner, distance < component.aperture_radius_outer)
254 | component.blocked_ray_idcs = np.where(blocked_ray_bools)[0]
255 |
256 | self.r[idx+1, :, :] = np.matmul(self.propagate(self.z_distances[idx]), self.r[idx, :, :])
257 | idx += 1
258 | elif component.type == 'Double Deflector':
259 | self.r[idx, :, :] = np.matmul(component.up_matrix, self.r[idx, :, :])
260 | self.r[idx+1, :, :] = np.matmul(self.propagate(self.z_distances[idx]), self.r[idx, :, :])
261 | idx += 1
262 |
263 | self.r[idx, :, :] = np.matmul(component.low_matrix, self.r[idx, :, :])
264 | self.r[idx+1, :, :] = np.matmul(self.propagate(self.z_distances[idx]), self.r[idx, :, :])
265 | idx += 1
266 | else:
267 | #Every other function has a single matrix, so just need to do straightforward matrix multiplication
268 | self.r[idx, :, :] = np.matmul(component.matrix, self.r[idx, :, :])
269 | self.r[idx+1, :, :] = np.matmul(self.propagate(self.z_distances[idx]), self.r[idx, :, :])
270 | idx += 1
271 |
272 | def update_parameters_from_gui(self):
273 | '''Update the GUI
274 | '''
275 | #This code updates the GUI sliders if it exists
276 | self.num_rays = 2**(self.gui.rayslider.value())
277 | self.gun_beam_semi_angle = self.gui.beamangleslider.value()*1e-2
278 | self.beam_radius = self.gui.beamwidthslider.value()*self.beam_radius_init
279 |
280 | self.allowed_ray_idcs = np.arange(self.num_rays)
281 |
282 |
283 | self.beam_tilt_x = self.gui.xangleslider.value()*np.pi*1e-3
284 | self.beam_tilt_y = self.gui.yangleslider.value()*np.pi*1e-3
285 |
286 | if self.gui.checkBoxAxial.isChecked():
287 | self.beam_type = 'axial'
288 | if self.gui.checkBoxParalell.isChecked():
289 | self.beam_type = 'paralell'
290 | if self.gui.checkBoxPoint.isChecked():
291 | self.beam_type = 'point'
292 |
293 | if self.experiment == '4DSTEM':
294 | self.scan_pixels = 2**(self.experiment_gui.scanpixelsslider.value())
295 | self.scanpixelsize = self.sample.width/self.scan_pixels
296 | self.cameralength = self.sample.z
297 | self.set_experiment_labels()
298 |
299 | self.set_model_labels()
300 |
301 | #After updating parameters, we need to regenerate rays.
302 | self.generate_rays()
303 |
304 | def update_scan_coil_ratio(self):
305 |
306 | sample_size = self.components[self.sample_idx].sample_size
307 | scan_position_x = sample_size/(2*self.scan_pixels)+(self.scan_pixel_x/self.scan_pixels)*sample_size - sample_size/2
308 | scan_position_y = sample_size/(2*self.scan_pixels)+(self.scan_pixel_y/self.scan_pixels)*sample_size - sample_size/2
309 |
310 | #Distance to front focal plane from bottom deflector
311 | dist_to_ffp = abs(self.scan_coils.z_low-(self.obj_lens.z+abs(self.obj_lens.f)))
312 | dist_to_lens = abs(self.scan_coils.z_low-self.obj_lens.z)
313 |
314 | self.scan_coils.defratiox = -1-1*self.scan_coils.dist/dist_to_ffp
315 | self.scan_coils.defratioy = -1-1*self.scan_coils.dist/dist_to_ffp
316 |
317 | #upper_deflection = x_specimen/(scan_coil_distance + distance_to_lens*deflector_ratio+distance_to_lens)
318 | self.scan_coils.updefx = scan_position_x/(self.scan_coils.dist+dist_to_lens*self.scan_coils.defratiox+dist_to_lens)
319 | self.scan_coils.lowdefx = self.scan_coils.defratiox*self.scan_coils.updefx
320 |
321 | self.scan_coils.updefy = scan_position_y/(self.scan_coils.dist+dist_to_lens*self.scan_coils.defratiox+dist_to_lens)
322 | self.scan_coils.lowdefy = self.scan_coils.defratioy*self.scan_coils.updefy
323 |
324 | self.scan_coils.set_matrices()
325 |
326 | self.descan_coils.defratiox = (-1-1*self.scan_coils.dist/dist_to_ffp)
327 | self.descan_coils.defratioy = (-1-1*self.scan_coils.dist/dist_to_ffp)
328 |
329 | #upper_deflection = x_specimen/(scan_coil_distance + distance_to_lens*deflector_ratio+distance_to_lens)
330 | self.descan_coils.updefx = -self.scan_coils.updefx*(self.scan_coils.dist+self.scan_coils.defratiox*dist_to_lens+dist_to_lens)/self.descan_coils.dist
331 | self.descan_coils.lowdefx = -self.descan_coils.updefx
332 |
333 | self.descan_coils.updefy = -self.scan_coils.updefy*(self.scan_coils.dist+self.scan_coils.defratiox*dist_to_lens+dist_to_lens)/self.descan_coils.dist
334 | self.descan_coils.lowdefy = -self.descan_coils.updefy
335 |
336 | self.descan_coils.set_matrices()
337 |
338 | def update_scan_position(self):
339 |
340 | self.scan_pixel_x +=1
341 |
342 | if self.scan_pixel_x == self.scan_pixels:
343 | self.scan_pixel_x = 0
344 | self.scan_pixel_y += 1
345 | if self.scan_pixel_y == self.scan_pixels:
346 | self.scan_pixel_y = 0
347 |
348 | def set_model_labels(self):
349 | '''Set labels of the model inside the GUI
350 | '''
351 | self.gui.raylabel.setText(
352 | str(self.num_rays))
353 | self.gui.beamanglelabel.setText(
354 | str(round(self.gun_beam_semi_angle, 2)))
355 | self.gui.beamwidthlabel.setText(
356 | str(round(self.beam_radius, 5)))
357 |
358 | self.gui.xanglelabel.setText(
359 | str('Beam Tilt X (Radians) = ' + "{:.3f}".format(self.beam_tilt_x))
360 | )
361 | self.gui.yanglelabel.setText(
362 | str('Beam Tilt Y (Radians) = ' + "{:.3f}".format(self.beam_tilt_y))
363 | )
364 |
365 | def set_experiment_labels(self):
366 | '''Set labels of the model inside the GUI
367 | '''
368 | self.experiment_gui.scanpixelslabel.setText(
369 | str(self.scan_pixels))
370 | self.experiment_gui.overfocuslabel.setText(
371 | str('Overfocus = ' + str(round(self.overfocus, 4))))
372 | self.experiment_gui.semiconvlabel.setText(
373 | str('Semiconv = ' + str(round(self.semiconv, 4))))
374 | self.experiment_gui.scanpixelsizelabel.setText(
375 | str('Scan pixel size = ' + str(round(self.scanpixelsize, 4))))
376 | self.experiment_gui.cameralengthlabel.setText(
377 | str('Camera length = ' + str(round(self.cameralength, 4))))
378 |
379 | def step(self):
380 | '''Master function that updates the matrices and perfroms ray propagation
381 |
382 | Returns
383 | -------
384 | r : ndarray
385 | Returns the array of ray positions
386 | '''
387 | #This method performs the computation of updating the matrices to their gui slider
388 | #paramaters, and of moving the rays throgh the model.
389 | self.update_component_matrix()
390 | self.update_rays_stepwise()
391 |
392 | return self.r
393 |
394 | #Propagation matrix used by the model to propagate rays between components
395 | def propagate(self, z):
396 | '''Propagation matrix
397 |
398 | Parameters
399 | ----------
400 | z : float
401 | Distance to propagate rays
402 |
403 | Returns
404 | -------
405 | ndarray
406 | Propagation matrix
407 | '''
408 |
409 | matrix = np.array([[1, z, 0, 0, 0],
410 | [0, 1, 0, 0, 0],
411 | [0, 0, 1, z, 0],
412 | [0, 0, 0, 1, 0],
413 | [0, 0, 0, 0, 1]])
414 |
415 | return matrix
416 |
--------------------------------------------------------------------------------
/src/temgymbasic/shapes.py:
--------------------------------------------------------------------------------
1 |
2 | import numpy as np
3 | import triangle as tr
4 |
5 |
6 | def square(w, x, y, z):
7 | '''Generates vertices for a square 3D model. Used to represent the detector
8 |
9 | Parameters
10 | ----------
11 | w : float
12 | Width of the square wire model
13 | x : float
14 | X position of the square wire model
15 | y : float
16 | Wire position of the square wire model
17 | z : float
18 | Wire position of the square wire model
19 |
20 | Returns
21 | -------
22 | verts3D: ndarray
23 | vertices to draw a 3D model
24 | '''
25 | vertices = np.array([[x + w/2, y + w/2], [x - w/2, y + w/2], [x - w/2, y - w/2],
26 | [x + w/2, y - w/2], [x + w/2, y + w/2]])
27 |
28 | sample_dict = dict(vertices=vertices)
29 |
30 | sample_tri = tr.triangulate(sample_dict)
31 |
32 | zverts = np.ones((sample_tri['triangles'].shape[0], 3, 1), dtype=np.float32)*z
33 | verts_2D = sample_tri['vertices'][sample_tri['triangles']]
34 | verts_3D = np.dstack([verts_2D, zverts])
35 |
36 | return verts_3D
37 |
38 |
39 | def deflector(r, phi, z, n_arc):
40 | '''Wire model geometry of deflector
41 |
42 | Parameters
43 | ----------
44 | r : float
45 | Radius of deflector geometry
46 | phi : float
47 | Angular width of deflector mode
48 | z : float
49 | Z position of deflector geometry
50 | n_arc : int
51 | Number of arcs to use to make up the model
52 |
53 | Returns
54 | -------
55 | points_arc_1 : ndarray
56 | Points of a circle to represent the lens geometry
57 | points_arc_2 : ndarray
58 | Points of a circle to represent the lens geometry
59 | '''
60 |
61 | THETA = np.linspace(-phi, phi, n_arc, endpoint=True)
62 | R = r*np.ones(np.size(THETA))
63 | Z = z*np.ones(np.size(THETA))
64 |
65 | points_arc_1 = np.array([R*np.cos(THETA), R*np.sin(THETA), Z])
66 | points_arc_2 = np.array([-R*np.cos(THETA), -R*np.sin(-THETA), Z])
67 |
68 | return points_arc_1, points_arc_2
69 |
70 |
71 | def lens(r, z, n_arc):
72 | '''Wire model geometry of lens
73 |
74 | Parameters
75 | ----------
76 | r : float
77 | Radius of lens geometry
78 | z : float
79 | Z position of lens geometry
80 | n_arc : int
81 | Number of arcs to use to make up the model
82 |
83 | Returns
84 | -------
85 | points_circle : ndarray
86 | Points of a circle to represent the lens geometry
87 | '''
88 | THETA = np.linspace(0, 2*np.pi, n_arc, endpoint=True)
89 | R = r*np.ones(np.size(THETA))
90 | Z = z*np.ones(np.size(THETA))
91 |
92 | points_circle = np.array([R*np.cos(THETA), R*np.sin(THETA), Z])
93 |
94 | return points_circle
95 |
96 |
97 | def biprism(r, z, theta):
98 | '''Wire model geometry for biprism
99 |
100 | Parameters
101 | ----------
102 | r : float
103 | Radius of wire
104 | z : float
105 | Z position of wire
106 | theta : float
107 | Angle of wire - Two options, 0 or np.pi/2
108 |
109 | Returns
110 | -------
111 | points : ndarray
112 | Points array of wire geometry
113 | '''
114 | THETA = np.array([theta, theta+np.pi])
115 | R = r*np.ones(np.size(THETA))
116 | Z = z*np.ones(np.size(THETA))
117 |
118 | points = np.array([R*np.cos(THETA), R*np.sin(THETA), Z])
119 |
120 | return points
121 |
122 |
123 | def quadrupole(r, phi, z, n_arc):
124 | '''Wire model geometry of deflector
125 |
126 | Parameters
127 | ----------
128 | r : float
129 | Radius of quadrupole geometry
130 | phi : float
131 | Angular width of quadrupole mode
132 | z : float
133 | Z position of quadrupole geometry
134 | n_arc : int
135 | Number of arcs to use to make up the model
136 |
137 | Returns
138 | -------
139 | points_arc_1 : ndarray
140 | Points of first semi circle that represent the quadrupole geometry
141 | points_arc_2 : ndarray
142 | Points of second semi circle that represent the quadrupole geometry
143 | points_arc_3 : ndarray
144 | Points of third semi circle that represent the quadrupole geometry
145 | points_arc_4 : ndarray
146 | Points of fourth semi circle that represent the quadrupole geometry
147 | '''
148 |
149 | THETA = np.linspace(-phi, phi, n_arc, endpoint=True)
150 | R = r*np.ones(np.size(THETA))
151 | Z = z*np.ones(np.size(THETA))
152 |
153 | points_arc_1 = np.array([R*np.cos(THETA), R*np.sin(THETA), Z])
154 | points_arc_2 = np.array([-R*np.cos(THETA), -R*np.sin(-THETA), Z])
155 | points_arc_3 = np.array([R*np.cos(THETA+np.pi/2), R*np.sin(THETA+np.pi/2), Z])
156 | points_arc_4 = np.array([-R*np.cos(THETA+np.pi/2), -R*np.sin(-THETA+np.pi/2), Z])
157 |
158 | return points_arc_1, points_arc_2, points_arc_3, points_arc_4
159 |
160 |
161 | def aperture(r_i, r_o, n_i, n_o, x, y, z):
162 | '''3D vertices model of an aperture
163 |
164 | Parameters
165 | ----------
166 | r_i : float
167 | Radius of inner aperture model
168 | r_o : float
169 | Radius of outer aperture model
170 | n_i : int
171 | Number of points used to represent inner aperture model
172 | n_o : int
173 | Number of points used to represent outer aperture model
174 | x : float
175 | X position of aperture
176 | y : float
177 | Y position of aperture
178 | z : float
179 | Z position of aperture
180 |
181 | Returns
182 | -------
183 | verts3D : ndarray
184 | 3D array of vertices that represent the aperture model
185 | '''
186 | i_i = np.arange(n_i)
187 | i_o = np.arange(n_o)
188 | theta_i = i_i * 2 * np.pi / n_i
189 | theta_o = i_o * 2 * np.pi / n_o
190 | pts_inner = np.stack([x + np.cos(theta_i)*r_i, y + np.sin(theta_i)*r_i], axis=1)
191 | pts_outer = np.stack([x + np.cos(theta_o)*r_o, y + np.sin(theta_o)*r_o], axis=1)
192 | seg_i = np.stack([i_i, i_i + 1], axis=1) % n_i
193 | seg_o = np.stack([i_o, i_o + 1], axis=1) % n_o
194 |
195 | pts = np.vstack([pts_outer, pts_inner])
196 | seg = np.vstack([seg_o, seg_i + seg_o.shape[0]])
197 |
198 | aperture_dict = dict(vertices=pts, segments=seg, holes=[[x, y]])
199 | aperture_tri = tr.triangulate(aperture_dict, 'qpa0.05')
200 |
201 | zverts = np.ones((aperture_tri['triangles'].shape[0], 3, 1), dtype=np.float32)*z
202 | verts_2D = aperture_tri['vertices'][aperture_tri['triangles']]
203 | verts_3D = np.dstack([verts_2D, zverts])
204 |
205 | return verts_3D
206 |
--------------------------------------------------------------------------------