├── .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 | 6 | 7 | 8 | 9 | 10 | 2022-09-06T16:42:45.680709 11 | image/svg+xml 12 | 13 | 14 | Matplotlib v3.3.3, https://matplotlib.org/ 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 33 | 34 | 35 | 36 | 81 | 114 | 133 | 161 | 176 | 184 | 190 | 198 | 215 | 236 | 269 | 302 | 310 | 316 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | 432 | 433 | 434 | 435 | 436 | 437 | -------------------------------------------------------------------------------- /Figures/Raw Images/Lens_Matrix.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 2022-09-06T16:42:45.619089 11 | image/svg+xml 12 | 13 | 14 | Matplotlib v3.3.3, https://matplotlib.org/ 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 33 | 34 | 35 | 36 | 81 | 102 | 121 | 154 | 177 | 192 | 200 | 206 | 214 | 231 | 252 | 260 | 288 | 296 | 302 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 431 | 438 | 439 | 440 | 441 | 442 | -------------------------------------------------------------------------------- /Figures/Raw Images/Quad_Matrix.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 2022-09-06T16:42:45.653540 11 | image/svg+xml 12 | 13 | 14 | Matplotlib v3.3.3, https://matplotlib.org/ 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 33 | 34 | 35 | 36 | 81 | 104 | 130 | 157 | 186 | 201 | 209 | 215 | 223 | 240 | 261 | 269 | 297 | 330 | 363 | 371 | 377 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | 432 | 433 | 434 | 435 | 436 | 437 | 438 | 439 | 440 | 441 | 442 | 443 | 444 | 445 | 446 | 447 | 448 | 449 | 450 | 451 | 452 | 453 | 454 | 455 | 456 | 457 | 458 | 459 | 460 | 461 | 462 | 463 | 464 | 465 | 466 | 467 | 468 | 469 | 470 | 471 | 472 | 473 | 474 | 475 | 476 | 477 | 478 | 479 | 480 | 481 | 482 | 483 | 484 | 485 | 486 | 487 | 488 | 489 | 490 | 491 | 492 | 493 | 494 | 495 | 496 | 497 | 498 | 499 | 500 | 501 | 502 | 503 | 510 | 517 | 518 | 519 | 520 | 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 | 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 | --------------------------------------------------------------------------------