├── .gitignore ├── .readthedocs.yaml ├── LICENSE ├── MANIFEST.in ├── README.md ├── docs ├── Makefile ├── about.rst ├── changelog.rst ├── conf.py ├── index.rst ├── instrument.rst ├── make.bat ├── microscope.rst ├── requirements.txt ├── restrictions.rst └── server.rst ├── pyproject.toml ├── scripts ├── run_null_server.py ├── test_camera_acquisition.py ├── test_instrument.py ├── test_json_encoder.py └── test_microscope.py ├── setup.py └── temscript ├── __init__.py ├── _com.py ├── _instrument_com.py ├── _instrument_stubs.py ├── base_microscope.py ├── enums.py ├── instrument.py ├── marshall.py ├── microscope.py ├── null_microscope.py ├── remote_microscope.py ├── server.py └── version.py /.gitignore: -------------------------------------------------------------------------------- 1 | /dist 2 | /build 3 | /temscript.egg-info 4 | /MANIFEST 5 | docs/_build 6 | .idea 7 | __pycache__ 8 | -------------------------------------------------------------------------------- /.readthedocs.yaml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | build: 4 | os: "ubuntu-20.04" 5 | tools: 6 | python: "3.9" 7 | 8 | sphinx: 9 | configuration: docs/conf.py 10 | 11 | python: 12 | install: 13 | - requirements: docs/requirements.txt 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012-2021, Tore Niermann 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | * Redistributions of source code must retain the above copyright 7 | notice, this list of conditions and the following disclaimer. 8 | * Redistributions in binary form must reproduce the above copyright 9 | notice, this list of conditions and the following disclaimer in the 10 | documentation and/or other materials provided with the distribution. 11 | * The names of the contributor(s) may not be used to endorse or promote 12 | products derived from this software without specific prior written 13 | permission. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 16 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL TORE NIERMANN BE LIABLE FOR ANY 19 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 20 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 21 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 22 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 24 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | graft docs 2 | graft scripts 3 | include LICENSE 4 | include README.md 5 | prune docs/_build 6 | exclude stdscript.dll 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # About 2 | 3 | The ``temscript`` package provides a Python wrapper for the scripting 4 | interface of Thermo Fisher Scientific and FEI microscopes. The functionality is 5 | limited to the functionality of the original scripting interface. For detailed information 6 | about TEM scripting see the documentation accompanying your microscope. 7 | 8 | The ``temscript`` package provides two interfaces to the microsope. The first one 9 | corresponds directly to the COM interface. The other interface is a more high level interface. 10 | Within the ``temscript`` package three implementation for the high level interface are provided, 11 | one for running scripts directly on the microscope PC, one to run scripts remotely over network, and 12 | finally a dummy implementation for offline development & testing exists. 13 | 14 | Currently the ``temscript`` package requires Python 3.4 or higher. The current plan is to keep the minimum 15 | supported Python version at 3.4, since this is the latest Python version supporting Windows XP. 16 | 17 | The sources can be found on GitHub: https://github.com/niermann/temscript 18 | 19 | # Documentation 20 | 21 | The documentation of the latest version can be found at: 22 | 23 | https://temscript.readthedocs.io/ 24 | 25 | # Installation 26 | 27 | Requirements: 28 | * Python >= 3.4 (tested with 3.4) 29 | * Numpy (tested with 1.9) 30 | * Sphinx (only for building documentation, tested with 1.6) 31 | 32 | On all platforms the dummy and remote high level interfaces are provided. 33 | On Windows platforms the package provides the Python wrapper 34 | to the scripting COM interface. However, trying to instantiate this wrapper 35 | will fail, if the scripting COM classes are not installed locally. 36 | 37 | ### Installation from PyPI (using pip) 38 | 39 | This assumes you have connection to the internet. 40 | 41 | Execute from the command line (assuming you have your python interpreter in the path, this might require superuser or 42 | administrator privileges): 43 | 44 | python3 -m pip install --upgrade pip 45 | python3 -m pip install temscript 46 | 47 | ### Offline-Installation from wheels file (using pip) 48 | 49 | This assumes you have downloaded the wheels file .whl 50 | 51 | Execute from the command line (assuming you have your python interpreter in the path, this might require superuser or 52 | administrator privileges): 53 | 54 | python3 -m pip install --upgrade pip 55 | python3 -m pip install .whl 56 | 57 | ### Installation from sources (using pip) 58 | 59 | This assumes you have downloaded and extracted the sources into the directory (alternative have 60 | cloned the sources from GitHub into ). 61 | 62 | Execute from the command line (assuming you have your python interpreter in the path, this might require superuser or 63 | administrator privileges): 64 | 65 | python3 -m pip install --upgrade pip 66 | python3 -m pip install 67 | 68 | ### Installation from sources (using distutils) 69 | 70 | This assumes you have downloaded and extracted the sources into the directory (alternative have 71 | cloned the sources from GitHub into ). 72 | 73 | Execute from the command line (assuming you have your python interpreter in the path, this might require superuser or 74 | administrator privileges): 75 | 76 | cd 77 | python3 setup.py install 78 | 79 | # Supported functions of the COM interface 80 | 81 | Relative to Titan V1.1 scripting adapter: 82 | * Projection: complete 83 | * Stage: complete 84 | * Configuration: complete 85 | * Acquisition: complete 86 | * AcqImage: complete 87 | * CCDCamera: complete 88 | * CCDCameraInfo: complete 89 | * CCDAcqParams: complete 90 | * STEMDetector: complete (but untested) 91 | * STEMAcqParams: complete (but untested) 92 | * STEMDetectorInfo: complete (but untested) 93 | * Camera: complete 94 | * Gauge: complete 95 | * Vacuum: complete 96 | * UserButton: missing 97 | * AutoLoader: missing 98 | * TemperatureControl: missing 99 | * Illumination: complete 100 | 101 | # Copyright & Disclaimer 102 | 103 | Copyright (c) 2012-2021 by Tore Niermann 104 | Contact: tore.niermann (at) tu-berlin.de 105 | 106 | All product and company names are trademarks or registered trademarks 107 | of their respective holders. Use of them does not imply any affiliation 108 | with or endorsement by them. 109 | 110 | temscript is distributed in the hope that it will be useful, 111 | but WITHOUT ANY WARRANTY; without even the implied warranty of 112 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 113 | LICENCE.txt file for any details. 114 | 115 | All product and company names are trademarks or registered trademarks of 116 | their respective holders. Use of them does not imply any affiliation 117 | with or endorsement by them. 118 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = _build 9 | 10 | # User-friendly check for sphinx-build 11 | ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) 12 | $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) 13 | endif 14 | 15 | # Internal variables. 16 | PAPEROPT_a4 = -D latex_paper_size=a4 17 | PAPEROPT_letter = -D latex_paper_size=letter 18 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 19 | # the i18n builder cannot share the environment and doctrees with the others 20 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 21 | 22 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext 23 | 24 | help: 25 | @echo "Please use \`make ' where is one of" 26 | @echo " html to make standalone HTML files" 27 | @echo " dirhtml to make HTML files named index.html in directories" 28 | @echo " singlehtml to make a single large HTML file" 29 | @echo " pickle to make pickle files" 30 | @echo " json to make JSON files" 31 | @echo " htmlhelp to make HTML files and a HTML help project" 32 | @echo " qthelp to make HTML files and a qthelp project" 33 | @echo " devhelp to make HTML files and a Devhelp project" 34 | @echo " epub to make an epub" 35 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 36 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 37 | @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" 38 | @echo " text to make text files" 39 | @echo " man to make manual pages" 40 | @echo " texinfo to make Texinfo files" 41 | @echo " info to make Texinfo files and run them through makeinfo" 42 | @echo " gettext to make PO message catalogs" 43 | @echo " changes to make an overview of all changed/added/deprecated items" 44 | @echo " xml to make Docutils-native XML files" 45 | @echo " pseudoxml to make pseudoxml-XML files for display purposes" 46 | @echo " linkcheck to check all external links for integrity" 47 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 48 | 49 | clean: 50 | rm -rf $(BUILDDIR)/* 51 | 52 | html: 53 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 54 | @echo 55 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 56 | 57 | dirhtml: 58 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 59 | @echo 60 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 61 | 62 | singlehtml: 63 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 64 | @echo 65 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 66 | 67 | pickle: 68 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 69 | @echo 70 | @echo "Build finished; now you can process the pickle files." 71 | 72 | json: 73 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 74 | @echo 75 | @echo "Build finished; now you can process the JSON files." 76 | 77 | htmlhelp: 78 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 79 | @echo 80 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 81 | ".hhp project file in $(BUILDDIR)/htmlhelp." 82 | 83 | qthelp: 84 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 85 | @echo 86 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 87 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 88 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/PyTEM.qhcp" 89 | @echo "To view the help file:" 90 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/PyTEM.qhc" 91 | 92 | devhelp: 93 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 94 | @echo 95 | @echo "Build finished." 96 | @echo "To view the help file:" 97 | @echo "# mkdir -p $$HOME/.local/share/devhelp/PyTEM" 98 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/PyTEM" 99 | @echo "# devhelp" 100 | 101 | epub: 102 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 103 | @echo 104 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 105 | 106 | latex: 107 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 108 | @echo 109 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 110 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 111 | "(use \`make latexpdf' here to do that automatically)." 112 | 113 | latexpdf: 114 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 115 | @echo "Running LaTeX files through pdflatex..." 116 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 117 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 118 | 119 | latexpdfja: 120 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 121 | @echo "Running LaTeX files through platex and dvipdfmx..." 122 | $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja 123 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 124 | 125 | text: 126 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 127 | @echo 128 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 129 | 130 | man: 131 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 132 | @echo 133 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 134 | 135 | texinfo: 136 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 137 | @echo 138 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 139 | @echo "Run \`make' in that directory to run these through makeinfo" \ 140 | "(use \`make info' here to do that automatically)." 141 | 142 | info: 143 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 144 | @echo "Running Texinfo files through makeinfo..." 145 | make -C $(BUILDDIR)/texinfo info 146 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 147 | 148 | gettext: 149 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 150 | @echo 151 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 152 | 153 | changes: 154 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 155 | @echo 156 | @echo "The overview file is in $(BUILDDIR)/changes." 157 | 158 | linkcheck: 159 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 160 | @echo 161 | @echo "Link check complete; look for any errors in the above output " \ 162 | "or in $(BUILDDIR)/linkcheck/output.txt." 163 | 164 | doctest: 165 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 166 | @echo "Testing of doctests in the sources finished, look at the " \ 167 | "results in $(BUILDDIR)/doctest/output.txt." 168 | 169 | xml: 170 | $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml 171 | @echo 172 | @echo "Build finished. The XML files are in $(BUILDDIR)/xml." 173 | 174 | pseudoxml: 175 | $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml 176 | @echo 177 | @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." 178 | -------------------------------------------------------------------------------- /docs/about.rst: -------------------------------------------------------------------------------- 1 | .. currentmodule:: temscript 2 | 3 | About 4 | ===== 5 | 6 | Introduction 7 | ------------ 8 | 9 | The ``temscript`` package provides a Python wrapper for the scripting 10 | interface of Thermo Fisher Scientific and FEI microscopes. The functionality is 11 | limited to the functionality of the original scripting interface. For detailed information 12 | about TEM scripting see the documentation accompanying your microscope. 13 | 14 | The ``temscript`` package provides two interfaces to the microsope. The first one 15 | corresponds directly to the COM interface and is implemented by the :class:`Instrument` class. A more thorough 16 | description of this interface can be found in the :ref:`instrument` section. 17 | 18 | The other interface is provided by the :class:`Microscope` class. While instances of the :class:`temscript.Microscope` class 19 | operate on the computer connected to the microscope directly, there are two replacement classes, which provide the 20 | same interface to as the :class:`Microscope` class. The first one, :class:`RemoteMicroscope` allows to operate the 21 | microscope remotely from a computer, which is connected to the microscope PC via network. The other one, 22 | :class:`NullMicroscope` serves as dummy replacement for offline development. A more thorough 23 | description of this interface can be found in the :ref:`microscope` section. 24 | 25 | For remote operation of the microscope the temscript server must run on the microscope PC. See section :ref:`server` 26 | for details. 27 | 28 | The section :ref:`restrictions` describes some known issues with the scripting interface itself. These are restrictions 29 | of the original scripting interface and not issues related to the ``temscript`` package itself. 30 | 31 | Quick example 32 | ------------- 33 | 34 | Execute this on the microscope PC (with ``temscript`` package installed) to create an instance of the local 35 | :class:`Microscope` interface: 36 | 37 | >>> import temscript 38 | >>> microscope = temscript.Microscope() 39 | 40 | Show the current acceleration voltage: 41 | 42 | >>> microscope.get_voltage() 43 | 300.0 44 | 45 | Move beam: 46 | 47 | >>> beam_pos = microscope.get_beam_shift() 48 | >>> print(beam_pos) 49 | (0.0, 0.0) 50 | >>> new_beam_pos = beam_pos[0], beam_pos[1] + 1e-6 51 | >>> microscope.set_beam_shift(new_beam_pos) 52 | -------------------------------------------------------------------------------- /docs/changelog.rst: -------------------------------------------------------------------------------- 1 | Changelog 2 | ========= 3 | 4 | Version 2.1.1 5 | ^^^^^^^^^^^^^ 6 | 7 | * Fixed error handling in server 8 | * Don't throw error for get_stage_holder(), if no holder inserted 9 | 10 | Version 2.1.0 11 | ^^^^^^^^^^^^^ 12 | 13 | * Changed default apartment model to COINIT_APARTMENTTHREADED to increase compatibility with comtypes 14 | * COM initialization respects setting from sys.coinit_flags 15 | 16 | Version 2.0.1 17 | ^^^^^^^^^^^^^ 18 | 19 | * Added Talos as ProductFamily 20 | * Added more graceful behavior for unsupported ProductFamily query and unknown future families. 21 | * Added column valves state to Microscope classes. 22 | 23 | Version 2.0.0 24 | ^^^^^^^^^^^^^ 25 | 26 | * C++ adapter removed, COM interface no directly accessed using ``ctypes`` 27 | * Raised required minimum Python version to 3.4 (dropped support of Python 2.X) 28 | * More extensive documentation of the high level interfaces and the temscript server 29 | * Documentation of known issues of the original scripting interface 30 | * Support of the fluorescent screen 31 | * Separation of STEM detectors and CCD cameras in high level interface 32 | * Deprecation of the methods 'get_detectors', 'get_detector_param', 'set_detector_params', and 'get_optics_state' of 33 | 'Microscope' and related classes. See docs for further details. 34 | * Deprecation of the property 'AcqParams' of 'STEMDetector'. See docs for further details. 35 | * Deprecation of the use of 'speed' and 'method' keywords in position dictionary of the 'set_stage_position' method. 36 | * Abstract base class for high level interface 37 | * Test scripts 38 | * More illumination related functions 39 | * TEM/STEM mode control 40 | * Several small improvements and fixes 41 | 42 | Version 1.0.10 43 | ^^^^^^^^^^^^^^ 44 | 45 | * Speed keyword added Stage.Goto / Microscope.set_stage_position 46 | * A lot of properties added to Microscope API (DiffShift, ObjStig, CondStig, Projection Mode / SubMode, Magnification, Normalization) 47 | * More properties returned by Microscope.get_optics_state 48 | * Timeout for RemoteMicroscope 49 | * Lots of fixes 50 | 51 | Version 1.0.9 52 | ^^^^^^^^^^^^^ 53 | 54 | * Normalization methods in new interface. 55 | * Projective system settings in new interface. 56 | 57 | Version 1.0.7 58 | ^^^^^^^^^^^^^ 59 | 60 | Started new interface (with client/server support). 61 | 62 | Version 1.0.5 63 | ^^^^^^^^^^^^^ 64 | 65 | * Small fixes 66 | * Clarified license: 3-clause BSD 67 | * Compatibility to Py3K and anaconda distribution 68 | 69 | Version 1.0.3 70 | ^^^^^^^^^^^^^ 71 | 72 | * Fixed some small things 73 | 74 | Version 1.0.2 75 | ^^^^^^^^^^^^^ 76 | 77 | * Renamed project to temscript. 78 | * Created documentation. 79 | 80 | Version 1.0.1 81 | ^^^^^^^^^^^^^ 82 | 83 | * Fixed memory leak related to safearray handling 84 | 85 | Version 1.0.0 86 | ^^^^^^^^^^^^^ 87 | 88 | * Initial release 89 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # temscript documentation build configuration file, created by 4 | # sphinx-quickstart on Wed Apr 18 05:56:30 2012. 5 | # 6 | # This file is execfile()d with the current directory set to its containing dir. 7 | # 8 | # Note that not all possible configuration values are present in this 9 | # autogenerated file. 10 | # 11 | # All configuration values have a default; values that are commented out 12 | # serve to show the default. 13 | 14 | import sys, os 15 | 16 | # Import version number 17 | with open("../temscript/version.py") as fp: 18 | exec(fp.read()) 19 | 20 | # If extensions (or modules to document with autodoc) are in another directory, 21 | # add these directories to sys.path here. If the directory is relative to the 22 | # documentation root, use os.path.abspath to make it absolute, like shown here. 23 | sys.path.insert(0, os.path.abspath('..')) 24 | 25 | # -- General configuration ----------------------------------------------------- 26 | 27 | # If your documentation needs a minimal Sphinx version, state it here. 28 | #needs_sphinx = '1.0' 29 | 30 | # Add any Sphinx extension module names here, as strings. They can be extensions 31 | # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. 32 | extensions = ['sphinx.ext.autodoc'] 33 | 34 | # Add any paths that contain templates here, relative to this directory. 35 | templates_path = ['_templates'] 36 | 37 | # The suffix of source filenames. 38 | source_suffix = '.rst' 39 | 40 | # The encoding of source files. 41 | #source_encoding = 'utf-8-sig' 42 | 43 | # The master toctree document. 44 | master_doc = 'index' 45 | 46 | # General information about the project. 47 | project = u'temscript' 48 | copyright = u'2012-2021, Tore Niermann' 49 | 50 | # The version info for the project you're documenting, acts as replacement for 51 | # |version| and |release|, also used in various other places throughout the 52 | # built documents. 53 | # 54 | # The short X.Y version. 55 | version = __version__ 56 | # The full version, including alpha/beta/rc tags. 57 | release = __version__ 58 | 59 | # The language for content autogenerated by Sphinx. Refer to documentation 60 | # for a list of supported languages. 61 | #language = None 62 | 63 | # There are two options for replacing |today|: either, you set today to some 64 | # non-false value, then it is used: 65 | #today = '' 66 | # Else, today_fmt is used as the format for a strftime call. 67 | #today_fmt = '%B %d, %Y' 68 | 69 | # List of patterns, relative to source directory, that match files and 70 | # directories to ignore when looking for source files. 71 | exclude_patterns = ['_build'] 72 | 73 | # The reST default role (used for this markup: `text`) to use for all documents. 74 | #default_role = None 75 | 76 | # If true, '()' will be appended to :func: etc. cross-reference text. 77 | #add_function_parentheses = True 78 | 79 | # If true, the current module name will be prepended to all description 80 | # unit titles (such as .. function::). 81 | #add_module_names = True 82 | 83 | # If true, sectionauthor and moduleauthor directives will be shown in the 84 | # output. They are ignored by default. 85 | #show_authors = False 86 | 87 | # The name of the Pygments (syntax highlighting) style to use. 88 | pygments_style = 'sphinx' 89 | 90 | # A list of ignored prefixes for module index sorting. 91 | #modindex_common_prefix = [] 92 | 93 | 94 | # -- Options for HTML output --------------------------------------------------- 95 | 96 | # The theme to use for HTML and HTML Help pages. See the documentation for 97 | # a list of builtin themes. 98 | html_theme = 'sphinx_rtd_theme' 99 | 100 | # Theme options are theme-specific and customize the look and feel of a theme 101 | # further. For a list of options available for each theme, see the 102 | # documentation. 103 | #html_theme_options = {} 104 | 105 | # Add any paths that contain custom themes here, relative to this directory. 106 | import sphinx_rtd_theme 107 | html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] 108 | 109 | # The name for this set of Sphinx documents. If None, it defaults to 110 | # " v documentation". 111 | #html_title = None 112 | 113 | # A shorter title for the navigation bar. Default is the same as html_title. 114 | #html_short_title = None 115 | 116 | # The name of an image file (relative to this directory) to place at the top 117 | # of the sidebar. 118 | #html_logo = None 119 | 120 | # The name of an image file (within the static path) to use as favicon of the 121 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 122 | # pixels large. 123 | #html_favicon = None 124 | 125 | # Add any paths that contain custom static files (such as style sheets) here, 126 | # relative to this directory. They are copied after the builtin static files, 127 | # so a file named "default.css" will overwrite the builtin "default.css". 128 | html_static_path = ['_static'] 129 | 130 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 131 | # using the given strftime format. 132 | #html_last_updated_fmt = '%b %d, %Y' 133 | 134 | # If true, SmartyPants will be used to convert quotes and dashes to 135 | # typographically correct entities. 136 | #html_use_smartypants = True 137 | 138 | # Custom sidebar templates, maps document names to template names. 139 | #html_sidebars = {} 140 | 141 | # Additional templates that should be rendered to pages, maps page names to 142 | # template names. 143 | #html_additional_pages = {} 144 | 145 | # If false, no module index is generated. 146 | #html_domain_indices = True 147 | 148 | # If false, no index is generated. 149 | #html_use_index = True 150 | 151 | # If true, the index is split into individual pages for each letter. 152 | #html_split_index = False 153 | 154 | # If true, links to the reST sources are added to the pages. 155 | #html_show_sourcelink = True 156 | 157 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 158 | #html_show_sphinx = True 159 | 160 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 161 | #html_show_copyright = True 162 | 163 | # If true, an OpenSearch description file will be output, and all pages will 164 | # contain a tag referring to it. The value of this option must be the 165 | # base URL from which the finished HTML is served. 166 | #html_use_opensearch = '' 167 | 168 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 169 | #html_file_suffix = None 170 | 171 | # Output file base name for HTML help builder. 172 | htmlhelp_basename = 'temscriptdoc' 173 | 174 | 175 | # -- Options for LaTeX output -------------------------------------------------- 176 | 177 | latex_elements = { 178 | # The paper size ('letterpaper' or 'a4paper'). 179 | #'papersize': 'letterpaper', 180 | 181 | # The font size ('10pt', '11pt' or '12pt'). 182 | #'pointsize': '10pt', 183 | 184 | # Additional stuff for the LaTeX preamble. 185 | #'preamble': '', 186 | } 187 | 188 | # Grouping the document tree into LaTeX files. List of tuples 189 | # (source start file, target name, title, author, documentclass [howto/manual]). 190 | latex_documents = [ 191 | ('index', 'temscript.tex', u'temscript Documentation', 192 | u'Tore Niermann', 'manual'), 193 | ] 194 | 195 | # The name of an image file (relative to this directory) to place at the top of 196 | # the title page. 197 | #latex_logo = None 198 | 199 | # For "manual" documents, if this is true, then toplevel headings are parts, 200 | # not chapters. 201 | #latex_use_parts = False 202 | 203 | # If true, show page references after internal links. 204 | #latex_show_pagerefs = False 205 | 206 | # If true, show URL addresses after external links. 207 | #latex_show_urls = False 208 | 209 | # Documents to append as an appendix to all manuals. 210 | #latex_appendices = [] 211 | 212 | # If false, no module index is generated. 213 | #latex_domain_indices = True 214 | 215 | 216 | # -- Options for manual page output -------------------------------------------- 217 | 218 | # One entry per manual page. List of tuples 219 | # (source start file, name, description, authors, manual section). 220 | man_pages = [ 221 | ('index', 'temscript', u'temscript Documentation', 222 | [u'Tore Niermann'], 1) 223 | ] 224 | 225 | # If true, show URL addresses after external links. 226 | #man_show_urls = False 227 | 228 | 229 | # -- Options for Texinfo output ------------------------------------------------ 230 | 231 | # Grouping the document tree into Texinfo files. List of tuples 232 | # (source start file, target name, title, author, 233 | # dir menu entry, description, category) 234 | texinfo_documents = [ 235 | ('index', 'temscript', u'temscript Documentation', 236 | u'Tore Niermann', 'temscript', 'One line description of project.', 237 | 'Miscellaneous'), 238 | ] 239 | 240 | # Documents to append as an appendix to all manuals. 241 | #texinfo_appendices = [] 242 | 243 | # If false, no module index is generated. 244 | #texinfo_domain_indices = True 245 | 246 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 247 | #texinfo_show_urls = 'footnote' 248 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | temscript documentation 2 | ======================= 3 | 4 | Contents: 5 | 6 | .. toctree:: 7 | :maxdepth: 2 8 | 9 | about 10 | instrument 11 | microscope 12 | server 13 | restrictions 14 | changelog 15 | 16 | Globals 17 | ------- 18 | 19 | .. data:: temscript.version 20 | 21 | A string describing the version of temscript in the format 'X.Y.Z'. 22 | Current value is '|release|'. This is not the version of the TEMScripting interface 23 | (which can't be queried such easily). 24 | 25 | Copyright & Disclaimer 26 | ---------------------- 27 | 28 | Copyright (c) 2012-2021 by Tore Niermann 29 | Contact: tore.niermann (at) tu-berlin.de 30 | 31 | All product and company names are trademarks or registered trademarks 32 | of their respective holders. Use of them does not imply any affiliation 33 | with or endorsement by them. 34 | 35 | temscript is distributed in the hope that it will be useful, 36 | but WITHOUT ANY WARRANTY; without even the implied warranty of 37 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 38 | LICENCE.txt file for any details. 39 | 40 | All product and company names are trademarks or registered trademarks of 41 | their respective holders. Use of them does not imply any affiliation 42 | with or endorsement by them. 43 | 44 | Indices and tables 45 | ================== 46 | 47 | * :ref:`genindex` 48 | * :ref:`search` 49 | 50 | -------------------------------------------------------------------------------- /docs/instrument.rst: -------------------------------------------------------------------------------- 1 | .. currentmodule:: temscript 2 | 3 | .. _instrument: 4 | 5 | The COM interface 6 | ================= 7 | 8 | The methods and classes directly represent the COM objects exposed by the *Scripting* interface. 9 | The interface is described in detail in the scripting manual of your microscope 10 | (usually in the file ``scripting.pdf`` located in the ``C:\Titan\Tem_help\manual`` or 11 | ``C:\Tecnai\tem_help\manual`` directories). 12 | 13 | The manual is your ultimate reference, this documentation will only describe the 14 | python wrapper to the COM interface. 15 | 16 | The COM apartment model will automatically be initialized as ``COINIT_APARTMENTTHREADED`` 17 | (this changed in Version 2.1.0, it was ``COINIT_MULTITHREADED`` before). However, if the 18 | apartment model was already initialized as ``COINIT_MULTITHREADED`` before `temscript` 19 | was imported, `temscript` will also work. If you want a specific apartment model, set the 20 | variable ``sys.coinit_flags`` before importing `temscript`. 21 | 22 | 23 | Enumerations 24 | ^^^^^^^^^^^^ 25 | 26 | Many of the attributes return values from enumerations. These enumerations can be found in the 27 | :mod:`temscript.enums` module. 28 | 29 | .. versionchanged:: 2.0 30 | All methods of the COM interface now directly return the enumeration objects. The constants 31 | from temscript version 1.x are not defined anymore. The numerical values still can be accessed 32 | by querying the corresponding enum, e.g. ``psmSA`` corresponds to ``ProjectionSubMode.SA``. 33 | 34 | Vectors 35 | ^^^^^^^ 36 | 37 | Some object attributes handle with two dimensional vectors (e.g. ``ImageShift``). These 38 | attributes return ``(x, y)`` like tuples and expect iterable objects (``tuple``, 39 | ``list``, ...) with two floats when written (numpy arrays with two entries also work). 40 | 41 | Interface classes 42 | ^^^^^^^^^^^^^^^^^ 43 | 44 | :class:`Instrument` - The entry point... 45 | ---------------------------------------- 46 | 47 | .. function:: GetInstrument() 48 | 49 | Creates a new instance of the :class:`Instrument` class. If your computer 50 | is not the microscope's PC or you don't have the *Scripting* option installed on 51 | your microscope, this method will raise an exception (most likely of the :exc:`OSError` 52 | type). 53 | 54 | .. class:: Instrument 55 | 56 | Top level object representing the microscope. Use the :func:`GetInstrument` 57 | function to create an instance of this class. 58 | 59 | .. attribute:: Gun 60 | 61 | (read) Instance of :class:`Gun` for access to gun related functionalities 62 | 63 | .. attribute:: Illumination 64 | 65 | (read) Instance of :class:`Illumination` for access to illumination 66 | system and condenser related functionalities 67 | 68 | .. attribute:: Projection 69 | 70 | (read) Instance of :class:`Projection` for access to projection 71 | system related functionalities 72 | 73 | .. attribute:: Stage 74 | 75 | (read) Instance of :class:`Stage` for stage control 76 | 77 | .. attribute:: Acquisition 78 | 79 | (read) Instance of :class:`Acquisition` for image acquisition 80 | 81 | .. attribute:: Vacuum 82 | 83 | (read) Instance of :class:`Vacuum` for access to vacuum system related 84 | functionalities 85 | 86 | .. attribute:: InstrumentModeControl 87 | 88 | (read) Instance of :class:`InstrumentModeControl` for TEM/STEM switching. 89 | 90 | .. attribute:: BlankerShutter 91 | 92 | (read) Instance of :class:`BlankerShutter` for blanker control. 93 | 94 | .. attribute:: Configuration 95 | 96 | (read) Instance of :class:`Configuration` for microscope identification. 97 | 98 | .. attribute:: Camera 99 | 100 | (read) Instance of :class:`Camera` for fluscreen / plate camera control. 101 | 102 | .. versionadded:: 2.0 103 | 104 | .. attribute:: AutoNormalizeEnabled 105 | 106 | (read/write) *bool* Enable/Disable autonormalization procedures 107 | 108 | .. method:: NormalizeAll() 109 | 110 | Normalize all the lenses 111 | 112 | .. versionadded:: 2.0 113 | 114 | 115 | :class:`Gun` - Gun stuff 116 | ------------------------ 117 | 118 | .. class:: Gun 119 | 120 | .. attribute:: Tilt 121 | 122 | (read/write) (X,Y) tuple in the range of -1.0 to +1.0 (logical units). 123 | This attribute is inaccessable when the beamblanker (see 124 | :class:`Illumination`) is active. 125 | 126 | .. attribute:: Shift 127 | 128 | (read/write) (X,Y) tuple in the range of -1.0 to +1.0 (logical units). 129 | 130 | .. attribute:: HTState 131 | 132 | (read/write) *HighTensionState* State of accelerator 133 | 134 | .. versionchanged:: 2.0 135 | Returns *HighTensionState* instance instead of integer. 136 | 137 | .. attribute:: HTValue 138 | 139 | (read/write) *float* Current acceleration voltage (Volts) 140 | 141 | .. warning:: 142 | 143 | Be careful when writing this attribute, it allows you to change the high tension. 144 | 145 | .. attribute:: HTMaxValue 146 | 147 | (read) *float* Max. HT Value of the microscope (Volts) 148 | 149 | 150 | :class:`Illumination` - Condenser sytem 151 | --------------------------------------- 152 | 153 | .. class:: Illumination 154 | 155 | The functionality of some methods/attributes depend on the 156 | mode the illumination is in (see the manual for details). 157 | 158 | .. attribute:: Mode 159 | 160 | (read/write) *IlluminationMode* Setting of minicondensor lens 161 | 162 | .. versionchanged:: 2.0 163 | Returns *IlluminationMode* instance instead of integer. 164 | 165 | .. attribute:: DFMode 166 | 167 | (read/write) *DarkFieldMode* Dark field mode. 168 | 169 | .. versionchanged:: 2.0 170 | Returns *DarkFieldMode* instance 171 | 172 | .. attribute:: DarkFieldMode 173 | 174 | (read/write) Alias of :attr:`DFMode` 175 | 176 | .. attribute:: BeamBlanked 177 | 178 | (read/write) *bool* Setting of beam blanker. The beam blanker puts a large current to 179 | the gun tilt coils to blank the beam before it is entering the condenser system. 180 | 181 | .. attribute:: CondenserStigmator 182 | 183 | (read/write) (X,Y) tuple in the range of -1.0 to +1.0 (logical units). 184 | 185 | .. attribute:: SpotSizeIndex 186 | 187 | (read/write) *int* The spot size (1-11). 188 | 189 | .. attribute:: SpotsizeIndex 190 | 191 | (read/write) Alias of :attr:`SpotSizeIndex` 192 | 193 | .. attribute:: Intensity 194 | 195 | (read/write) *float* Value corresponding to the C2-Knob setting, range 196 | between 0.0 to 1.0 (logical units) 197 | 198 | .. attribute:: IntensityZoomEnabled 199 | 200 | (read/write) *bool* Enable intensity zoom 201 | 202 | .. attribute:: IntensityLimitEnabled 203 | 204 | (read/write) *bool* Enable Intensity limit 205 | 206 | .. note:: Reading this property raises an exception (E_UNEXPECTED, "Catastrophic failure") on Titan 1.1 207 | 208 | .. attribute:: Shift 209 | 210 | (read/write) (X,Y) tuple of shift value (Meters). This corresponds to 211 | the *User (Beam) Shift* setting (which is displayed in logical units) in the 212 | *System Status* page. The scaling between the *Meters* and *logical units* 213 | depend on the calibration value stored in the aligment. 214 | 215 | .. attribute:: Tilt 216 | 217 | (read/write) Meaning depends on the setting of :attr:`DFMode` 218 | * in ``DarkFieldMode.CARTESIAN`` mode: (X, Y) tuple of shift value (Radians). 219 | * in ``DarkFieldMode.CONICAL`` mode: (theta, phi) tuple of angles (Radians). 220 | 221 | This corresponds to the *DF Tilt* setting (which is displayed in logical units) in the 222 | *System Status* page. The scaling between the *Radians* and the *logical units* 223 | depend on the calibration value stored in the aligment. 224 | 225 | .. attribute:: RotationCenter 226 | 227 | (read/write) (X,Y) tuple of tilt value (Radians). This corresponds to the 228 | *Rot. Center* setting (which is displayed in logical units) in the 229 | *System Status* page. The scaling between the *Radians* and the *logical units* 230 | depend on the calibration value stored in the aligment. 231 | 232 | .. attribute:: StemMagnification 233 | 234 | (read/write) *float* Magnification in STEM. As the magnification must be 235 | one of the discret values (as displayed on the micrsocope), the value is 236 | rounded to the next available value on write. 237 | 238 | .. note:: 239 | On Titan 1.1, reading this attribute fails, if STEM is not available. See :ref:`restrictions`. 240 | 241 | .. attribute:: StemRotation 242 | 243 | (read/write) *float* Rotation in STEM (radians). 244 | 245 | .. note:: 246 | On Titan 1.1, reading this attribute fails, if STEM is not available. See :ref:`restrictions`. 247 | 248 | .. attribute:: CondenserMode 249 | 250 | (read/write) *CondenserMode* Condenser mode 251 | (Available only on Titan). 252 | 253 | .. versionchanged:: 2.0 254 | Returns *CondenserMode* instance 255 | 256 | .. attribute:: IlluminatedArea 257 | 258 | (read/write) *float* Illuminated area (meters? Is diameter meant? still to check). Requires parallel 259 | condensor mode. 260 | (Available only on Titan and only in `CondenserMode.PARALLEL` mode). 261 | 262 | .. attribute:: ProbeDefocus 263 | 264 | (read/write) *float* Probe defocus (meters) Requires probe condenser mode. 265 | (Available only on Titan and only in `CondenserMode.PROBE` mode). 266 | 267 | .. attribute:: ConvergenceAngle 268 | 269 | (read/write) *float* Convergence angle (radians) Requires probe condenser mode. 270 | (Available only on Titan and only in `CondenserMode.PROBE` mode). 271 | 272 | .. method:: Normalize(mode) 273 | 274 | Normalizes condenser lenses. *norm* specifies what elements to normalize, see *IlluminationNormalization* 275 | 276 | 277 | :class:`Projection` - Projective system 278 | --------------------------------------- 279 | 280 | .. class:: Projection 281 | 282 | Depending on the mode the microscope is in not all properties are 283 | accessable at all times (see scripting manual for details). 284 | 285 | .. attribute:: Mode 286 | 287 | (read/write) *ProjectionMode* Mode 288 | 289 | .. versionchanged:: 2.0 290 | Returns *ProjectionMode* instance instead of integer. 291 | 292 | .. note:: 293 | On Titan 1.1 software changing the mode from IMAGING to DIFFRACTION and back again changes the 294 | magnification. See :ref:`restrictions`. 295 | 296 | .. attribute:: SubMode 297 | 298 | (read) *ProjectionSubMode* SubMode 299 | 300 | .. versionchanged:: 2.0 301 | Returns *ProjectionSubMode* instance instead of integer. 302 | 303 | .. attribute:: SubModeString 304 | 305 | (read) *str* Textual description of :attr:`Submode`. 306 | 307 | .. attribute:: LensProgram 308 | 309 | (read/write) *LensProg* Lens program 310 | 311 | .. versionchanged:: 2.0 312 | Returns *LensProg* instance instead of integer. 313 | 314 | .. attribute:: Magnification 315 | 316 | (read) *float* Magnification as seen be plate camera. 317 | Use :attr:`MagnificationIndex` to change. 318 | 319 | .. note:: 320 | On Titan 1.1 software this property reads 0.0 regardless of used mode. See :ref:`restrictions`. 321 | 322 | .. attribute:: MagnificationIndex 323 | 324 | (read/write) *int* Magnification setting 325 | 326 | .. attribute:: ImageRotation 327 | 328 | (read) *float* Rotation of image/diffraction pattern with respect 329 | to specimen (radians) 330 | 331 | .. attribute:: DetectorShift 332 | 333 | (read/write) *ProjectionDetectorShift* Set shift of diffraction pattern to specified axis. 334 | 335 | .. versionchanged:: 2.0 336 | Returns *ProjectionDetectorShift* instance instead of integer. 337 | 338 | .. attribute:: DetectorShiftMode 339 | 340 | (read/write) *ProjDetectorShiftMode* Shift mode 341 | 342 | .. versionchanged:: 2.0 343 | Returns *ProjDetectorShiftMode* instance instead of integer. 344 | 345 | .. attribute:: Focus 346 | 347 | (read/write) *float* Focus setting relative to focus preset (logical units). 348 | Range -1.0 (underfocus) to +1.0 (overfocus). 349 | 350 | .. attribute:: Defocus 351 | 352 | (read/write) *float* Defocus (meters), relative to defocus set with :func:`ResetDefocus`. 353 | 354 | .. attribute:: ObjectiveExcitation 355 | 356 | (read) *float* Objective lens excitation in percent. 357 | 358 | .. attribute:: CameraLength 359 | 360 | (read) *float* Camera length as seen by plate camera (meters). Use 361 | :attr:`CameraLengthIndex` to change. 362 | 363 | .. attribute:: CameraLengthIndex 364 | 365 | (read/write) *int* Camera length setting 366 | 367 | .. attribute:: ObjectiveStigmator 368 | 369 | (read/write) (X,Y) tuple in the range of -1.0 to +1.0 (logical units). 370 | 371 | .. attribute:: DiffractionStigmator 372 | 373 | (read/write) (X,Y) tuple in the range of -1.0 to +1.0 (logical units). 374 | 375 | .. attribute:: DiffractionShift 376 | 377 | (read/write) (X,Y) tuple of shift value (radians). This corresponds to 378 | the *User Diffraction Shift* setting (which is displayed in logical units) in the 379 | *System Status* page. The scaling between the *radians* and *logical units* 380 | depend on the calibration value stored in the aligment. 381 | 382 | .. attribute:: ImageShift 383 | 384 | (read/write) (X,Y) tuple of shift value (meters). This corresponds to 385 | the *User (Image) Shift* setting (which is displayed in logical units) in the 386 | *System Status* page. The scaling between the *Meters* and *logical units* 387 | depend on the calibration value stored in the aligment. 388 | 389 | .. attribute:: ImageBeamShift 390 | 391 | (read/write) (X,Y) tuple of shift value (meters). Shifts image and while compensating 392 | for the apparent beam shift. 393 | From the manual: Don't intermix :attr:`ImageShift` and :attr:`ImageBeamShift`, reset 394 | one of them ot zero before using the other. 395 | 396 | .. attribute:: ImageBeamTilt 397 | 398 | (read/write) (X,Y) tuple of tilt value. Tilts beam and compensates tilt by diffraction 399 | shift. 400 | 401 | .. attribute:: ProjectionIndex 402 | 403 | (read/write) *int* Corresponds to :attr:`MagnificationIndex` or 404 | :attr:`CameraLengthIndex` depending on mode. 405 | 406 | .. attribute:: SubModeMinIndex 407 | 408 | (read) *int* Smallest projection index of current submode. 409 | 410 | .. attribute:: SubModeMaxIndex 411 | 412 | (read) *int* Largest projection index of current submode. 413 | 414 | .. method:: ResetDefocus() 415 | 416 | Sets the :attr:`Defocus` of the current focus setting to zero (does not 417 | actually change the focus). 418 | 419 | .. method:: ChangeProjectionIndex(steps) 420 | 421 | Changes the current :attr:`ProjectionIndex` by *steps*. 422 | 423 | .. method:: Normalize(norm) 424 | 425 | Normalize projection system. *norm* specifies what elements to normalize, see *ProjectionNormalization* 426 | 427 | 428 | :class:`Stage` - Stage control 429 | ------------------------------ 430 | 431 | .. class:: Stage 432 | 433 | .. attribute:: Status 434 | 435 | (read) *StageStatus* Status of the stage 436 | 437 | .. versionchanged:: 2.0 438 | Returns *StageStatus* instance instead of integer. 439 | 440 | .. attribute:: Position 441 | 442 | (read) Current position of stage. The function returns a ``dict`` 443 | object with the values of the individual axes ('x', 'y', 'z', 'a', and 'b'). 444 | 445 | .. attribute:: Holder 446 | 447 | (read) *StageHolderType* Type of holder 448 | 449 | .. versionchanged:: 2.0 450 | Returns *StageHolderType* instance instead of integer. 451 | 452 | .. method:: AxisData(axis) 453 | 454 | Returns tuple with information about that axis. Returned tuple 455 | is of the form (*min*, *max*, *unit*), where *min* is the minimum 456 | value, *max* the maximum value of the particular axis, and *unit* is 457 | a string containing the unit the axis is measured in (either 'meters' or 458 | 'radians'). The *axis* must be one of the axes ('x', 'y', 'z', 'a', or 'b'). 459 | 460 | .. method:: GoTo(x=None, y=None, z=None, a=None, b=None, speed=None) 461 | 462 | Moves stage to indicated position. Stage is only moved along 463 | the axes that are not ``None``. 464 | 465 | Optionally the keyword *speed* can be given, which allows to set the 466 | speed of the movement. 1.0 correspond to the default speed. 467 | 468 | .. note:: 469 | At least with Titan 1.1 software, moving the stage along multiple axes with *speed* keyword set 470 | fails. Thus movement with *speed* set, must be done along a single axis only. See :ref:`restrictions`. 471 | 472 | .. versionchanged:: 1.0.10 473 | "speed" keyword added. 474 | 475 | .. versionchanged:: 2.0 476 | Internally the ``GoToWithSpeed`` method is used, when the *speed* keyword is given. Previous to version 2.0, 477 | the ``GoToWithSpeed`` method was only used if the *speed* keyword was different from 1.0. 478 | 479 | .. method:: MoveTo(x=None, y=None, z=None, a=None, b=None) 480 | 481 | Moves stage to indicated position. Stage is only moved along 482 | the axes that are not ``None``. In order to avoid pole-piece 483 | touch, the movement is carried out in the following order: 484 | 485 | b->0; a->0; z->Z; (x,y)->(X,Y); a->A; b->B 486 | 487 | .. versionchanged:: 2.0 488 | Invalid keywords raise ValueError (instead of TypeError) 489 | 490 | 491 | Vacuum related classes 492 | ---------------------- 493 | 494 | .. class:: Vacuum 495 | 496 | .. attribute:: Status 497 | 498 | (read) *VacuumStatus* Status of the vacuum system 499 | 500 | .. versionchanged:: 2.0 501 | Returns *VacuumStatus* instance instead of integer. 502 | 503 | .. attribute:: PVPRunning 504 | 505 | (read) *bool* Whether the prevacuum pump is running 506 | 507 | .. attribute:: ColumnValvesOpen 508 | 509 | (read/write) *bool* Status of column valves 510 | 511 | .. attribute:: Gauges 512 | 513 | (read) List of :class:`Gauge` objects 514 | 515 | .. method:: RunBufferCycle() 516 | 517 | Runs a buffer cycle. 518 | 519 | 520 | .. class:: Gauge 521 | 522 | .. attribute:: Name 523 | 524 | (read) *str* Name of the gauge 525 | 526 | .. attribute:: Status 527 | 528 | (read) *GaugeStatus* Status of the gauge 529 | 530 | .. versionchanged:: 2.0 531 | Returns *GaugeStatus* instance instead of integer. 532 | 533 | .. attribute:: Pressure 534 | 535 | (read) *float* Last measured pressure (Pascal) 536 | 537 | .. attribute:: PressureLevel 538 | 539 | (read) *GaugePressureLevel* Level of the pressure 540 | 541 | .. versionchanged:: 2.0 542 | Returns *GaugePressureLevel* instance instead of integer. 543 | 544 | .. method:: Read() 545 | 546 | Read the pressure level. Call this before reading the value 547 | from :attr:`Pressure`. 548 | 549 | 550 | Acquisition related classes 551 | --------------------------- 552 | 553 | .. class:: Acquisition 554 | 555 | .. note:: 556 | 557 | From the manual: 558 | * TIA must be running 559 | * After changing the detector selection in the UI you must 560 | reacquire a new :class:`Instrument` using the :func:`GetInstrument` 561 | function. 562 | * In order for detectors/cameras to be available, they must 563 | be selected in the UI. 564 | 565 | .. attribute:: Cameras 566 | 567 | (read) List of :class:`CCDCamera` objects. 568 | 569 | .. attribute:: Detectors 570 | 571 | (read) List of :class:`STEMDetector` objects. 572 | 573 | .. attribute:: StemAcqParams 574 | 575 | (read/write) *STEMAcqParams* Acquisition parameters for STEM acquisition. 576 | 577 | In the original Scripting interface the STEM acquisition parameters are 578 | read/write on the detectors collection returned by the *Detectors* attribute. 579 | obtained via the list of detectors returned by the Acquisition instance. 580 | 581 | In version 1.X of the temscript adapter, this parameters were set via the STEMDetector 582 | instances itself, however the setting was common to all detectors. 583 | 584 | .. versionadded:: 2.0 585 | 586 | .. method:: AddAcqDevice(device) 587 | 588 | Adds *device* to the list active devices. *device* must be of 589 | type :class:`CCDCamera` or :class:`STEMDetector`. 590 | 591 | .. method:: AddAcqDeviceByName(deviceName) 592 | 593 | Adds device with name *deviceName* to the list active devices. 594 | 595 | .. method:: RemoveAcqDevice(device) 596 | 597 | Removes *device* to the list active devices. *device* must be of 598 | type :class:`CCDCamera` or :class:`STEMDetector`. 599 | 600 | .. method:: RemoveAcqDeviceByName(deviceName) 601 | 602 | Removes device with name *deviceName* to the list active devices. 603 | 604 | .. method:: RemoveAllAcqDevices() 605 | 606 | Clears the list of active devices. 607 | 608 | .. method:: AcquireImages() 609 | 610 | Acquires image from each active device, and returns them as list 611 | of :class:`AcqImage`. 612 | 613 | 614 | .. class:: CCDCamera 615 | 616 | .. attribute:: Info 617 | 618 | Information about the camera as instance of :class:`CCDCameraInfo` 619 | 620 | .. attribute:: AcqParams 621 | 622 | Acquisition parameters of the camera as instance of :class:`CCDAcqParams` 623 | 624 | 625 | .. class:: CCDCameraInfo 626 | 627 | .. attribute:: Name 628 | 629 | (read) *str* Name of CCD camera 630 | 631 | .. attribute:: Height 632 | 633 | (read) *int* Height of camera (pixels) 634 | 635 | .. attribute:: Width 636 | 637 | (read) *int* Width of camera (pixels) 638 | 639 | .. attribute:: PixelSize 640 | 641 | (read) (X, Y)-Tuple with physical pixel size (Manual says nothing about units, seems to be meters) 642 | 643 | .. attribute:: Binnings 644 | 645 | (read) List with supported binning values. 646 | 647 | .. versionchanged:: 2.0 648 | This attribute now returns a list of int (instead of a numpy array). 649 | 650 | .. attribute:: ShutterModes 651 | 652 | (read) List with supported shutter modes (see *AcqShutterMode* enumeration). 653 | 654 | .. versionchanged:: 2.0 655 | This attribute now returns a list of AcqShutterMode (instead of a numpy array). 656 | 657 | .. attribute:: ShutterMode 658 | 659 | (read/write) *AcqShutterMode* Selected shutter mode. 660 | 661 | .. versionchanged:: 2.0 662 | Returns *AcqShutterMode* instance instead of integer. 663 | 664 | 665 | .. class:: CCDAcqParams 666 | 667 | .. attribute:: ImageSize 668 | 669 | (read/write) *AcqImageSize* Camera area used. 670 | 671 | .. versionchanged:: 2.0 672 | Returns *AcqImageSize* instance instead of integer. 673 | 674 | .. attribute:: ExposureTime 675 | 676 | (read/write) *float* Exposure time (seconds) 677 | 678 | .. note:: 679 | On Titan 1.1 software images acquired after setting this property might not be acquired with the new 680 | setting, even though this property reflects the new value when read. See :ref:`restrictions`. 681 | 682 | .. attribute:: Binning 683 | 684 | (read/write) *int* Binning value 685 | 686 | .. note:: 687 | On Titan 1.1 software setting this property also changes the exposure time. See :ref:`restrictions`. 688 | 689 | .. attribute:: ImageCorrection 690 | 691 | (read/write) *AcqImageCorrection* Correction mode. 692 | 693 | .. versionchanged:: 2.0 694 | Returns *AcqImageCorrection* instance instead of integer. 695 | 696 | .. attribute:: ExposureMode 697 | 698 | (read/write) *AcqExposureMode* Exposure mode. 699 | 700 | .. versionchanged:: 2.0 701 | Returns *AcqExposureMode* instance instead of integer. 702 | 703 | .. attribute:: MinPreExposureTime 704 | 705 | (read) *float* Smallest pre exposure time (seconds) 706 | 707 | .. attribute:: MaxPreExposureTime 708 | 709 | (read) *float* Largest pre exposure time (seconds) 710 | 711 | .. attribute:: MinPreExposurePauseTime 712 | 713 | (read) *float* Smallest pre exposure pause time (seconds) 714 | 715 | .. attribute:: MaxPreExposurePauseTime 716 | 717 | (read) *float* Largest pre exposure pause time (seconds) 718 | 719 | .. attribute:: PreExposureTime 720 | 721 | (read/write) *float* pre exposure time (seconds) 722 | 723 | .. attribute:: PreExposurePauseTime 724 | 725 | (read/write) *float* pre exposure pause time (seconds) 726 | 727 | 728 | .. class:: STEMDetector 729 | 730 | .. attribute:: Info 731 | 732 | Information about the detector as instance of :class:`STEMDetectorInfo` 733 | 734 | .. attribute:: AcqParams 735 | 736 | Parameters for STEM acquisition as instance of :class:`STEMAcqParams`. The 737 | acquisition parameters of all STEM detectors are identical, so this attribute 738 | will return the same instance for all detectors. 739 | 740 | .. deprecated:: 2.0 741 | 742 | Use the :attr:`StemAcqParams` attribute of the :class:`Acquisition` to set the parameters 743 | for STEM acqisition. 744 | 745 | 746 | .. class:: STEMDetectorInfo 747 | 748 | .. attribute:: Name 749 | 750 | (read) *str* Name of detector camera 751 | 752 | .. attribute:: Brightness 753 | 754 | (read/write) *float* Brightness setting of the detector. 755 | 756 | .. attribute:: Contrast 757 | 758 | (read/write) *float* Contrast setting of the detector. 759 | 760 | .. attribute:: Binnings 761 | 762 | (read) List with supported binning values. 763 | 764 | .. versionchanged:: 2.0 765 | This attribute now returns a list of int (instead of a numpy array). 766 | 767 | 768 | .. class:: STEMAcqParams 769 | 770 | .. attribute:: ImageSize 771 | 772 | (read/write) *AcqImageSize* Area of scan 773 | 774 | .. versionchanged:: 2.0 775 | Returns *AcqImageSize* instance instead of integer. 776 | 777 | .. attribute:: DwellTime 778 | 779 | (read/write) *float* Dwell time (seconds) 780 | 781 | .. note:: 782 | On Titan 1.1, reading this attribute fails, if STEM is not available. See :ref:`restrictions`. 783 | 784 | .. attribute:: Binning 785 | 786 | (read/write) *int* Binning value 787 | 788 | 789 | .. class:: AcqImage 790 | 791 | .. attribute:: Name 792 | 793 | (read) *unicode* Name of camera/detector 794 | 795 | .. attribute:: Height 796 | 797 | (read) *int* Height of acquired data array (pixels) 798 | 799 | .. attribute:: Width 800 | 801 | (read) *int* Width of acquired data array (pixels) 802 | 803 | .. attribute:: Depth 804 | 805 | (read) *int* Unsure: something like dynamic in bits, but not 806 | correct on our microscope. 807 | 808 | .. attribute:: Array 809 | 810 | (read) *numpy.ndarray* Acquired data as numpy array object. 811 | 812 | 813 | Fluscreen and plate camera 814 | -------------------------- 815 | 816 | .. class:: Camera 817 | 818 | Fluorescent screen and plate camera related methods. 819 | 820 | Since the plate camera is not supported anymore on newer Titans most of the methods 821 | of the Camera class are meaningless. Nevertheless, the attributes :attr:`ScreenCurrent` 822 | :attr:`MainScreen`, and :attr:`IsSmallScreenDown` still are usefull for fluscreen control. 823 | 824 | .. versionadded:: 2.0 825 | 826 | .. attribute:: Stock 827 | 828 | (read) *int* Number of plates still on stock 829 | 830 | .. attribute:: MainScreen 831 | 832 | (read/write) *ScreenPosition* Position of the fluscreen. 833 | 834 | .. attribute:: IsSmallScreenDown 835 | 836 | (read) *bool* Whether the focus screen is down. 837 | 838 | .. attribute:: MeasuredExposureTime 839 | 840 | (read) *float* Measured exposure time (seconds) 841 | 842 | .. attribute:: FilmText 843 | 844 | (read/write) *str* Text on plate. Up to 96 characters long. 845 | 846 | .. attribute:: ManualExposureTime 847 | 848 | (read/write) *float* Exposure time for manual exposures (seconds) 849 | 850 | .. attribute:: PlateuMarker 851 | 852 | (read/write) *bool* 853 | 854 | .. note:: Undocumented property 855 | 856 | .. attribute:: ExposureNumber 857 | 858 | (read/write) *int* Exposure number. The number is given as a 5 digit number 859 | plus 100000 * the ASCII code of one of the characters '0' to '9' or 'A' to 'Z'. 860 | 861 | .. attribute:: Usercode 862 | 863 | (read/write) *str* Three letter user code displayed on plate. 864 | 865 | .. attribute:: ManualExposure 866 | 867 | (read/write) *bool* Whether the `ManualExposureTime` will be used for plate exposure. 868 | 869 | .. attribute:: PlateLabelDataType 870 | 871 | (read/write) *PlateLabelDateFormat* Format of the date displayed on plate 872 | 873 | .. attribute:: ScreenDim 874 | 875 | (read/write) *bool* Whether the computer monitor is dimmed 876 | 877 | .. attribute:: ScreenDimText 878 | 879 | (read/write) *str* Test displayed when the computer monitor is dimmed. 880 | 881 | .. attribute:: ScreenCurrent 882 | 883 | (read) *float* The current measured on the flu screen (Amperes) 884 | 885 | .. method:: TakeExposure() 886 | 887 | Take single plate exposure. 888 | 889 | 890 | Miscellaneous classes 891 | --------------------- 892 | 893 | .. class:: InstrumentModeControl 894 | 895 | .. attribute:: StemAvailabe 896 | 897 | (read) *bool* Quite self decribing attribute 898 | 899 | .. attribute:: InstrumentMode 900 | 901 | (read/write) *InstrumentMode* TEM or STEM mode 902 | 903 | .. versionchanged:: 2.0 904 | Returns *InstrumentMode* instance instead of integer. 905 | 906 | 907 | .. class:: BlankerShutter 908 | 909 | .. attribute:: ShutterOverrideOn 910 | 911 | (read/write) *bool* Overrides shutter control. 912 | 913 | .. warning:: 914 | 915 | From the manual: If this override is on, there is no way to 916 | determine externally, that the override shutter is the active. 917 | So **always** reset this value from script, when finished. 918 | 919 | 920 | .. class:: Configuration 921 | 922 | .. attribute:: ProductFamily 923 | 924 | (read) *ProductFamily* Microscope type 925 | 926 | .. versionchanged:: 2.0 927 | Returns *ProductFamily* instance instead of integer. 928 | 929 | 930 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | REM Command file for Sphinx documentation 4 | 5 | if "%SPHINXBUILD%" == "" ( 6 | set SPHINXBUILD=sphinx-build 7 | ) 8 | set BUILDDIR=_build 9 | set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . 10 | set I18NSPHINXOPTS=%SPHINXOPTS% . 11 | if NOT "%PAPER%" == "" ( 12 | set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% 13 | set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% 14 | ) 15 | 16 | if "%1" == "" goto help 17 | 18 | if "%1" == "help" ( 19 | :help 20 | echo.Please use `make ^` where ^ is one of 21 | echo. html to make standalone HTML files 22 | echo. dirhtml to make HTML files named index.html in directories 23 | echo. singlehtml to make a single large HTML file 24 | echo. pickle to make pickle files 25 | echo. json to make JSON files 26 | echo. htmlhelp to make HTML files and a HTML help project 27 | echo. qthelp to make HTML files and a qthelp project 28 | echo. devhelp to make HTML files and a Devhelp project 29 | echo. epub to make an epub 30 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter 31 | echo. text to make text files 32 | echo. man to make manual pages 33 | echo. texinfo to make Texinfo files 34 | echo. gettext to make PO message catalogs 35 | echo. changes to make an overview over all changed/added/deprecated items 36 | echo. linkcheck to check all external links for integrity 37 | echo. doctest to run all doctests embedded in the documentation if enabled 38 | goto end 39 | ) 40 | 41 | if "%1" == "clean" ( 42 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i 43 | del /q /s %BUILDDIR%\* 44 | goto end 45 | ) 46 | 47 | if "%1" == "html" ( 48 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html 49 | if errorlevel 1 exit /b 1 50 | echo. 51 | echo.Build finished. The HTML pages are in %BUILDDIR%/html. 52 | goto end 53 | ) 54 | 55 | if "%1" == "dirhtml" ( 56 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml 57 | if errorlevel 1 exit /b 1 58 | echo. 59 | echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. 60 | goto end 61 | ) 62 | 63 | if "%1" == "singlehtml" ( 64 | %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml 65 | if errorlevel 1 exit /b 1 66 | echo. 67 | echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. 68 | goto end 69 | ) 70 | 71 | if "%1" == "pickle" ( 72 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle 73 | if errorlevel 1 exit /b 1 74 | echo. 75 | echo.Build finished; now you can process the pickle files. 76 | goto end 77 | ) 78 | 79 | if "%1" == "json" ( 80 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json 81 | if errorlevel 1 exit /b 1 82 | echo. 83 | echo.Build finished; now you can process the JSON files. 84 | goto end 85 | ) 86 | 87 | if "%1" == "htmlhelp" ( 88 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp 89 | if errorlevel 1 exit /b 1 90 | echo. 91 | echo.Build finished; now you can run HTML Help Workshop with the ^ 92 | .hhp project file in %BUILDDIR%/htmlhelp. 93 | goto end 94 | ) 95 | 96 | if "%1" == "qthelp" ( 97 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp 98 | if errorlevel 1 exit /b 1 99 | echo. 100 | echo.Build finished; now you can run "qcollectiongenerator" with the ^ 101 | .qhcp project file in %BUILDDIR%/qthelp, like this: 102 | echo.^> qcollectiongenerator %BUILDDIR%\qthelp\temscript.qhcp 103 | echo.To view the help file: 104 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\temscript.ghc 105 | goto end 106 | ) 107 | 108 | if "%1" == "devhelp" ( 109 | %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp 110 | if errorlevel 1 exit /b 1 111 | echo. 112 | echo.Build finished. 113 | goto end 114 | ) 115 | 116 | if "%1" == "epub" ( 117 | %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub 118 | if errorlevel 1 exit /b 1 119 | echo. 120 | echo.Build finished. The epub file is in %BUILDDIR%/epub. 121 | goto end 122 | ) 123 | 124 | if "%1" == "latex" ( 125 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 126 | if errorlevel 1 exit /b 1 127 | echo. 128 | echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. 129 | goto end 130 | ) 131 | 132 | if "%1" == "text" ( 133 | %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text 134 | if errorlevel 1 exit /b 1 135 | echo. 136 | echo.Build finished. The text files are in %BUILDDIR%/text. 137 | goto end 138 | ) 139 | 140 | if "%1" == "man" ( 141 | %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man 142 | if errorlevel 1 exit /b 1 143 | echo. 144 | echo.Build finished. The manual pages are in %BUILDDIR%/man. 145 | goto end 146 | ) 147 | 148 | if "%1" == "texinfo" ( 149 | %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo 150 | if errorlevel 1 exit /b 1 151 | echo. 152 | echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. 153 | goto end 154 | ) 155 | 156 | if "%1" == "gettext" ( 157 | %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale 158 | if errorlevel 1 exit /b 1 159 | echo. 160 | echo.Build finished. The message catalogs are in %BUILDDIR%/locale. 161 | goto end 162 | ) 163 | 164 | if "%1" == "changes" ( 165 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes 166 | if errorlevel 1 exit /b 1 167 | echo. 168 | echo.The overview file is in %BUILDDIR%/changes. 169 | goto end 170 | ) 171 | 172 | if "%1" == "linkcheck" ( 173 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck 174 | if errorlevel 1 exit /b 1 175 | echo. 176 | echo.Link check complete; look for any errors in the above output ^ 177 | or in %BUILDDIR%/linkcheck/output.txt. 178 | goto end 179 | ) 180 | 181 | if "%1" == "doctest" ( 182 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest 183 | if errorlevel 1 exit /b 1 184 | echo. 185 | echo.Testing of doctests in the sources finished, look at the ^ 186 | results in %BUILDDIR%/doctest/output.txt. 187 | goto end 188 | ) 189 | 190 | :end 191 | -------------------------------------------------------------------------------- /docs/microscope.rst: -------------------------------------------------------------------------------- 1 | .. currentmodule:: temscript 2 | 3 | .. _microscope: 4 | 5 | The Microscope classes 6 | ====================== 7 | 8 | Several classes with a more pythonic interface exists. All these classes implement the same methods as described by 9 | the :class:`BaseMicroscope`. Currently the following implementations exists: 10 | 11 | * Local Microscope via the :class:`Microscope` class. 12 | * Dummy Microscope via the :class:`NullMicroscope` class. 13 | * Remove Microscope via the :class:`RemoteMicroscope` class. 14 | 15 | The BaseMicroscope class 16 | ------------------------ 17 | 18 | .. autoclass:: BaseMicroscope 19 | :members: 20 | 21 | 22 | The Microscope class itself 23 | --------------------------- 24 | 25 | The :class:`Microscope` class provides a interface to the microscope if the script is run locally on the microscope's 26 | computer. See :class:`BaseMicroscope` for a description of its methods. 27 | 28 | .. autoclass:: Microscope 29 | 30 | The RemoteMicroscope class 31 | -------------------------- 32 | 33 | The :class:`RemoteMicroscope` provides the same methods as the :class:`Microscope` class, but connects to a remote 34 | microscope server. 35 | 36 | The temscript server must be running on the microscope PC. See section :ref:`server` for details. 37 | 38 | .. autoclass:: RemoteMicroscope 39 | :members: 40 | 41 | 42 | The NullMicroscope class 43 | ------------------------ 44 | 45 | The :class:`NullMicroscope` is a dummy replacement :class:`Microscope` class, which emulates the 46 | microscope. 47 | 48 | .. autoclass:: NullMicroscope 49 | :members: 50 | -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | sphinx 2 | sphinx_rtd_theme 3 | readthedocs-sphinx-search 4 | numpy 5 | -------------------------------------------------------------------------------- /docs/restrictions.rst: -------------------------------------------------------------------------------- 1 | .. _restrictions: 2 | 3 | Restrictions 4 | ============ 5 | 6 | The restrictions listed here are issues with the scripting interface itself. `temscript` only provides Python bindings 7 | to this scripting interface, thus these issues also occur using `temscript`. As there is no public list of known issues 8 | with the scripting interfaces by FEI or Thermo Fisher Scientific themself, known issues are listed here for the user's 9 | reference. 10 | 11 | * Changing the projection mode from IMAGING to DIFFRACTION and back again changes the magnification in imaging 12 | mode (Titan 1.1). 13 | * :attr:`Projection.Magnification` does not return the actual magnification, but always 0.0 (Titan 1.1) 14 | * Setting the binning value for a CCD camera, changes the exposure time (Titan 1.1 with Gatan US1000 camera). 15 | * Acquisition with changed exposure time with a CCD camera, are not always done with the new exposure time. 16 | * :attr:`Illumination.IntensityLimitEnabled` raises exception when queried (Titan 1.1). 17 | * :meth:`GoTo()` fails if movement is performed along multiple axes with speed keyword specified (internally the 18 | GoToWithSpeed method if the COM interface fails for multiple axes, Titan 1.1) 19 | * Querying the attributes :attr:`STEMAcqParams.DwellTime`, :attr:`Illumination.StemMagnification`, and 20 | :attr:`Illumination.StemRotation` fails, if STEM is not available (Titan 1.1) 21 | * If during a specimen holder exchange no holder is selected (yet), querying :attr:`Stage.Holder` fails (Titan 1.1). 22 | -------------------------------------------------------------------------------- /docs/server.rst: -------------------------------------------------------------------------------- 1 | .. module:: temscript 2 | 3 | .. _server: 4 | 5 | The temscript server 6 | ==================== 7 | 8 | If remote scripting of the microscope is required as provided via the :class:`RemoteMicroscope` class, the temscript 9 | server must run on the microscope PC. The temscript server provides a Web-API to the interface. 10 | 11 | .. warning:: 12 | 13 | The server provides no means of security or authorization control itself. 14 | 15 | Thus it is highly recommended to let the server 16 | only listen to internal networks or at least route it through a reverse proxy, which implements sufficient security. 17 | 18 | Command line 19 | ------------ 20 | 21 | The temscript server is started by the ``temscript-server`` command line script provided with the :mod:`temscript`` 22 | package (obviously :mod:`temscript` must also be installed on the microscope PC). 23 | 24 | .. code-block:: none 25 | 26 | usage: temscript-server [-h] [-p PORT] [--host HOST] [--null] 27 | 28 | optional arguments: 29 | -h, --help show this help message and exit 30 | -p PORT, --port PORT Specify port on which the server is listening 31 | --host HOST Specify host address on which the the server is listening 32 | --null Use NullMicroscope instead of local microscope as backend 33 | 34 | Python command 35 | -------------- 36 | 37 | Alternatively the temscript server can also be run from within Python using :func:`run_server` function. 38 | 39 | .. autofunction:: temscript.run_server 40 | 41 | 42 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = [ 3 | "setuptools>=17", 4 | "wheel" 5 | ] 6 | build-backend = "setuptools.build_meta" 7 | -------------------------------------------------------------------------------- /scripts/run_null_server.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | from temscript.server import run_server 3 | 4 | if __name__ == '__main__': 5 | run_server(['--null']) 6 | -------------------------------------------------------------------------------- /scripts/test_camera_acquisition.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import numpy as np 3 | import matplotlib.pyplot as plt 4 | 5 | from temscript import Microscope, NullMicroscope 6 | 7 | 8 | if __name__ == '__main__': 9 | # for testing on the Titan microscope PC 10 | print("Starting Test...") 11 | 12 | microscope = Microscope() 13 | cameras = microscope.get_cameras() 14 | 15 | for name in cameras.keys(): 16 | print("Testing %s:" % name) 17 | init_param = microscope.get_camera_param(name) 18 | print("\tInitial param:", init_param) 19 | 20 | param = dict(init_param) 21 | param["image_size"] = "FULL" 22 | param["exposure(s)"] = 1.0 23 | param["binning"] = 1 24 | microscope.set_camera_param(name, param) 25 | print() 26 | print("\tFull param:", microscope.get_camera_param(name)) 27 | 28 | acq = microscope.acquire(name) 29 | image = acq[name] 30 | print("\tSize: ", image.shape[1], "x", image.shape[0]) 31 | print("\tMean: ", np.mean(image)) 32 | print("\tStdDev: ", np.std(image)) 33 | vmin = np.percentile(image, 3) 34 | vmax = np.percentile(image, 97) 35 | plt.subplot(141) 36 | plt.imshow(image, interpolation="nearest", cmap="gray", vmin=vmin, vmax=vmax) 37 | plt.title("Full") 38 | plt.colorbar() 39 | 40 | param = dict(init_param) 41 | param["image_size"] = "HALF" 42 | param["exposure(s)"] = 1.0 43 | param["binning"] = 1 44 | microscope.set_camera_param(name, param) 45 | print() 46 | print("\tHalf param:", microscope.get_camera_param(name)) 47 | 48 | acq = microscope.acquire(name) 49 | image = acq[name] 50 | print("\tSize: ", image.shape[1], "x", image.shape[0]) 51 | print("\tMean: ", np.mean(image)) 52 | print("\tStdDev: ", np.std(image)) 53 | vmin = np.percentile(image, 3) 54 | vmax = np.percentile(image, 97) 55 | plt.subplot(142) 56 | plt.imshow(image, interpolation="nearest", cmap="gray", vmin=vmin, vmax=vmax) 57 | plt.title("Half") 58 | plt.colorbar() 59 | 60 | param = dict(init_param) 61 | param["image_size"] = "FULL" 62 | param["exposure(s)"] = 1.0 63 | param["binning"] = 2 64 | microscope.set_camera_param(name, param) 65 | print() 66 | print("\tBinned param:", microscope.get_camera_param(name)) 67 | 68 | acq = microscope.acquire(name) 69 | image = acq[name] 70 | print("\tSize: ", image.shape[1], "x", image.shape[0]) 71 | print("\tMean: ", np.mean(image)) 72 | print("\tStdDev: ", np.std(image)) 73 | vmin = np.percentile(image, 3) 74 | vmax = np.percentile(image, 97) 75 | plt.subplot(143) 76 | plt.imshow(image, interpolation="nearest", cmap="gray", vmin=vmin, vmax=vmax) 77 | plt.title("Binned") 78 | plt.colorbar() 79 | 80 | param = dict(init_param) 81 | param["image_size"] = "FULL" 82 | param["exposure(s)"] = 2.0 83 | param["binning"] = 1 84 | microscope.set_camera_param(name, param) 85 | print() 86 | print("\tLong exposure param:", microscope.get_camera_param(name)) 87 | 88 | acq = microscope.acquire(name) 89 | image = acq[name] 90 | print("\tSize: ", image.shape[1], "x", image.shape[0]) 91 | print("\tMean: ", np.mean(image)) 92 | vmin = np.percentile(image, 3) 93 | vmax = np.percentile(image, 97) 94 | print("\tStdDev: ", np.std(image)) 95 | plt.subplot(144) 96 | plt.imshow(image, interpolation="nearest", cmap="gray", vmin=vmin, vmax=vmax) 97 | print("\tStdDev: ", np.std(image)) 98 | plt.title("Long Exp.") 99 | plt.colorbar() 100 | 101 | print() 102 | plt.suptitle(name) 103 | plt.show() 104 | -------------------------------------------------------------------------------- /scripts/test_instrument.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | from temscript import GetInstrument 3 | 4 | 5 | def test_projection(instrument): 6 | print("Testing projection...") 7 | projection = instrument.Projection 8 | print("Projection.Mode:", projection.Mode) 9 | print("Projection.Focus:", projection.Focus) 10 | print("Projection.Magnification:", projection.Magnification) 11 | print("Projection.MagnificationIndex:", projection.MagnificationIndex) 12 | print("Projection.CameraLengthIndex:", projection.CameraLengthIndex) 13 | print("Projection.ImageShift:", projection.ImageShift) 14 | print("Projection.ImageBeamShift:", projection.ImageBeamShift) 15 | print("Projection.DiffractionShift:", projection.DiffractionShift) 16 | print("Projection.DiffractionStigmator:", projection.DiffractionStigmator) 17 | print("Projection.ObjectiveStigmator:", projection.ObjectiveStigmator) 18 | print("Projection.SubModeString:", projection.SubModeString) 19 | print("Projection.SubMode:", projection.SubMode) 20 | print("Projection.SubModeMinIndex:", projection.SubModeMinIndex) 21 | print("Projection.SubModeMaxIndex:", projection.SubModeMaxIndex) 22 | print("Projection.ObjectiveExcitation:", projection.ObjectiveExcitation) 23 | print("Projection.ProjectionIndex:", projection.ProjectionIndex) 24 | print("Projection.LensProgram:", projection.LensProgram) 25 | print("Projection.ImageRotation:", projection.ImageRotation) 26 | print("Projection.DetectorShift:", projection.DetectorShift) 27 | print("Projection.DetectorShiftMode:", projection.DetectorShiftMode) 28 | print("Projection.ImageBeamTilt:", projection.ImageBeamTilt) 29 | print() 30 | 31 | 32 | def test_acquisition(instrument, do_acquisition=False): 33 | camera_name = None 34 | 35 | print("Testing acquisition...") 36 | acquisition = instrument.Acquisition 37 | cameras = acquisition.Cameras 38 | for n, camera in enumerate(cameras): 39 | print("Acquisition.Camera[%d]:" % n) 40 | info = camera.Info 41 | if not camera_name: 42 | camera_name = info.Name 43 | print("\tInfo.Name:", info.Name) 44 | print("\tInfo.Width:", info.Width) 45 | print("\tInfo.Height:", info.Height) 46 | print("\tInfo.PixelSize:", info.PixelSize) 47 | print("\tInfo.ShutterMode:", info.ShutterMode) 48 | print("\tInfo.ShutterModes:", info.ShutterModes) 49 | print("\tInfo.Binnings:", info.Binnings) 50 | params = camera.AcqParams 51 | print("\tAcqParams.ImageSize:", params.ImageSize) 52 | print("\tAcqParams.ExposureTime:", params.ExposureTime) 53 | print("\tAcqParams.Binning:", params.Binning) 54 | print("\tAcqParams.ImageCorrection:", params.ImageCorrection) 55 | print("\tAcqParams.ExposureMode:", params.ExposureMode) 56 | print("\tAcqParams.MinPreExposureTime:", params.MinPreExposureTime) 57 | print("\tAcqParams.MaxPreExposureTime:", params.MaxPreExposureTime) 58 | print("\tAcqParams.PreExposureTime:", params.PreExposureTime) 59 | print("\tAcqParams.MinPreExposurePauseTime:", params.MinPreExposurePauseTime) 60 | print("\tAcqParams.MaxPreExposurePauseTime:", params.MaxPreExposurePauseTime) 61 | print("\tAcqParams.PreExposurePauseTime:", params.PreExposurePauseTime) 62 | 63 | detectors = acquisition.Detectors 64 | for n, det in enumerate(detectors): 65 | print("Acquisition.Detector[%d]:" % n) 66 | info = det.Info 67 | print("\tInfo.Name:", info.Name) 68 | print("\tInfo.Brightness:", info.Brightness) 69 | print("\tInfo.Contrast:", info.Contrast) 70 | print("\tInfo.Binnings:", info.Binnings) 71 | 72 | params = acquisition.StemAcqParams 73 | print("Acquisition.StemAcqParams.ImageSize:", params.ImageSize) 74 | #Raises exception? 75 | #print("Acquisition.StemAcqParams.DwellTime:", params.DwellTime) 76 | print("Acquisition.StemAcqParams.Binning:", params.Binning) 77 | print() 78 | 79 | if not do_acquisition or not camera_name: 80 | return 81 | 82 | print("Testing image acquisition (%s)" % camera_name) 83 | acquisition.RemoveAllAcqDevices() 84 | acquisition.AddAcqDeviceByName(camera_name) 85 | images = acquisition.AcquireImages() 86 | for n, image in enumerate(images): 87 | print("Acquisition.AcquireImages()[%d]:" % n) 88 | print("\tAcqImage.Name:", image.Name) 89 | print("\tAcqImage.Width:", image.Width) 90 | print("\tAcqImage.Height:", image.Height) 91 | print("\tAcqImage.Depth:", image.Depth) 92 | array = image.Array 93 | print("\tAcqImage.Array.dtype:", array.dtype) 94 | print("\tAcqImage.Array.shape:", array.shape) 95 | print() 96 | 97 | 98 | def test_vacuum(instrument): 99 | print("Testing vacuum...") 100 | vacuum = instrument.Vacuum 101 | print("Vacuum.Status:", vacuum.Status) 102 | print("Vacuum.PVPRunning:", vacuum.PVPRunning) 103 | print("Vacuum.ColumnValvesOpen:", vacuum.ColumnValvesOpen) 104 | for n, gauge in enumerate(vacuum.Gauges): 105 | print("Vacuum.Gauges[%d]:" % n) 106 | gauge.Read() 107 | print("\tGauge.Name:", gauge.Name) 108 | print("\tGauge.Pressure:", gauge.Pressure) 109 | print("\tGauge.Status:", gauge.Status) 110 | print("\tGauge.PressureLevel:", gauge.PressureLevel) 111 | print() 112 | 113 | 114 | def test_stage(instrument, do_move=False): 115 | print("Testing stage...") 116 | stage = instrument.Stage 117 | pos = stage.Position 118 | print("Stage.Status:", stage.Status) 119 | print("Stage.Position:", pos) 120 | print("Stage.Holder:", stage.Holder) 121 | for axis in 'xyzab': 122 | print("Stage.AxisData(%s):" % axis, stage.AxisData(axis)) 123 | print() 124 | 125 | if not do_move: 126 | return 127 | 128 | print("Testing movement") 129 | print("\tStage.Goto(x=1e-6, y=-1e-6)") 130 | stage.GoTo(x=1e-6, y=-1e-6) 131 | print("\tStage.Position:", stage.Position) 132 | print("\tStage.Goto(x=-1e-6, speed=0.5)") 133 | stage.GoTo(x=-1e-6, speed=0.5) 134 | print("\tStage.Position:", stage.Position) 135 | print("\tStage.MoveTo() to original position") 136 | stage.MoveTo(**pos) 137 | print("\tStage.Position:", stage.Position) 138 | print() 139 | 140 | 141 | def test_camera(instrument): 142 | print("Testing camera...") 143 | camera = instrument.Camera 144 | print("Camera.Stock:", camera.Stock) 145 | print("Camera.MainScreen:", camera.MainScreen) 146 | print("Camera.IsSmallScreenDown:", camera.IsSmallScreenDown) 147 | print("Camera.MeasuredExposureTime:", camera.MeasuredExposureTime) 148 | print("Camera.FilmText:", repr(camera.FilmText)) 149 | print("Camera.ManualExposureTime:", camera.ManualExposureTime) 150 | print("Camera.PlateuMarker:", camera.PlateuMarker) 151 | print("Camera.ExposureNumber:", camera.ExposureNumber) 152 | print("Camera.Usercode:", repr(camera.Usercode)) 153 | print("Camera.ManualExposure:", camera.ManualExposure) 154 | print("Camera.PlateLabelDataType:", camera.PlateLabelDataType) 155 | print("Camera.ScreenDim:", camera.ScreenDim) 156 | print("Camera.ScreenDimText:", repr(camera.ScreenDimText)) 157 | print("Camera.ScreenCurrent:", camera.ScreenCurrent) 158 | print() 159 | 160 | 161 | def test_illumination(instrument): 162 | print("Testing illumination...") 163 | illum = instrument.Illumination 164 | print("Illumination.Mode:", illum.Mode) 165 | print("Illumination.SpotsizeIndex:", illum.SpotsizeIndex) 166 | print("Illumination.Intensity:", illum.Intensity) 167 | print("Illumination.IntensityZoomEnabled:", illum.IntensityZoomEnabled) 168 | #Critical error? 169 | #print("Illumination.IntensityLimitEnabled:", illum.IntensityLimitEnabled) 170 | print("Illumination.Shift:", illum.Shift) 171 | print("Illumination.Tilt:", illum.Tilt) 172 | print("Illumination.RotationCenter:", illum.RotationCenter) 173 | print("Illumination.CondenserStigmator:", illum.CondenserStigmator) 174 | print("Illumination.DFMode:", illum.DFMode) 175 | print("Illumination.CondenserMode:", illum.CondenserMode) 176 | print("Illumination.IlluminatedArea:", illum.IlluminatedArea) 177 | print("Illumination.ProbeDefocus:", illum.ProbeDefocus) 178 | print("Illumination.ConvergenceAngle:", illum.ConvergenceAngle) 179 | # Only in STEM mode? 180 | #print("Illumination.StemMagnification:", illum.StemMagnification) 181 | #print("Illumination.StemRotation:", illum.StemRotation) 182 | print() 183 | 184 | 185 | def test_gun(instrument): 186 | print("Testing gun...") 187 | gun = instrument.Gun 188 | print("Gun.HTState:", gun.HTState) 189 | print("Gun.HTValue:", gun.HTValue) 190 | print("Gun.HTMaxValue:", gun.HTMaxValue) 191 | print("Gun.Shift:", gun.Shift) 192 | print("Gun.Tilt:", gun.Tilt) 193 | print() 194 | 195 | 196 | def test_blankershutter(instrument): 197 | print("Testing blanker/shutter...") 198 | bs = instrument.BlankerShutter 199 | print("BlankerShutter.ShutterOverrideOn:", bs.ShutterOverrideOn) 200 | print() 201 | 202 | 203 | def test_instrument_mode_control(instrument): 204 | print("Testing instrument mode control...") 205 | ctrl = instrument.InstrumentModeControl 206 | print("InstrumentModeControl.StemAvailable:", ctrl.StemAvailable) 207 | print("InstrumentModeControl.InstrumentMode:", ctrl.InstrumentMode) 208 | print() 209 | 210 | 211 | def test_configuration(instrument): 212 | print("Testing configuration...") 213 | config = instrument.Configuration 214 | print("Configuration.ProductFamily:", config.ProductFamily) 215 | print() 216 | 217 | 218 | if __name__ == '__main__': 219 | # for testing on the Titan microscope PC 220 | print("Starting Test...") 221 | 222 | full_test = False 223 | instrument = GetInstrument() 224 | test_projection(instrument) 225 | test_acquisition(instrument, do_acquisition=full_test) 226 | test_vacuum(instrument) 227 | test_stage(instrument, do_move=full_test) 228 | test_camera(instrument) 229 | test_illumination(instrument) 230 | test_gun(instrument) 231 | test_blankershutter(instrument) 232 | test_instrument_mode_control(instrument) 233 | test_configuration(instrument) 234 | 235 | -------------------------------------------------------------------------------- /scripts/test_json_encoder.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | from temscript.remote_microscope import ExtendedJsonEncoder 4 | from temscript.enums import * 5 | 6 | print("RequestJsonEncoder:") 7 | reqenc = ExtendedJsonEncoder() 8 | 9 | print("\tint:", reqenc.encode(123)) 10 | print("\tnp.int64:", reqenc.encode(np.int64(123))) 11 | print("\tnp.float32:", reqenc.encode(np.float32(123.45))) 12 | print("\ttuple:", reqenc.encode((1, 2, 3))) 13 | print("\tlist:", reqenc.encode([1, 2, 3])) 14 | print("\tgenerator:", reqenc.encode(range(3))) 15 | print("\tnp.array:", reqenc.encode(np.arange(3))) 16 | print("\tStageStatus.MOVING:", reqenc.encode(StageStatus.MOVING)) 17 | 18 | -------------------------------------------------------------------------------- /scripts/test_microscope.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | from time import sleep 3 | from argparse import ArgumentParser 4 | 5 | from temscript import Microscope, NullMicroscope, RemoteMicroscope 6 | 7 | 8 | parser = ArgumentParser() 9 | parser.add_argument("--null", action='store_true', default=False, help="Use NullMicroscope") 10 | parser.add_argument("--remote", type=str, default='', help="Use RemoteMicroscope with given hostname:port") 11 | parser.add_argument("--stage", action='store_true', default=False, help="Test stage movement (defaults to false)") 12 | parser.add_argument("--ccd", action='store_true', default=False, help="Test CCD acquisition (defaults to false)") 13 | parser.add_argument("--all", action='store_true', default=False, help="Perform all optional tests (defaults to false)") 14 | parser.add_argument("--noshow", action='store_true', default=False, help="Don't show anything on UI (only stdout)") 15 | args = parser.parse_args() 16 | 17 | if args.null: 18 | print("Starting test of NullMicroscope()") 19 | microscope = NullMicroscope() 20 | elif args.remote: 21 | address = args.remote.split(":", 2) 22 | if len(address) > 1: 23 | address = (address[0], int(address[1])) 24 | else: 25 | address = (address[0], 8080) 26 | print("Starting test of RemoteMicroscope(%s:%d)" % address) 27 | microscope = RemoteMicroscope(address) 28 | else: 29 | print("Starting test of local Microscope()") 30 | microscope = Microscope() 31 | 32 | print("Microscope.get_family():", microscope.get_family()) 33 | print("Microscope.get_microscope_id():", microscope.get_microscope_id()) 34 | print("Microscope.get_version():", microscope.get_version()) 35 | print("Microscope.get_voltage():", microscope.get_voltage()) 36 | print("Microscope.get_vacuum():", microscope.get_vacuum()) 37 | print("Microscope.get_column_valves_open():", microscope.get_column_valves_open()) 38 | print("Microscope.get_stage_holder():", microscope.get_stage_holder()) 39 | print("Microscope.get_stage_status():", microscope.get_stage_status()) 40 | print("Microscope.get_stage_limits():", microscope.get_stage_limits()) 41 | print("Microscope.get_stage_position():", microscope.get_stage_position()) 42 | 43 | cameras = microscope.get_cameras() 44 | print("Microscope.get_cameras():", cameras) 45 | for name in cameras.keys(): 46 | print("Microscope.get_camera_param(%s):" % name, microscope.get_camera_param(name)) 47 | detectors = microscope.get_stem_detectors() 48 | print("Microscope.get_stem_detectors():", detectors) 49 | for name in detectors.keys(): 50 | print("Microscope.get_stem_detector_param(%s):" % name, microscope.get_stem_detector_param(name)) 51 | 52 | # TODO: fails if not STEM 53 | # print("Microscope.get_stem_acquisition_param():", microscope.get_stem_acquisition_param()) 54 | 55 | # Test invalid camera 56 | try: 57 | microscope.get_camera_param('ThereIsNoCameraWithThisName') 58 | except KeyError: 59 | print("Microscope.get_camera_param() fails with KeyError: YES") 60 | else: 61 | print("Microscope.get_camera_param() fails with KeyError: NO") 62 | 63 | print("Microscope.get_image_shift():", microscope.get_image_shift()) 64 | print("Microscope.get_beam_shift():", microscope.get_beam_shift()) 65 | print("Microscope.get_beam_tilt():", microscope.get_beam_tilt()) 66 | print("Microscope.get_projection_sub_mode():", microscope.get_projection_sub_mode()) 67 | print("Microscope.get_projection_mode():", microscope.get_projection_mode()) 68 | print("Microscope.get_projection_mode_string():", microscope.get_projection_mode_string()) 69 | print("Microscope.get_magnification_index():", microscope.get_magnification_index()) 70 | print("Microscope.get_indicated_camera_length():", microscope.get_indicated_camera_length()) 71 | print("Microscope.get_indicated_magnification():", microscope.get_indicated_magnification()) 72 | print("Microscope.get_defocus():", microscope.get_defocus()) 73 | print("Microscope.get_objective_excitation():", microscope.get_objective_excitation()) 74 | print("Microscope.get_intensity():", microscope.get_intensity()) 75 | print("Microscope.get_objective_stigmator():", microscope.get_objective_stigmator()) 76 | print("Microscope.get_condenser_stigmator():", microscope.get_condenser_stigmator()) 77 | print("Microscope.get_diffraction_shift():", microscope.get_diffraction_shift()) 78 | print("Microscope.get_intensity():", microscope.get_intensity()) 79 | print("Microscope.get_screen_current():", microscope.get_screen_current()) 80 | print("Microscope.get_screen_position():", microscope.get_screen_position()) 81 | print("Microscope.get_illumination_mode():", microscope.get_illumination_mode()) 82 | print("Microscope.get_spot_size_index():", microscope.get_spot_size_index()) 83 | print("Microscope.get_dark_field_mode():", microscope.get_dark_field_mode()) 84 | print("Microscope.get_beam_blanked():", microscope.get_beam_blanked()) 85 | print("Microscope.is_stem_available():", microscope.is_stem_available()) 86 | print("Microscope.get_instrument_mode():", microscope.get_instrument_mode()) 87 | 88 | print("Microscope.get_condenser_mode():", microscope.get_condenser_mode()) 89 | print("Microscope.get_convergence_angle():", microscope.get_convergence_angle()) 90 | print("Microscope.get_probe_defocus():", microscope.get_probe_defocus()) 91 | print("Microscope.get_illuminated_area():", microscope.get_illuminated_area()) 92 | 93 | # TODO: fails if not STEM 94 | # print("Microscope.get_stem_magnification():", microscope.get_stem_magnification()) 95 | # print("Microscope.get_stem_rotation():", microscope.get_stem_rotation()) 96 | 97 | print("Microscope.get_state():", microscope.get_state()) 98 | 99 | if args.stage or args.all: 100 | print("Testing stage movement:") 101 | 102 | pos = microscope.get_stage_position() 103 | new_x = 10e-6 if pos['x'] < 0 else -10e-6 104 | microscope.set_stage_position(x=new_x) 105 | for n in range(5): 106 | print("\tstatus=%s, position=%s" % (microscope.get_stage_status(), microscope.get_stage_position())) 107 | sleep(0.1) 108 | 109 | pos = microscope.get_stage_position() 110 | new_y = 10e-6 if pos['y'] < 0 else -10e-6 111 | new_x = 10e-6 if pos['x'] < 0 else -10e-6 112 | microscope.set_stage_position({'y': new_y}, x=new_x) 113 | for n in range(5): 114 | print("\tstatus=%s, position=%s" % (microscope.get_stage_status(), microscope.get_stage_position())) 115 | sleep(0.1) 116 | 117 | pos = microscope.get_stage_position() 118 | new_y = 10e-6 if pos['y'] < 0 else -10e-6 119 | microscope.set_stage_position(y=new_y, speed=0.5) 120 | for n in range(5): 121 | print("\tstatus=%s, position=%s" % (microscope.get_stage_status(), microscope.get_stage_position())) 122 | sleep(0.1) 123 | 124 | if cameras and (args.ccd or args.all): 125 | ccd = list(cameras.keys())[0] 126 | print("Testing camera '%s'" % ccd) 127 | 128 | param = microscope.get_camera_param(ccd) 129 | print("\tinitial camera_param(%s):" % ccd, param) 130 | exposure = 1.0 if param["exposure(s)"] != 1.0 else 2.0 131 | microscope.set_camera_param(ccd, {"exposure(s)": exposure}) 132 | param = microscope.get_camera_param(ccd) 133 | print("\tupdated camera_param(%s):" % ccd, param) 134 | 135 | print("\tacquiring image...") 136 | images = microscope.acquire(ccd) 137 | 138 | print("\t\tshape:", images[ccd].shape) 139 | print("\t\tdtype:", images[ccd].dtype) 140 | 141 | if not args.noshow: 142 | import matplotlib.pyplot as plt 143 | plt.imshow(images[ccd], cmap="gray") 144 | plt.show() 145 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | try: 3 | from setuptools import setup 4 | except ImportError: 5 | from distutils.core import setup 6 | 7 | # Long description 8 | with open("README.md", "r", encoding="utf-8") as fp: 9 | long_description = fp.read() 10 | 11 | # Read version 12 | with open("temscript/version.py") as fp: 13 | exec(fp.read()) 14 | 15 | setup(name='temscript', 16 | version=__version__, 17 | description='TEM Scripting adapter for FEI microscopes', 18 | author='Tore Niermann', 19 | author_email='tore.niermann@tu-berlin.de', 20 | long_description=long_description, 21 | long_description_content_type='text/markdown', 22 | packages=['temscript'], 23 | platforms=['any'], 24 | license="BSD 3-Clause License", 25 | python_requires='>=3.4', 26 | classifiers=[ 27 | 'Development Status :: 5 - Production/Stable', 28 | 'Intended Audience :: Science/Research', 29 | 'Intended Audience :: Developers', 30 | 'Operating System :: OS Independent', 31 | 'Programming Language :: Python :: 3', 32 | 'Topic :: Scientific/Engineering', 33 | 'Topic :: Software Development :: Libraries', 34 | 'License :: OSI Approved :: BSD License', 35 | 'Operating System :: OS Independent' 36 | ], 37 | install_requires=['numpy'], 38 | entry_points={'console_scripts': ['temscript-server = temscript.server:run_server']}, 39 | url="https://github.com/niermann/temscript", 40 | project_urls={ 41 | "Source": "https://github.com/niermann/temscript", 42 | 'Documentation': "https://temscript.readthedocs.io/" 43 | } 44 | ) 45 | -------------------------------------------------------------------------------- /temscript/__init__.py: -------------------------------------------------------------------------------- 1 | from .version import __version__ as version 2 | from .enums import * 3 | from .instrument import * 4 | from .base_microscope import BaseMicroscope 5 | from .microscope import Microscope 6 | from .null_microscope import NullMicroscope 7 | from .remote_microscope import RemoteMicroscope 8 | from .server import run_server 9 | 10 | -------------------------------------------------------------------------------- /temscript/_com.py: -------------------------------------------------------------------------------- 1 | import ctypes 2 | from uuid import UUID 3 | from enum import IntEnum 4 | 5 | __all__ = 'UUID', 'CLSCTX_ALL', 'IUnknown', 'co_create_instance', 'SafeArray', 'BStr', 'Variant', 'VARIANT', 'VariantType' 6 | 7 | # COM constants 8 | COINIT_MULTITHREADED = 0 9 | COINIT_APARTMENTTHREADED = 2 10 | CLSCTX_ALL = 0x17 11 | 12 | 13 | class IUnknown: 14 | """Base class for COM interface classes, adapter for IUnknown methods""" 15 | __slots__ = '_ptr' 16 | 17 | IID = UUID('00000000-0000-0000-C000-000000000046') 18 | 19 | QUERYINTERFACE_METHOD = ctypes.WINFUNCTYPE(ctypes.HRESULT, ctypes.c_char_p, ctypes.c_void_p)(0, "QueryInterface") 20 | ADDREF_METHOD = ctypes.WINFUNCTYPE(ctypes.c_int)(1, "AddRef") 21 | RELEASE_METHOD = ctypes.WINFUNCTYPE(ctypes.c_int)(2, "Release") 22 | 23 | def __del__(self): 24 | if self._ptr: 25 | IUnknown.RELEASE_METHOD(self._ptr) 26 | self._ptr = None 27 | 28 | def __init__(self, value=None, adopt_reference=False): 29 | self._ptr = ctypes.c_void_p() 30 | self.reset(value=value, adopt_reference=adopt_reference) 31 | 32 | def reset(self, value=None, adopt_reference=False): 33 | if value is None: 34 | value = ctypes.c_void_p() 35 | elif isinstance(value, ctypes._SimpleCData): 36 | value = ctypes.c_void_p(value.value) 37 | elif isinstance(value, IUnknown): 38 | value = ctypes.c_void_p(value.get().value) 39 | else: 40 | value = ctypes.c_void_p(int(value)) 41 | 42 | if value and not adopt_reference: 43 | IUnknown.ADDREF_METHOD(value) 44 | 45 | old = self._ptr 46 | self._ptr = value 47 | 48 | if old: 49 | IUnknown.RELEASE_METHOD(old) 50 | 51 | def query_interface(self, interface, iid=None): 52 | if not self._ptr: 53 | raise ValueError("Calling method of NULL pointer.") 54 | if iid is None: 55 | iid = interface.IID 56 | if not isinstance(iid, UUID): 57 | iid = UUID(iid) 58 | 59 | obj = interface() 60 | IUnknown.QUERYINTERFACE_METHOD(self._ptr, iid.bytes_le, obj.byref()) 61 | 62 | return obj 63 | 64 | def get(self): 65 | return self._ptr 66 | 67 | def __bool__(self): 68 | return bool(self._ptr) 69 | 70 | def byref(self): 71 | return ctypes.byref(self._ptr) 72 | 73 | 74 | def co_create_instance(clsid, clsctx, interface=None, iid=None): 75 | if interface is None: 76 | interface = IUnknown 77 | if not isinstance(clsid, UUID): 78 | clsid = UUID(clsid) 79 | if iid is None: 80 | iid = interface.IID 81 | if not isinstance(iid, UUID): 82 | iid = UUID(iid) 83 | 84 | obj = interface() 85 | _ole32.CoCreateInstance(clsid.bytes_le, None, ctypes.c_uint(clsctx), iid.bytes_le, obj.byref()) 86 | 87 | return obj 88 | 89 | 90 | class BStr: 91 | __slots__ = '_ptr' 92 | 93 | def __del__(self): 94 | if self._ptr: 95 | _oleauto.SysFreeString(self._ptr) 96 | 97 | def __init__(self, text=None): 98 | if text is not None: 99 | self._ptr = ctypes.c_wchar_p(_oleauto.SysAllocString(ctypes.c_wchar_p(text))) 100 | if not self._ptr: 101 | raise ValueError("SysAllocString returned NULL.") 102 | else: 103 | self._ptr = ctypes.c_wchar_p() 104 | 105 | def __len__(self): 106 | return _oleauto.SysStringLen(self._ptr) 107 | 108 | def __str__(self): 109 | return self.value 110 | 111 | def byref(self): 112 | return ctypes.byref(self._ptr) 113 | 114 | def get(self): 115 | return self._ptr 116 | 117 | @property 118 | def value(self): 119 | return str(self._ptr.value) 120 | 121 | 122 | class VariantType(IntEnum): 123 | EMPTY = 0 124 | NULL = 1 125 | I2 = 2 # iVal 126 | I4 = 3 # lVal 127 | R4 = 4 # fltVal 128 | R8 = 5 # dblVal 129 | BSTR = 8 # bstr 130 | BOOL = 11 # bool(uiVal) 131 | I1 = 16 # cVal 132 | UI1 = 17 # bVal 133 | UI2 = 18 # uiVal 134 | UI4 = 19 # ulVal 135 | I8 = 20 # llVal 136 | UI8 = 21 # ullVal 137 | INT = 22 # intVal 138 | UINT = 23 # uintVal 139 | SAFEARRAY = 27 # TODO 140 | 141 | 142 | class VARIANT(ctypes.Structure): 143 | class _ValueUnion(ctypes.Union): 144 | _fields_ = [('llVal', ctypes.c_longlong), 145 | ('ullVal', ctypes.c_ulonglong), 146 | ('lVal', ctypes.c_long), 147 | ('ulVal', ctypes.c_ulong), 148 | ('iVal', ctypes.c_short), 149 | ('uiVal', ctypes.c_ushort), 150 | ('intVal', ctypes.c_int), 151 | ('uintVal', ctypes.c_uint), 152 | ('fltVal', ctypes.c_float), 153 | ('dblVal', ctypes.c_double), 154 | ('cVal', ctypes.c_byte), 155 | ('bVal', ctypes.c_ubyte), 156 | ('bstr', ctypes.c_wchar_p)] 157 | 158 | _anonymous_ = ('value',) 159 | _fields_ = [('vt', ctypes.c_ushort), 160 | ('wReserved1', ctypes.c_ushort), 161 | ('wReserved2', ctypes.c_ushort), 162 | ('wReserved3', ctypes.c_ushort), 163 | ('value', _ValueUnion)] 164 | 165 | 166 | class Variant: 167 | __slots__ = '_variant' 168 | 169 | def __del__(self): 170 | _oleauto.VariantClear(ctypes.byref(self._variant)) 171 | 172 | def __init__(self, value=None, vartype=None): 173 | self._variant = VARIANT() 174 | _oleauto.VariantInit(ctypes.byref(self._variant)) 175 | 176 | # Determine variant type if needed 177 | if vartype is not None: 178 | vartype = VariantType(vartype) 179 | elif value is None: 180 | vartype = VariantType.NULL 181 | elif isinstance(value, int): 182 | vartype = VariantType.I4 # TODO: Check range 183 | elif isinstance(value, float): 184 | vartype = VariantType.R8 # TODO: Check range 185 | elif isinstance(value, bool): 186 | vartype = VariantType.BOOL 187 | else: 188 | raise TypeError("Unsupported value type: %s" % type(value).__name__) 189 | 190 | # Set value 191 | if (vartype == VariantType.EMPTY) or (vartype == VariantType.NULL): 192 | pass 193 | elif vartype == VariantType.I2: 194 | self._variant.iVal = int(value) 195 | elif vartype == VariantType.I4: 196 | self._variant.lVal = int(value) 197 | elif vartype == VariantType.R4: 198 | self._variant.fltVal = float(value) 199 | elif vartype == VariantType.R8: 200 | self._variant.dblVal = float(value) 201 | elif vartype == VariantType.BOOL: 202 | self._variant.uiVal = 0xffff if value else 0x0000 203 | elif vartype == VariantType.I1: 204 | self._variant.cVal = int(value) 205 | elif vartype == VariantType.UI1: 206 | self._variant.bVal = int(value) 207 | elif vartype == VariantType.UI2: 208 | self._variant.uiVal = int(value) 209 | elif vartype == VariantType.UI4: 210 | self._variant.ulVal = int(value) 211 | elif vartype == VariantType.I8: 212 | self._variant.llVal = int(value) 213 | elif vartype == VariantType.UI8: 214 | self._variant.ullVal = int(value) 215 | elif vartype == VariantType.INT: 216 | self._variant.intVal = int(value) 217 | elif vartype == VariantType.UINT: 218 | self._variant.uintVal = int(value) 219 | else: 220 | raise ValueError("Unsupported variant type: %s" % vartype) 221 | self._variant.vt = vartype 222 | 223 | def byref(self): 224 | return ctypes.byref(self._variant) 225 | 226 | def get(self): 227 | return self._variant 228 | 229 | @property 230 | def vartype(self): 231 | return VariantType(self._variant.vt) 232 | 233 | @property 234 | def value(self): 235 | t = self.vartype 236 | if t == VariantType.NULL: 237 | return None 238 | elif t == VariantType.I2: 239 | return self._variant.iVal 240 | elif t == VariantType.I4: 241 | return self._variant.lVal 242 | elif t == VariantType.R4: 243 | return self._variant.fltVal 244 | elif t == VariantType.R8: 245 | return self._variant.dblVal 246 | elif t == VariantType.BOOL: 247 | return bool(self._variant.uiVal) 248 | elif t == VariantType.I1: 249 | return self._variant.cVal 250 | elif t == VariantType.UI1: 251 | return self._variant.bVal 252 | elif t == VariantType.UI2: 253 | return self._variant.uiVal 254 | elif t == VariantType.UI4: 255 | return self._variant.ulVal 256 | elif t == VariantType.I8: 257 | return self._variant.llVal 258 | elif t == VariantType.UI8: 259 | return self._variant.ullVal 260 | elif t == VariantType.INT: 261 | return self._variant.intVal 262 | elif t == VariantType.UINT: 263 | return self._variant.uintVal 264 | elif t == VariantType.BSTR: 265 | return self._variant.bstr 266 | else: 267 | raise ValueError("Unexpected Variant type") 268 | 269 | 270 | class SafeArray: 271 | __slots__ = '_ptr' 272 | 273 | CTYPE_FOR_VARTYPE = { 274 | VariantType.I1: ctypes.c_byte, 275 | VariantType.I2: ctypes.c_short, 276 | VariantType.I4: ctypes.c_long, # long is always 4 byte in MSVC 277 | VariantType.I8: ctypes.c_longlong, 278 | VariantType.UI1: ctypes.c_ubyte, 279 | VariantType.UI2: ctypes.c_ushort, 280 | VariantType.UI4: ctypes.c_ulong, # long is always 4 byte in MSVC 281 | VariantType.UI8: ctypes.c_ulonglong, 282 | VariantType.R4: ctypes.c_float, 283 | VariantType.R8: ctypes.c_double, 284 | VariantType.INT: ctypes.c_int, 285 | VariantType.UINT: ctypes.c_uint 286 | } 287 | 288 | def __del__(self): 289 | _oleauto.SafeArrayDestroy(self._ptr) 290 | 291 | def __init__(self): 292 | self._ptr = ctypes.c_void_p() 293 | 294 | def byref(self): 295 | return ctypes.byref(self._ptr) 296 | 297 | def get(self): 298 | return self._ptr 299 | 300 | def get_vartype(self): 301 | vt = ctypes.c_int() 302 | if _oleauto.SafeArrayGetVartype(self._ptr, ctypes.byref(vt)) < 0: 303 | raise ValueError("Error retrieving vartype from SafeArray.") 304 | return VariantType(vt.value) 305 | 306 | def get_ctype(self): 307 | return SafeArray.CTYPE_FOR_VARTYPE[self.get_vartype()] 308 | 309 | def get_dim(self): 310 | return _oleauto.SafeArrayGetDim(self._ptr) 311 | 312 | def get_lower_bound(self, axis): 313 | lower = ctypes.c_long(-1) 314 | if _oleauto.SafeArrayGetLBound(self._ptr, ctypes.c_int(axis + 1), ctypes.byref(lower)) < 0: 315 | raise ValueError("Error retrieving lower bound from SafeArray.") 316 | return lower.value 317 | 318 | def get_upper_bound(self, axis): 319 | upper = ctypes.c_long(-1) 320 | if _oleauto.SafeArrayGetUBound(self._ptr, ctypes.c_int(axis + 1), ctypes.byref(upper)) < 0: 321 | raise ValueError("Error retrieving lower bound from SafeArray.") 322 | return upper.value 323 | 324 | @staticmethod 325 | def _forward(arg): 326 | return arg 327 | 328 | def as_list(self, cast=None): 329 | if cast is None: 330 | cast = SafeArray._forward 331 | 332 | ndim = self.get_dim() 333 | size = 1 334 | for n in range(ndim): 335 | lower = self.get_lower_bound(n) 336 | upper = self.get_upper_bound(n) 337 | size *= upper - lower + 1 338 | 339 | ct = self.get_ctype() 340 | ptr = ctypes.POINTER(ct)() 341 | if _oleauto.SafeArrayAccessData(self._ptr, ctypes.byref(ptr)) < 0: 342 | raise ValueError("Error accessing SafeArray's data.") 343 | try: 344 | result = [cast(ptr[n]) for n in range(size)] 345 | finally: 346 | _oleauto.SafeArrayUnaccessData(self._ptr) 347 | 348 | return result 349 | 350 | def as_array(self): 351 | import numpy as np 352 | 353 | ndim = self.get_dim() 354 | shape = [] 355 | for n in range(ndim): 356 | lower = self.get_lower_bound(ndim - n - 1) 357 | upper = self.get_upper_bound(ndim - n - 1) 358 | shape.append(upper - lower + 1) 359 | shape = tuple(shape) 360 | 361 | ct = self.get_ctype() 362 | ptr = ctypes.POINTER(ct)() 363 | if _oleauto.SafeArrayAccessData(self._ptr, ctypes.byref(ptr)) < 0: 364 | raise ValueError("Error accessing SafeArray's data.") 365 | try: 366 | result = np.ctypeslib.as_array(ptr, shape=shape).copy() 367 | finally: 368 | _oleauto.SafeArrayUnaccessData(self._ptr) 369 | 370 | return result 371 | 372 | 373 | _ole32 = ctypes.oledll.ole32 374 | _oleauto = ctypes.windll.oleaut32 375 | 376 | def initialize_com(flags=None): 377 | RPC_E_CHANGED_MODE = -2147417850 378 | 379 | # The comtypes package allows to specify the apartment model by 380 | # setting sys.coinit_flags in advance. We copy this behavior. 381 | if flags is None: 382 | import sys 383 | flags = getattr(sys, "coinit_flags", None) 384 | 385 | if flags is None: 386 | # If we don't have explicit flags, try STA first and fall back to MTA 387 | # if COM is already initialized to MTA. 388 | try: 389 | _ole32.CoInitializeEx(None, ctypes.c_int(COINIT_APARTMENTTHREADED)) 390 | except WindowsError as exc: 391 | if exc.winerror == RPC_E_CHANGED_MODE: 392 | _ole32.CoInitializeEx(None, ctypes.c_int(COINIT_MULTITHREADED)) 393 | else: 394 | # Otherwise, use the apartment model we got 395 | _ole32.CoInitializeEx(None, flags) 396 | 397 | initialize_com() 398 | -------------------------------------------------------------------------------- /temscript/_instrument_com.py: -------------------------------------------------------------------------------- 1 | import ctypes 2 | from .enums import * 3 | from ._com import * 4 | 5 | 6 | __all__ = ('GetInstrument', 'Projection', 'CCDCameraInfo', 'CCDAcqParams', 'CCDCamera', 7 | 'STEMDetectorInfo', 'STEMAcqParams', 'STEMDetector', 'AcqImage', 'Acquisition', 8 | 'Gauge', 'Vacuum', 'Stage', 'Camera', 'Illumination', 'Gun', 'BlankerShutter', 9 | 'InstrumentModeControl', 'Configuration', 'Instrument') 10 | 11 | 12 | class BaseProperty: 13 | __slots__ = '_get_index', '_put_index', '_name' 14 | 15 | def __init__(self, get_index=None, put_index=None): 16 | self._get_index = get_index 17 | self._put_index = put_index 18 | self._name = '' 19 | 20 | def __set_name__(self, owner, name): 21 | self._name = " '%s'" % name 22 | 23 | 24 | class LongProperty(BaseProperty): 25 | __slots__ = '_get_index', '_put_index', '_name' 26 | 27 | def __get__(self, obj, objtype=None): 28 | if self._get_index is None: 29 | raise AttributeError("Attribute %s is not readable" % self._name) 30 | result = ctypes.c_long(-1) 31 | prototype = ctypes.WINFUNCTYPE(ctypes.HRESULT, ctypes.c_void_p)(self._get_index, "get_property") 32 | prototype(obj.get(), ctypes.byref(result)) 33 | return result.value 34 | 35 | def __set__(self, obj, value): 36 | if self._put_index is None: 37 | raise AttributeError("Attribute %s is not writable" % self._name) 38 | value = int(value) 39 | prototype = ctypes.WINFUNCTYPE(ctypes.HRESULT, ctypes.c_long)(self._put_index, "put_property") 40 | prototype(obj.get(), value) 41 | 42 | 43 | class VariantBoolProperty(BaseProperty): 44 | def __get__(self, obj, objtype=None): 45 | if self._get_index is None: 46 | raise AttributeError("Attribute %s is not readable" % self._name) 47 | result = ctypes.c_short(-1) 48 | prototype = ctypes.WINFUNCTYPE(ctypes.HRESULT, ctypes.c_void_p)(self._get_index, "get_property") 49 | prototype(obj.get(), ctypes.byref(result)) 50 | return bool(result.value) 51 | 52 | def __set__(self, obj, value): 53 | if self._put_index is None: 54 | raise AttributeError("Attribute %s is not writable" % self._name) 55 | bool_value = 0xffff if value else 0x0000 56 | prototype = ctypes.WINFUNCTYPE(ctypes.HRESULT, ctypes.c_short)(self._put_index, "put_property") 57 | prototype(obj.get(), bool_value) 58 | 59 | 60 | class DoubleProperty(BaseProperty): 61 | def __get__(self, obj, objtype=None): 62 | if self._get_index is None: 63 | raise AttributeError("Attribute %s is not readable" % self._name) 64 | result = ctypes.c_double(-1) 65 | prototype = ctypes.WINFUNCTYPE(ctypes.HRESULT, ctypes.c_void_p)(self._get_index, "get_property") 66 | prototype(obj.get(), ctypes.byref(result)) 67 | return result.value 68 | 69 | def __set__(self, obj, value): 70 | if self._put_index is None: 71 | raise AttributeError("Attribute %s is not writable" % self._name) 72 | value = float(value) 73 | prototype = ctypes.WINFUNCTYPE(ctypes.HRESULT, ctypes.c_double)(self._put_index, "put_property") 74 | prototype(obj.get(), value) 75 | 76 | 77 | class StringProperty(BaseProperty): 78 | def __get__(self, obj, objtype=None): 79 | if self._get_index is None: 80 | raise AttributeError("Attribute %s is not readable" % self._name) 81 | result = BStr() 82 | prototype = ctypes.WINFUNCTYPE(ctypes.HRESULT, ctypes.c_void_p)(self._get_index, "get_property") 83 | prototype(obj.get(), result.byref()) 84 | return result.value 85 | 86 | def __set__(self, obj, value): 87 | if self._put_index is None: 88 | raise AttributeError("Attribute %s is not writable" % self._name) 89 | value = BStr(str(value)) 90 | prototype = ctypes.WINFUNCTYPE(ctypes.HRESULT, ctypes.c_void_p)(self._put_index, "put_property") 91 | prototype(obj.get(), BStr(value).get()) 92 | 93 | 94 | class EnumProperty(BaseProperty): 95 | __slots__ = '_enum_type' 96 | 97 | def __init__(self, enum_type, get_index=None, put_index=None): 98 | super(EnumProperty, self).__init__(get_index=get_index, put_index=put_index) 99 | self._enum_type = enum_type 100 | 101 | def __get__(self, obj, objtype=None): 102 | if self._get_index is None: 103 | raise AttributeError("Attribute %s is not readable" % self._name) 104 | result = ctypes.c_int(-1) 105 | prototype = ctypes.WINFUNCTYPE(ctypes.HRESULT, ctypes.c_void_p)(self._get_index, "get_property") 106 | prototype(obj.get(), ctypes.byref(result)) 107 | return self._enum_type(result.value) 108 | 109 | def __set__(self, obj, value): 110 | if self._put_index is None: 111 | raise AttributeError("Attribute %s is not writable" % self._name) 112 | value = int(value) 113 | prototype = ctypes.WINFUNCTYPE(ctypes.HRESULT, ctypes.c_long)(self._put_index, "put_property") 114 | prototype(obj.get(), value) 115 | 116 | def __set_name__(self, owner, name): 117 | self._name = " '%s'" % name 118 | 119 | 120 | class Vector(IUnknown): 121 | IID = UUID("9851bc47-1b8c-11d3-ae0a-00a024cba50c") 122 | 123 | X = DoubleProperty(get_index=7, put_index=8) 124 | Y = DoubleProperty(get_index=9, put_index=10) 125 | 126 | 127 | class VectorProperty(BaseProperty): 128 | __slots__ = '_get_prototype' 129 | 130 | def __init__(self, get_index, put_index=None): 131 | super(VectorProperty, self).__init__(get_index=get_index, put_index=put_index) 132 | self._get_prototype = ctypes.WINFUNCTYPE(ctypes.HRESULT, ctypes.c_void_p)(get_index, "get_property") 133 | 134 | def __get__(self, obj, objtype=None): 135 | result = Vector() 136 | self._get_prototype(obj.get(), result.byref()) 137 | return result.X, result.Y 138 | 139 | def __set__(self, obj, value): 140 | if self._put_index is None: 141 | raise AttributeError("Attribute%s is not writable" % self._name) 142 | 143 | value = [float(c) for c in value] 144 | if len(value) != 2: 145 | raise ValueError("Expected two items for attribute%s." % self._name) 146 | 147 | result = Vector() 148 | self._get_prototype(obj.get(), result.byref()) 149 | result.X = value[0] 150 | result.Y = value[1] 151 | prototype = ctypes.WINFUNCTYPE(ctypes.HRESULT, ctypes.c_void_p)(self._put_index, "put_property") 152 | prototype(obj.get(), result.get()) 153 | 154 | 155 | class ObjectProperty(BaseProperty): 156 | __slots__ = '_interface' 157 | 158 | def __init__(self, interface, get_index, put_index=None): 159 | super(ObjectProperty, self).__init__(get_index=get_index, put_index=put_index) 160 | self._interface = interface 161 | 162 | def __get__(self, obj, objtype=None): 163 | result = self._interface() 164 | prototype = ctypes.WINFUNCTYPE(ctypes.HRESULT, ctypes.c_void_p)(self._get_index, "get_property") 165 | prototype(obj.get(), result.byref()) 166 | return result 167 | 168 | def __set__(self, obj, value): 169 | if self._put_index is None: 170 | raise AttributeError("Attribute%s is not writable" % self._name) 171 | if not isinstance(value, self._interface): 172 | raise TypeError("Expected attribute%s to be set to an instance of type %s" % (self._name, self._interface.__name__)) 173 | prototype = ctypes.WINFUNCTYPE(ctypes.HRESULT, ctypes.c_void_p)(self._put_index, "put_property") 174 | prototype(obj.get(), value.get()) 175 | 176 | 177 | class CollectionProperty(BaseProperty): 178 | __slots__ = '_interface' 179 | 180 | GET_COUNT_METHOD = ctypes.WINFUNCTYPE(ctypes.HRESULT, ctypes.c_void_p)(7, "get_Count") 181 | GET_ITEM_METHOD = ctypes.WINFUNCTYPE(ctypes.HRESULT, VARIANT, ctypes.c_void_p)(8, "get_Item") 182 | 183 | def __init__(self, get_index, interface=None): 184 | super(CollectionProperty, self).__init__(get_index=get_index) 185 | if interface is None: 186 | interface = IUnknown 187 | self._interface = interface 188 | 189 | def __get__(self, obj, objtype=None): 190 | collection = IUnknown() 191 | prototype = ctypes.WINFUNCTYPE(ctypes.HRESULT, ctypes.c_void_p)(self._get_index, "get_property") 192 | prototype(obj.get(), collection.byref()) 193 | 194 | count = ctypes.c_long(-1) 195 | CollectionProperty.GET_COUNT_METHOD(collection.get(), ctypes.byref(count)) 196 | result = [] 197 | 198 | for n in range(count.value): 199 | index = Variant(n, vartype=VariantType.I4) 200 | item = self._interface() 201 | CollectionProperty.GET_ITEM_METHOD(collection.get(), index.get(), item.byref()) 202 | result.append(item) 203 | 204 | return result 205 | 206 | 207 | class SafeArrayProperty(BaseProperty): 208 | def __get__(self, obj, objtype=None): 209 | result = SafeArray() 210 | prototype = ctypes.WINFUNCTYPE(ctypes.HRESULT, ctypes.c_void_p)(self._get_index, "get_property") 211 | prototype(obj.get(), result.byref()) 212 | return result 213 | 214 | 215 | class Projection(IUnknown): 216 | IID = UUID("b39c3ae1-1e41-11d3-ae0e-00a024cba50c") 217 | 218 | Mode = EnumProperty(ProjectionMode, get_index=10, put_index=11) 219 | Focus = DoubleProperty(get_index=12, put_index=13) 220 | Magnification = DoubleProperty(get_index=14) 221 | CameraLength = DoubleProperty(get_index=15) 222 | MagnificationIndex = LongProperty(get_index=16, put_index=17) 223 | CameraLengthIndex = LongProperty(get_index=18, put_index=19) 224 | ImageShift = VectorProperty(get_index=20, put_index=21) 225 | ImageBeamShift = VectorProperty(get_index=22, put_index=23) 226 | DiffractionShift = VectorProperty(get_index=24, put_index=25) 227 | DiffractionStigmator = VectorProperty(get_index=26, put_index=27) 228 | ObjectiveStigmator = VectorProperty(get_index=28, put_index=29) 229 | Defocus = DoubleProperty(get_index=30, put_index=31) 230 | SubModeString = StringProperty(get_index=32) 231 | SubMode = EnumProperty(ProjectionSubMode, get_index=33) 232 | SubModeMinIndex = LongProperty(get_index=34) 233 | SubModeMaxIndex = LongProperty(get_index=35) 234 | ObjectiveExcitation = DoubleProperty(get_index=36) 235 | ProjectionIndex = LongProperty(get_index=37, put_index=38) 236 | LensProgram = EnumProperty(LensProg, get_index=39, put_index=40) 237 | ImageRotation = DoubleProperty(get_index=41) 238 | DetectorShift = EnumProperty(ProjectionDetectorShift, get_index=42, put_index=43) 239 | DetectorShiftMode = EnumProperty(ProjDetectorShiftMode, get_index=44, put_index=45) 240 | ImageBeamTilt = VectorProperty(get_index=46, put_index=47) 241 | 242 | RESET_DEFOCUS_METHOD = ctypes.WINFUNCTYPE(ctypes.HRESULT)(7, "ResetDefocus") 243 | NORMALIZE_METHOD = ctypes.WINFUNCTYPE(ctypes.HRESULT, ctypes.c_int)(8, "Normalize") 244 | CHANGE_PROJECTION_INDEX_METHOD = ctypes.WINFUNCTYPE(ctypes.HRESULT, ctypes.c_long)(9, "ChangeProjectionIndex") 245 | 246 | def ResetDefocus(self): 247 | Projection.RESET_DEFOCUS_METHOD(self.get()) 248 | 249 | def Normalize(self, norm): 250 | Projection.NORMALIZE_METHOD(self.get(), norm) 251 | 252 | def ChangeProjectionIndex(self, add_val): 253 | Projection.CHANGE_PROJECTION_INDEX_METHOD(self.get(), add_val) 254 | 255 | 256 | class CCDCameraInfo(IUnknown): 257 | IID = UUID("024ded60-b124-4514-bfe2-02c0f5c51db9") 258 | 259 | Name = StringProperty(get_index=7) 260 | Width = LongProperty(get_index=8) 261 | Height = LongProperty(get_index=9) 262 | PixelSize = VectorProperty(get_index=10) 263 | ShutterMode = EnumProperty(AcqShutterMode, get_index=13, put_index=14) 264 | _ShutterModes = SafeArrayProperty(get_index=12) 265 | _Binnings = SafeArrayProperty(get_index=11) 266 | 267 | @property 268 | def Binnings(self): 269 | return self._Binnings.as_list(int) 270 | 271 | @property 272 | def ShutterModes(self): 273 | return self._ShutterModes.as_list(AcqShutterMode) 274 | 275 | 276 | class CCDAcqParams(IUnknown): 277 | IID = UUID("c03db779-1345-42ab-9304-95b85789163d") 278 | 279 | ImageSize = EnumProperty(AcqImageSize, get_index=7, put_index=8) 280 | ExposureTime = DoubleProperty(get_index=9, put_index=10) 281 | Binning = LongProperty(get_index=11, put_index=12) 282 | ImageCorrection = EnumProperty(AcqImageCorrection, get_index=13, put_index=14) 283 | ExposureMode = EnumProperty(AcqExposureMode, get_index=15, put_index=16) 284 | MinPreExposureTime = DoubleProperty(get_index=17) 285 | MaxPreExposureTime = DoubleProperty(get_index=18) 286 | PreExposureTime = DoubleProperty(get_index=19, put_index=20) 287 | MinPreExposurePauseTime = DoubleProperty(get_index=21) 288 | MaxPreExposurePauseTime = DoubleProperty(get_index=22) 289 | PreExposurePauseTime = DoubleProperty(get_index=23, put_index=24) 290 | 291 | 292 | class CCDCamera(IUnknown): 293 | IID = UUID("e44e1565-4131-4937-b273-78219e090845") 294 | 295 | Info = ObjectProperty(CCDCameraInfo, get_index=7) 296 | AcqParams = ObjectProperty(CCDAcqParams, get_index=8, put_index=9) 297 | 298 | 299 | class STEMDetectorInfo(IUnknown): 300 | IID = UUID("96de094b-9cdc-4796-8697-e7dd5dc3ec3f") 301 | 302 | Name = StringProperty(get_index=7) 303 | Brightness = DoubleProperty(get_index=8, put_index=9) 304 | Contrast = DoubleProperty(get_index=10, put_index=11) 305 | _Binnings = SafeArrayProperty(get_index=11) 306 | 307 | @property 308 | def Binnings(self): 309 | return self._Binnings.as_list(int) 310 | 311 | 312 | class STEMAcqParams(IUnknown): 313 | IID = UUID("ddc14710-6152-4963-aea4-c67ba784c6b4") 314 | 315 | ImageSize = EnumProperty(AcqImageSize, get_index=7, put_index=8) 316 | DwellTime = DoubleProperty(get_index=9, put_index=10) 317 | Binning = LongProperty(get_index=11, put_index=12) 318 | 319 | 320 | class STEMDetector(IUnknown): 321 | __slots__ = '_acquisition' 322 | 323 | IID = UUID("d77c0d65-a1dd-4d0a-af25-c280046a5719") 324 | 325 | Info = ObjectProperty(STEMDetectorInfo, get_index=7) 326 | 327 | def __init__(self, value=None, adopt_reference=False, acquisition=None): 328 | super(STEMDetector, self).__init__(value=value, adopt_reference=adopt_reference) 329 | self._acquisition = acquisition 330 | 331 | @property 332 | def AcqParams(self): 333 | import warnings 334 | warnings.warn("The attribute AcqParams of STEMDetector instances is deprecated. Use Acquisition.StemAcqParams instead.", DeprecationWarning) 335 | return self._acquisition.StemAcqParams 336 | 337 | @AcqParams.setter 338 | def AcqParams(self, value): 339 | import warnings 340 | warnings.warn("The attribute AcqParams of STEMDetector instances is deprecated. Use Acquisition.StemAcqParams instead.", DeprecationWarning) 341 | self._acquisition.StemAcqParams = value 342 | 343 | 344 | class AcqImage(IUnknown): 345 | IID = UUID("e15f4810-43c6-489a-9e8a-588b0949e153") 346 | 347 | Name = StringProperty(get_index=7) 348 | Width = LongProperty(get_index=8) 349 | Height = LongProperty(get_index=9) 350 | Depth = LongProperty(get_index=10) 351 | _AsSafeArray = SafeArrayProperty(get_index=11) 352 | 353 | @property 354 | def Array(self): 355 | return self._AsSafeArray.as_array() 356 | 357 | 358 | class Acquisition(IUnknown): 359 | IID = UUID("d6bbf89c-22b8-468f-80a1-947ea89269ce") 360 | 361 | _AcquireImages = CollectionProperty(get_index=12, interface=AcqImage) 362 | Cameras = CollectionProperty(get_index=13, interface=CCDCamera) 363 | _Detectors = CollectionProperty(get_index=14) 364 | 365 | ADD_ACQ_DEVICE_METHOD = ctypes.WINFUNCTYPE(ctypes.HRESULT, ctypes.c_void_p)(7, "AddAcqDevice") 366 | ADD_ACQ_DEVICE_BY_NAME_METHOD = ctypes.WINFUNCTYPE(ctypes.HRESULT, ctypes.c_wchar_p)(8, "AddAcqDeviceByName") 367 | REMOVE_ACQ_DEVICE_METHOD = ctypes.WINFUNCTYPE(ctypes.HRESULT, ctypes.c_void_p)(9, "RemoveAcqDevice") 368 | REMOVE_ACQ_DEVICE_BY_NAME_METHOD = ctypes.WINFUNCTYPE(ctypes.HRESULT, ctypes.c_wchar_p)(10, "RemoveAcqDeviceByName") 369 | REMOVE_ALL_ACQ_DEVICES_METHOD = ctypes.WINFUNCTYPE(ctypes.HRESULT)(11, "RemoveAllAcqDevices") 370 | GET_DETECTORS_METHOD = ctypes.WINFUNCTYPE(ctypes.HRESULT, ctypes.c_void_p)(14, "get_Detectors") 371 | 372 | # Methods of STEMDetectors 373 | GET_ACQ_PARAMS_METHOD = ctypes.WINFUNCTYPE(ctypes.HRESULT, ctypes.c_void_p)(10, "get_AcqParams") 374 | PUT_ACQ_PARAMS_METHOD = ctypes.WINFUNCTYPE(ctypes.HRESULT, ctypes.c_void_p)(11, "put_AcqParams") 375 | 376 | def AddAcqDevice(self, device): 377 | if not isinstance(device, (STEMDetector, CCDCamera)): 378 | raise TypeError("Expected device to be instance of types STEMDetector or CCDCamera") 379 | Acquisition.ADD_ACQ_DEVICE_METHOD(self.get(), device.get()) 380 | 381 | def AddAcqDeviceByName(self, name): 382 | name_bstr = BStr(name) 383 | Acquisition.ADD_ACQ_DEVICE_BY_NAME_METHOD(self.get(), name_bstr.get()) 384 | 385 | def RemoveAcqDevice(self, device): 386 | if not isinstance(device, (STEMDetector, CCDCamera)): 387 | raise TypeError("Expected device to be instance of types STEMDetector or CCDCamera") 388 | Acquisition.REMOVE_ACQ_DEVICE_METHOD(self.get(), device.get()) 389 | 390 | def RemoveAcqDeviceByName(self, name): 391 | name_bstr = BStr(name) 392 | Acquisition.REMOVE_ACQ_DEVICE_BY_NAME_METHOD(self.get(), name_bstr.get()) 393 | 394 | def RemoveAllAcqDevices(self): 395 | Acquisition.REMOVE_ALL_ACQ_DEVICES_METHOD(self.get()) 396 | 397 | def AcquireImages(self): 398 | return self._AcquireImages 399 | 400 | @property 401 | def Detectors(self): 402 | collection = self._Detectors 403 | return [STEMDetector(item, acquisition=self) for item in collection] 404 | 405 | @property 406 | def StemAcqParams(self): 407 | collection = IUnknown() 408 | Acquisition.GET_DETECTORS_METHOD(self.get(), collection.byref()) 409 | params = STEMAcqParams() 410 | Acquisition.GET_ACQ_PARAMS_METHOD(collection.get(), params.byref()) 411 | return params 412 | 413 | @StemAcqParams.setter 414 | def StemAcqParams(self, value): 415 | if not isinstance(value, STEMAcqParams): 416 | raise TypeError("Expected attribute AcqParams to be set to an instance of type STEMAcqParams") 417 | collection = IUnknown() 418 | Acquisition.GET_DETECTORS_METHOD(self.get(), collection.byref()) 419 | Acquisition.PUT_ACQ_PARAMS_METHOD(collection.get(), value.get()) 420 | 421 | 422 | class Gauge(IUnknown): 423 | IID = UUID("52020820-18bf-11d3-86e1-00c04fc126dd") 424 | 425 | Name = StringProperty(get_index=8) 426 | Pressure = DoubleProperty(get_index=9) 427 | Status = EnumProperty(GaugeStatus, get_index=10) 428 | PressureLevel = EnumProperty(GaugePressureLevel, get_index=11) 429 | 430 | READ_METHOD = ctypes.WINFUNCTYPE(ctypes.HRESULT)(7, "Read") 431 | 432 | def Read(self): 433 | Gauge.READ_METHOD(self.get()) 434 | 435 | 436 | class Vacuum(IUnknown): 437 | IID = UUID("c7646442-1115-11d3-ae00-00a024cba50c") 438 | 439 | Status = EnumProperty(VacuumStatus, get_index=8) 440 | PVPRunning = VariantBoolProperty(get_index=9) 441 | Gauges = CollectionProperty(get_index=10, interface=Gauge) 442 | ColumnValvesOpen = VariantBoolProperty(get_index=11, put_index=12) 443 | 444 | RUN_BUFFER_CYCLE_METHOD = ctypes.WINFUNCTYPE(ctypes.HRESULT)(7, "RunBufferCycle") 445 | 446 | def RunBufferCycle(self): 447 | Vacuum.RUN_BUFFER_CYCLE_METHOD(self.get()) 448 | 449 | 450 | class StagePosition(IUnknown): 451 | IID = UUID("9851bc4a-1b8c-11d3-ae0a-00a024cba50c") 452 | AXES = 'xyzab' 453 | 454 | X = DoubleProperty(get_index=9, put_index=10) 455 | Y = DoubleProperty(get_index=11, put_index=12) 456 | Z = DoubleProperty(get_index=13, put_index=14) 457 | A = DoubleProperty(get_index=15, put_index=16) 458 | B = DoubleProperty(get_index=17, put_index=18) 459 | 460 | def to_dict(self): 461 | return {key: getattr(self, key.upper()) for key in StagePosition.AXES} 462 | 463 | def from_dict(self, values): 464 | axes = 0 465 | for key, value in values.items(): 466 | if key not in StagePosition.AXES: 467 | raise ValueError("Unexpected axes: %s" % key) 468 | attr_name = key.upper() 469 | setattr(self, attr_name, float(value)) 470 | axes |= getattr(StageAxes, attr_name) 471 | return axes 472 | 473 | 474 | class StageAxisData(IUnknown): 475 | IID = UUID("8f1e91c2-b97d-45b8-87c9-423f5eb10b8a") 476 | 477 | MinPos = DoubleProperty(get_index=7) 478 | MaxPos = DoubleProperty(get_index=8) 479 | UnitType = EnumProperty(MeasurementUnitType, get_index=9) 480 | 481 | 482 | class Stage(IUnknown): 483 | IID = UUID("e7ae1e41-1bf8-11d3-ae0b-00a024cba50c") 484 | 485 | STAGEAXES_FROM_AXIS = { 486 | 'x': StageAxes.X, 487 | 'y': StageAxes.Y, 488 | 'z': StageAxes.Z, 489 | 'a': StageAxes.A, 490 | 'b': StageAxes.B, 491 | } 492 | 493 | UNIT_STRING = { 494 | MeasurementUnitType.METERS: 'meters', 495 | MeasurementUnitType.RADIANS: 'radians' 496 | } 497 | 498 | Status = EnumProperty(StageStatus, get_index=9) 499 | _Position = ObjectProperty(get_index=10, interface=StagePosition) 500 | Holder = EnumProperty(StageHolderType, get_index=11) 501 | 502 | GOTO_METHOD = ctypes.WINFUNCTYPE(ctypes.HRESULT, ctypes.c_void_p, ctypes.c_int)(7, 'GoToWithSpeed') 503 | MOVETO_METHOD = ctypes.WINFUNCTYPE(ctypes.HRESULT, ctypes.c_void_p, ctypes.c_int)(8, 'MoveTo') 504 | GET_AXIS_DATA_METHOD = ctypes.WINFUNCTYPE(ctypes.HRESULT, ctypes.c_int, ctypes.c_void_p)(12, 'get_AxisData') 505 | GOTO_WITH_SPEED_METHOD = ctypes.WINFUNCTYPE(ctypes.HRESULT, ctypes.c_void_p, ctypes.c_int, ctypes.c_double)(13, 'GoToWithSpeed') 506 | 507 | @property 508 | def Position(self): 509 | return self._Position.to_dict() 510 | 511 | def GoTo(self, speed=None, **kw): 512 | pos = self._Position 513 | axes = pos.from_dict(kw) 514 | if not axes: 515 | return 516 | if speed is not None: 517 | Stage.GOTO_WITH_SPEED_METHOD(self.get(), pos.get(), axes, speed) 518 | else: 519 | Stage.GOTO_METHOD(self.get(), pos.get(), axes) 520 | 521 | def MoveTo(self, **kw): 522 | pos = self._Position 523 | axes = pos.from_dict(kw) 524 | if not axes: 525 | return 526 | Stage.MOVETO_METHOD(self.get(), pos.get(), axes) 527 | 528 | def AxisData(self, axis): 529 | try: 530 | mask = Stage.STAGEAXES_FROM_AXIS[axis] 531 | except KeyError: 532 | raise ValueError("Expected axis name: 'x', 'y', 'z', 'a', or 'b'") 533 | data = StageAxisData() 534 | Stage.GET_AXIS_DATA_METHOD(self.get(), mask, data.byref()) 535 | return (data.MinPos, data.MaxPos, Stage.UNIT_STRING.get(data.UnitType)) 536 | 537 | 538 | class Camera(IUnknown): 539 | IID = UUID("9851bc41-1b8c-11d3-ae0a-00a024cba50c") 540 | 541 | Stock = LongProperty(get_index=8) 542 | MainScreen = EnumProperty(ScreenPosition, get_index=9, put_index=10) 543 | IsSmallScreenDown = VariantBoolProperty(get_index=11) 544 | MeasuredExposureTime = DoubleProperty(get_index=12) 545 | FilmText = StringProperty(get_index=13, put_index=14) 546 | ManualExposureTime = DoubleProperty(get_index=15, put_index=16) 547 | PlateuMarker = VariantBoolProperty(get_index=17, put_index=18) 548 | ExposureNumber = LongProperty(get_index=19, put_index=20) 549 | Usercode = StringProperty(get_index=21, put_index=22) 550 | ManualExposure = VariantBoolProperty(get_index=23, put_index=24) 551 | PlateLabelDataType = EnumProperty(PlateLabelDateFormat, get_index=25, put_index=26) 552 | ScreenDim = VariantBoolProperty(get_index=27, put_index=28) 553 | ScreenDimText = StringProperty(get_index=29, put_index=30) 554 | ScreenCurrent = DoubleProperty(get_index=31) 555 | 556 | TAKE_EXPOSURE_METHOD = ctypes.WINFUNCTYPE(ctypes.HRESULT) 557 | 558 | def TakeExposure(self): 559 | Camera.TAKE_EXPOSURE_METHOD(self.get()) 560 | 561 | 562 | class Illumination(IUnknown): 563 | IID = UUID("ef960690-1c38-11d3-ae0b-00a024cba50c") 564 | 565 | Mode = EnumProperty(IlluminationMode, get_index=8, put_index=9) 566 | SpotsizeIndex = LongProperty(get_index=10, put_index=11) 567 | SpotSizeIndex = LongProperty(get_index=10, put_index=11) 568 | Intensity = DoubleProperty(get_index=12, put_index=13) 569 | IntensityZoomEnabled = VariantBoolProperty(get_index=14, put_index=15) 570 | IntensityLimitEnabled = VariantBoolProperty(get_index=16, put_index=17) 571 | BeamBlanked = VariantBoolProperty(get_index=18, put_index=19) 572 | Shift = VectorProperty(get_index=20, put_index=21) 573 | Tilt = VectorProperty(get_index=22, put_index=23) 574 | RotationCenter = VectorProperty(get_index=24, put_index=25) 575 | CondenserStigmator = VectorProperty(get_index=26, put_index=27) 576 | DFMode = EnumProperty(DarkFieldMode, get_index=28, put_index=29) 577 | DarkFieldMode = EnumProperty(DarkFieldMode, get_index=28, put_index=29) 578 | CondenserMode = EnumProperty(CondenserMode, get_index=30, put_index=31) 579 | IlluminatedArea = DoubleProperty(get_index=32, put_index=33) 580 | ProbeDefocus = DoubleProperty(get_index=34) 581 | ConvergenceAngle = DoubleProperty(get_index=35) 582 | StemMagnification = DoubleProperty(get_index=36, put_index=37) 583 | StemRotation = DoubleProperty(get_index=38, put_index=39) 584 | 585 | NORMALIZE_METHOD = ctypes.WINFUNCTYPE(ctypes.HRESULT, ctypes.c_int) 586 | 587 | def Normalize(self, norm): 588 | Illumination.NORMALIZE_METHOD(self.get(), norm) 589 | 590 | 591 | class Gun(IUnknown): 592 | IID = UUID("e6f00870-3164-11d3-b4c8-00a024cb9221") 593 | 594 | HTState = EnumProperty(HighTensionState, get_index=7, put_index=8) 595 | HTValue = DoubleProperty(get_index=9, put_index=10) 596 | HTMaxValue = DoubleProperty(get_index=11) 597 | Shift = VectorProperty(get_index=12, put_index=13) 598 | Tilt = VectorProperty(get_index=14, put_index=15) 599 | 600 | 601 | class BlankerShutter(IUnknown): 602 | IID = UUID("f1f59bb0-f8a0-439d-a3bf-87f527b600c4") 603 | 604 | ShutterOverrideOn = VariantBoolProperty(get_index=7, put_index=8) 605 | 606 | 607 | class InstrumentModeControl(IUnknown): 608 | IID = UUID("8dc0fc71-ff15-40d8-8174-092218d8b76b") 609 | 610 | StemAvailable = VariantBoolProperty(get_index=7) 611 | InstrumentMode = EnumProperty(InstrumentMode, get_index=8, put_index=9) 612 | 613 | 614 | class Configuration(IUnknown): 615 | IID = UUID("39cacdaf-f47c-4bbf-9ffa-a7a737664ced") 616 | 617 | ProductFamily = EnumProperty(ProductFamily, get_index=7) 618 | 619 | 620 | class Instrument(IUnknown): 621 | IID = UUID("bc0a2b11-10ff-11d3-ae00-00a024cba50c") 622 | 623 | AutoNormalizeEnabled = VariantBoolProperty(get_index=8, put_index=9) 624 | Vacuum = ObjectProperty(Vacuum, get_index=13) 625 | Camera = ObjectProperty(Camera, get_index=14) 626 | Stage = ObjectProperty(Stage, get_index=15) 627 | Illumination = ObjectProperty(Illumination, get_index=16) 628 | Projection = ObjectProperty(Projection, get_index=17) 629 | Gun = ObjectProperty(Gun, get_index=18) 630 | BlankerShutter = ObjectProperty(BlankerShutter, get_index=22) 631 | InstrumentModeControl = ObjectProperty(InstrumentModeControl, get_index=23) 632 | Acquisition = ObjectProperty(Acquisition, get_index=24) 633 | Configuration = ObjectProperty(Configuration, get_index=25) 634 | 635 | NORMALIZE_ALL_METHOD = ctypes.WINFUNCTYPE(ctypes.HRESULT)(7, "NormalizeAll") 636 | 637 | def NormalizeAll(self): 638 | Instrument.NORMALIZE_ALL_METHOD(self.get()) 639 | 640 | 641 | CLSID_INSTRUMENT = UUID('02CDC9A1-1F1D-11D3-AE11-00A024CBA50C') 642 | 643 | 644 | def GetInstrument(): 645 | """Returns Instrument instance.""" 646 | instrument = co_create_instance(CLSID_INSTRUMENT, CLSCTX_ALL, Instrument) 647 | return instrument 648 | 649 | 650 | -------------------------------------------------------------------------------- /temscript/_instrument_stubs.py: -------------------------------------------------------------------------------- 1 | # Have at least some stubs to develop software also on off-line computers... 2 | class Projection: 3 | pass 4 | 5 | 6 | class CCDCameraInfo: 7 | pass 8 | 9 | 10 | class CCDAcqParams: 11 | pass 12 | 13 | 14 | class CCDCamera: 15 | pass 16 | 17 | 18 | class STEMDetectorInfo: 19 | pass 20 | 21 | 22 | class STEMAcqParams: 23 | pass 24 | 25 | 26 | class STEMDetector: 27 | pass 28 | 29 | 30 | class AcqImage: 31 | pass 32 | 33 | 34 | class Acquisition: 35 | pass 36 | 37 | 38 | class Gauge: 39 | pass 40 | 41 | 42 | class Vacuum: 43 | pass 44 | 45 | 46 | class Stage: 47 | pass 48 | 49 | 50 | class Camera: 51 | pass 52 | 53 | 54 | class Illumination: 55 | pass 56 | 57 | 58 | class Gun: 59 | pass 60 | 61 | 62 | class BlankerShutter: 63 | pass 64 | 65 | 66 | class InstrumentModeControl: 67 | pass 68 | 69 | 70 | class Configuration: 71 | pass 72 | 73 | 74 | class Instrument: 75 | pass 76 | 77 | 78 | def GetInstrument(): 79 | """Returns Instrument instance.""" 80 | raise RuntimeError("temscript microscope API is not accessible") 81 | 82 | -------------------------------------------------------------------------------- /temscript/enums.py: -------------------------------------------------------------------------------- 1 | from enum import IntEnum 2 | 3 | 4 | # Not defined by FEI, but used in server/client 5 | class DetectorType(IntEnum): 6 | CAMERA = 1, 7 | STEM_DETECTOR = 2 8 | 9 | 10 | class TEMScriptingError(IntEnum): 11 | E_NOT_OK = -2147155969 # 0x8004ffff 12 | E_VALUE_CLIP = -2147155970 # 0x8004fffe 13 | E_OUT_OF_RANGE = -2147155971 # 0x8004fffd 14 | E_NOT_IMPLEMENTED = -2147155972 # 0x8004fffc 15 | # The following are also mentioned in the manual 16 | E_UNEXPECTED = -2147418113 # 0x8000FFFF 17 | E_NOTIMPL = -2147467263 # 0x80004001 18 | E_INVALIDARG = -2147024809 # 0x80070057 19 | E_ABORT = -2147467260 # 0x80004004 20 | E_FAIL = -2147467259 # 0x80004005 21 | E_ACCESSDENIED = -2147024891 # 0x80070005 22 | 23 | 24 | class VacuumStatus(IntEnum): 25 | UNKNOWN = 1 26 | OFF = 2 27 | CAMERA_AIR = 3 28 | BUSY = 4 29 | READY = 5 30 | ELSE = 6 31 | 32 | 33 | class GaugeStatus(IntEnum): 34 | UNDEFINED = 0 35 | UNDERFLOW = 1 36 | OVERFLOW = 2 37 | INVALID = 3 38 | VALID = 4 39 | 40 | 41 | class GaugePressureLevel(IntEnum): 42 | UNDEFINED = 0 43 | LOW = 1 44 | LOW_MEDIUM = 2 45 | MEDIUM_HIGH = 3 46 | HIGH = 4 47 | 48 | 49 | class StageStatus(IntEnum): 50 | READY = 0 51 | DISABLED = 1 52 | NOT_READY = 2 53 | GOING = 3 54 | MOVING = 4 55 | WOBBLING = 5 56 | DISABLE = 1 # Misnaming in temscript 1.X 57 | 58 | 59 | class MeasurementUnitType(IntEnum): 60 | UNKNOWN = 0 61 | METERS = 1 62 | RADIANS = 2 63 | 64 | 65 | class StageHolderType(IntEnum): 66 | NONE = 0 67 | SINGLE_TILT = 1 68 | DOUBLE_TILT = 2 69 | INVALID = 4 70 | POLARA = 5 71 | DUAL_AXIS = 6 72 | 73 | 74 | class StageAxes(IntEnum): 75 | NONE = 0 76 | X = 1 77 | Y = 2 78 | Z = 4 79 | A = 8 80 | B = 16 81 | XY = 3 82 | 83 | 84 | class IlluminationNormalization(IntEnum): 85 | SPOTSIZE = 1 86 | INTENSITY = 2 87 | CONDENSER = 3 88 | MINI_CONDENSER = 4 89 | OBJECTIVE = 5 90 | ALL = 6 91 | 92 | 93 | class IlluminationMode(IntEnum): 94 | NANOPROBE = 0 95 | MICROPROBE = 1 96 | 97 | 98 | class DarkFieldMode(IntEnum): 99 | OFF = 1 100 | CARTESIAN = 2 101 | CONICAL = 3 102 | 103 | 104 | class CondenserMode(IntEnum): 105 | PARALLEL = 0 106 | PROBE = 1 107 | 108 | 109 | class ProjectionNormalization(IntEnum): 110 | OBJECTIVE = 10 111 | PROJECTOR = 11 112 | ALL = 12 113 | 114 | 115 | class ProjectionMode(IntEnum): 116 | IMAGING = 1 117 | DIFFRACTION = 2 118 | 119 | 120 | class ProjectionSubMode(IntEnum): 121 | LM = 1 122 | M = 2 123 | SA = 3 124 | MH = 4 125 | LAD = 5 126 | D = 6 127 | 128 | 129 | class LensProg(IntEnum): 130 | REGULAR = 1 131 | EFTEM = 2 132 | 133 | 134 | class ProjectionDetectorShift(IntEnum): 135 | ON_AXIS = 0 136 | NEAR_AXIS = 1 137 | OFF_AXIS = 2 138 | 139 | 140 | class ProjDetectorShiftMode(IntEnum): 141 | AUTO_IGNORE = 1 142 | MANUAL = 2 143 | ALIGNMENT = 3 144 | 145 | 146 | class HighTensionState(IntEnum): 147 | DISABLED = 1 148 | OFF = 2 149 | ON = 3 150 | 151 | 152 | class InstrumentMode(IntEnum): 153 | TEM = 0 154 | STEM = 1 155 | 156 | 157 | class AcqShutterMode(IntEnum): 158 | PRE_SPECIMEN = 0 159 | POST_SPECIMEN = 1 160 | BOTH = 2 161 | 162 | 163 | class AcqImageSize(IntEnum): 164 | FULL = 0 165 | HALF = 1 166 | QUARTER = 2 167 | 168 | 169 | class AcqImageCorrection(IntEnum): 170 | UNPROCESSED = 0 171 | DEFAULT = 1 172 | 173 | 174 | class AcqExposureMode(IntEnum): 175 | NONE = 0 176 | SIMULTANEOUS = 1 177 | PRE_EXPOSURE = 2 178 | PRE_EXPOSURE_PAUSE = 3 179 | 180 | 181 | class ProductFamily(IntEnum): 182 | UNDEFINED = -1 183 | TECNAI = 0 184 | TITAN = 1 185 | TALOS = 2 186 | FUTURE_3 = 3 # Future family #3 187 | FUTURE_4 = 4 # Future family #4 188 | FUTURE_5 = 5 # Future family #5 189 | FUTURE_6 = 6 # Future family #6 190 | FUTURE_7 = 7 # Future family #7 191 | FUTURE_8 = 8 # Future family #8 192 | FUTURE_9 = 9 # Future family #9 193 | FUTURE_2 = TALOS # Alias 194 | 195 | 196 | class ScreenPosition(IntEnum): 197 | UNKNOWN = 1 198 | UP = 2 199 | DOWN = 3 200 | 201 | 202 | class PlateLabelDateFormat(IntEnum): 203 | NO_DATA = 0 204 | DDMMYY = 1 205 | MMDDYY = 2 206 | YYMMDD = 3 207 | -------------------------------------------------------------------------------- /temscript/instrument.py: -------------------------------------------------------------------------------- 1 | import platform 2 | if platform.system() == "Windows": 3 | from ._instrument_com import * 4 | else: 5 | from ._instrument_stubs import * 6 | 7 | 8 | __all__ = 'GetInstrument', 9 | -------------------------------------------------------------------------------- /temscript/marshall.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import json 3 | import sys 4 | import base64 5 | import zlib 6 | import gzip 7 | import io 8 | 9 | 10 | MIME_TYPE_PICKLE = "application/python-pickle" 11 | MIME_TYPE_JSON = "application/json" 12 | 13 | 14 | class ExtendedJsonEncoder(json.JSONEncoder): 15 | """JSONEncoder which handles iterables and numpy types""" 16 | def default(self, obj): 17 | if isinstance(obj, np.generic): 18 | return obj.item() 19 | try: 20 | iterable = iter(obj) 21 | except TypeError: 22 | pass 23 | else: 24 | return list(iterable) 25 | return super(ExtendedJsonEncoder, self).default(obj) 26 | 27 | 28 | ARRAY_TYPES = { 29 | "INT8": np.int8, 30 | "INT16": np.int16, 31 | "INT32": np.int32, 32 | "INT64": np.int64, 33 | "UINT8": np.uint8, 34 | "UINT16": np.uint16, 35 | "UINT32": np.uint32, 36 | "UINT64": np.uint64, 37 | "FLOAT32": np.float32, 38 | "FLOAT64": np.float64 39 | } 40 | 41 | ARRAY_ENDIANNESS = {"LITTLE", "BIG"} 42 | 43 | 44 | def unpack_array(obj): 45 | """ 46 | Unpack an packed array. 47 | 48 | :param obj: Dict with packed array 49 | """ 50 | sys_endianness = sys.byteorder.upper() 51 | shape = int(obj["height"]), int(obj["width"]) 52 | dtype = ARRAY_TYPES[obj["type"]] 53 | endianess = obj["endianness"] 54 | if endianess not in ARRAY_ENDIANNESS: 55 | raise ValueError("Unsupported endianness for encoded array: %s" % str(endianess)) 56 | encoding = obj["encoding"] 57 | if encoding == "BASE64": 58 | data = base64.b64decode(obj["data"]) 59 | else: 60 | raise ValueError("Unsupported encoding of array in JSON stream: %s" % str(encoding)) 61 | data = np.frombuffer(data, dtype=dtype).reshape(*shape) 62 | if obj["endianness"] != sys_endianness: 63 | data = data.byteswap() 64 | return data 65 | 66 | 67 | def pack_array(array): 68 | """ 69 | Pack array for JSON serialization. 70 | 71 | :param array: Numpy array to pack 72 | """ 73 | array = np.asanyarray(array) 74 | 75 | type_name = array.dtype.name.upper() 76 | if type_name not in ARRAY_TYPES: 77 | raise TypeError("Array data type %s can not be packed" % type_name) 78 | 79 | if array.dtype.byteorder == '<': 80 | endianness = "LITTLE" 81 | elif array.dtype.byteorder == '>': 82 | endianness = "BIG" 83 | else: 84 | endianness = sys.byteorder.upper() 85 | 86 | return { 87 | 'width': array.shape[1], 88 | 'height': array.shape[0], 89 | 'type': type_name, 90 | 'endianness': endianness, 91 | 'encoding': "BASE64", 92 | 'data': base64.b64encode(array).decode("ascii") 93 | } 94 | 95 | 96 | def gzip_encode(content): 97 | """GZIP encode bytes object""" 98 | out = io.BytesIO() 99 | f = gzip.GzipFile(fileobj=out, mode='w', compresslevel=5) 100 | f.write(content) 101 | f.close() 102 | return out.getvalue() 103 | 104 | 105 | def gzip_decode(content): 106 | """Decode GZIP encoded bytes object""" 107 | return zlib.decompress(content, 16 + zlib.MAX_WBITS) # No keyword arguments until Python 3.6 108 | -------------------------------------------------------------------------------- /temscript/microscope.py: -------------------------------------------------------------------------------- 1 | import math 2 | 3 | from temscript.base_microscope import set_enum_attr_from_dict, set_attr_from_dict 4 | 5 | from .base_microscope import BaseMicroscope, parse_enum, STAGE_AXES 6 | from .instrument import CCDCamera, GetInstrument, STEMDetector 7 | from .enums import * 8 | 9 | 10 | class Microscope(BaseMicroscope): 11 | """ 12 | Hich level interface to local microscope. 13 | 14 | Creating an instance of this class, already queries the COM interface for the instrument. 15 | 16 | Usage: 17 | 18 | >>> microscope = Microscope() 19 | >>> microscope.get_family() 20 | "TITAN" 21 | """ 22 | def __init__(self): 23 | tem = GetInstrument() 24 | self._tem_instrument = tem 25 | self._tem_gun = tem.Gun 26 | self._tem_illumination = tem.Illumination 27 | self._tem_projection = tem.Projection 28 | self._tem_stage = tem.Stage 29 | self._tem_acquisition = tem.Acquisition 30 | self._tem_vacuum = tem.Vacuum 31 | self._tem_camera = tem.Camera 32 | self._tem_control = tem.InstrumentModeControl 33 | try: 34 | self._family = tem.Configuration.ProductFamily 35 | except Exception: 36 | self._family = ProductFamily.UNDEFINED 37 | 38 | def get_family(self): 39 | return ProductFamily(self._family).name 40 | 41 | def get_microscope_id(self): 42 | import socket 43 | return socket.gethostname() 44 | 45 | def get_version(self): 46 | from .version import __version__ 47 | return __version__ 48 | 49 | def get_voltage(self): 50 | state = self._tem_gun.HTState 51 | if state == HighTensionState.ON: 52 | return self._tem_gun.HTValue * 1e-3 53 | else: 54 | return 0.0 55 | 56 | def get_vacuum(self): 57 | gauges = {} 58 | for g in self._tem_vacuum.Gauges: 59 | g.Read() 60 | status = GaugeStatus(g.Status) 61 | name = g.Name 62 | if status == GaugeStatus.UNDERFLOW: 63 | gauges[name] = "UNDERFLOW" 64 | elif status == GaugeStatus.OVERFLOW: 65 | gauges[name] = "OVERFLOW" 66 | elif status == GaugeStatus.VALID: 67 | gauges[name] = g.Pressure 68 | return { 69 | "status": VacuumStatus(self._tem_vacuum.Status).name, 70 | "column_valves_open": self.get_column_valves_open(), 71 | "pvp_running": self._tem_vacuum.PVPRunning, 72 | "gauges(Pa)": gauges, 73 | } 74 | 75 | def get_column_valves_open(self): 76 | return self._tem_vacuum.ColumnValvesOpen 77 | 78 | def set_column_valves_open(self, state): 79 | self._tem_vacuum.ColumnValvesOpen = state 80 | 81 | def get_stage_holder(self): 82 | try: 83 | return self._tem_stage.Holder.name 84 | except OSError: 85 | return "" 86 | 87 | def get_stage_status(self): 88 | return self._tem_stage.Status.name 89 | 90 | def get_stage_limits(self): 91 | result = {} 92 | for axis in STAGE_AXES: 93 | mn, mx, unit = self._tem_stage.AxisData(axis) 94 | result[axis] = (mn, mx) 95 | return result 96 | 97 | def get_stage_position(self): 98 | return self._tem_stage.Position 99 | 100 | def _set_stage_position(self, pos=None, method="GO", speed=None): 101 | if method == "GO": 102 | self._tem_stage.GoTo(speed=speed, **pos) 103 | elif method == "MOVE": 104 | self._tem_stage.MoveTo(**pos) 105 | else: 106 | raise ValueError("Unknown movement method.") 107 | 108 | def get_cameras(self): 109 | cameras = {} 110 | for cam in self._tem_acquisition.Cameras: 111 | info = cam.Info 112 | param = cam.AcqParams 113 | name = info.Name 114 | cameras[name] = { 115 | "type": "CAMERA", 116 | "height": info.Height, 117 | "width": info.Width, 118 | "pixel_size(um)": tuple(size / 1e-6 for size in info.PixelSize), 119 | "binnings": [int(b) for b in info.Binnings], 120 | "shutter_modes": [AcqShutterMode(x).name for x in info.ShutterModes], 121 | "pre_exposure_limits(s)": (param.MinPreExposureTime, param.MaxPreExposureTime), 122 | "pre_exposure_pause_limits(s)": (param.MinPreExposurePauseTime, param.MaxPreExposurePauseTime) 123 | } 124 | return cameras 125 | 126 | def get_stem_detectors(self): 127 | detectors = {} 128 | for stem in self._tem_acquisition.Detectors: 129 | info = stem.Info 130 | name = info.Name 131 | detectors[name] = { 132 | "type": "STEM_DETECTOR", 133 | "binnings": [int(b) for b in info.Binnings], 134 | } 135 | return detectors 136 | 137 | def _find_camera(self, name): 138 | """Find camera object by name""" 139 | if isinstance(name, CCDCamera): 140 | return name 141 | for cam in self._tem_acquisition.Cameras: 142 | if cam.Info.Name == name: 143 | return cam 144 | raise KeyError("No camera with name %s" % name) 145 | 146 | def _find_stem_detector(self, name): 147 | """Find STEM detector object by name""" 148 | if isinstance(name, STEMDetector): 149 | return name 150 | for stem in self._tem_acquisition.Detectors: 151 | if stem.Info.Name == name: 152 | return stem 153 | raise KeyError("No STEM detector with name %s" % name) 154 | 155 | def get_camera_param(self, name): 156 | camera = self._find_camera(name) 157 | info = camera.Info 158 | param = camera.AcqParams 159 | return { 160 | "image_size": param.ImageSize.name, 161 | "exposure(s)": param.ExposureTime, 162 | "binning": param.Binning, 163 | "correction": param.ImageCorrection.name, 164 | "exposure_mode": param.ExposureMode.name, 165 | "shutter_mode": info.ShutterMode.name, 166 | "pre_exposure(s)": param.PreExposureTime, 167 | "pre_exposure_pause(s)": param.PreExposurePauseTime 168 | } 169 | 170 | def set_camera_param(self, name, values, ignore_errors=False): 171 | camera = self._find_camera(name) 172 | values = dict(values) 173 | info = camera.Info 174 | param = camera.AcqParams 175 | set_enum_attr_from_dict(param, 'ImageSize', AcqImageSize, values, 'image_size', ignore_errors=ignore_errors) 176 | set_attr_from_dict(param, 'Binning', values, 'binning', ignore_errors=ignore_errors) 177 | set_enum_attr_from_dict(param, 'ImageCorrection', AcqImageCorrection, values, 'correction', 178 | ignore_errors=ignore_errors) 179 | set_enum_attr_from_dict(param, 'ExposureMode', AcqExposureMode, values, 'exposure_mode', 180 | ignore_errors=ignore_errors) 181 | set_enum_attr_from_dict(info, 'ShutterMode', AcqShutterMode, values, 'shutter_mode', 182 | ignore_errors=ignore_errors) 183 | set_attr_from_dict(param, 'PreExposureTime', values, 'pre_exposure(s)', ignore_errors=ignore_errors) 184 | set_attr_from_dict(param, 'PreExposurePauseTime', values, 'pre_exposure_pause(s)', ignore_errors=ignore_errors) 185 | 186 | # Set exposure after binning, since it adjusted automatically when binning is set 187 | set_attr_from_dict(param, 'ExposureTime', values, 'exposure(s)', ignore_errors=ignore_errors) 188 | 189 | if not ignore_errors and values: 190 | raise ValueError("Unknown keys in parameter dictionary.") 191 | 192 | def get_stem_detector_param(self, name): 193 | det = self._find_stem_detector(name) 194 | info = det.Info 195 | return { 196 | "brightness": info.Brightness, 197 | "contrast": info.Contrast 198 | } 199 | 200 | def set_stem_detector_param(self, name, values, ignore_errors=False): 201 | det = self._find_stem_detector(name) 202 | values = dict(values) 203 | info = det.Info 204 | set_attr_from_dict(info, 'Brightness', values, 'brightness') 205 | set_attr_from_dict(info, 'Contrast', values, 'contrast') 206 | 207 | if not ignore_errors and values: 208 | raise ValueError("Unknown keys in parameter dictionary.") 209 | 210 | def get_stem_acquisition_param(self): 211 | param = self._tem_acquisition.StemAcqParams 212 | return { 213 | "image_size": param.ImageSize.name, 214 | "binning": param.Binning, 215 | "dwell_time(s)": param.DwellTime 216 | } 217 | 218 | def set_stem_acquisition_param(self, values, ignore_errors=False): 219 | values = dict(values) 220 | param = self._tem_acquisition.StemAcqParams 221 | set_enum_attr_from_dict(param, 'ImageSize', AcqImageSize, values, 'image_size', ignore_errors=ignore_errors) 222 | set_attr_from_dict(param, 'Binning', values, 'binning', ignore_errors=ignore_errors) 223 | set_attr_from_dict(param, 'DwellTime', values, 'dwell_time(s)', ignore_errors=ignore_errors) 224 | 225 | if not ignore_errors and values: 226 | raise ValueError("Unknown keys in parameter dictionary.") 227 | 228 | def acquire(self, *args): 229 | self._tem_acquisition.RemoveAllAcqDevices() 230 | for det in args: 231 | try: 232 | self._tem_acquisition.AddAcqDeviceByName(det) 233 | except Exception: 234 | pass 235 | images = self._tem_acquisition.AcquireImages() 236 | result = {} 237 | for img in images: 238 | result[img.Name] = img.Array 239 | return result 240 | 241 | def get_image_shift(self): 242 | return self._tem_projection.ImageShift 243 | 244 | def set_image_shift(self, pos): 245 | self._tem_projection.ImageShift = pos 246 | 247 | def get_beam_shift(self): 248 | return self._tem_illumination.Shift 249 | 250 | def set_beam_shift(self, pos): 251 | self._tem_illumination.Shift = pos 252 | 253 | def get_beam_tilt(self): 254 | mode = self._tem_illumination.DFMode 255 | tilt = self._tem_illumination.Tilt 256 | if mode == DarkFieldMode.CONICAL: 257 | return tilt[0] * math.cos(tilt[1]), tilt[0] * math.sin(tilt[1]) 258 | elif mode == DarkFieldMode.CARTESIAN: 259 | return tilt 260 | else: 261 | return 0.0, 0.0 # Microscope might return nonsense if DFMode is OFF 262 | 263 | def set_beam_tilt(self, tilt): 264 | mode = self._tem_illumination.DFMode 265 | if tilt[0] == 0.0 and tilt[1] == 0.0: 266 | self._tem_illumination.Tilt = 0.0, 0.0 267 | self._tem_illumination.DFMode = DarkFieldMode.OFF 268 | elif mode == DarkFieldMode.CONICAL: 269 | self._tem_illumination.Tilt = math.sqrt(tilt[0]**2 + tilt[1]**2), math.atan2(tilt[1], tilt[0]) 270 | elif mode == DarkFieldMode.OFF: 271 | self._tem_illumination.DFMode = DarkFieldMode.CARTESIAN 272 | self._tem_illumination.Tilt = tilt 273 | else: 274 | self._tem_illumination.Tilt = tilt 275 | 276 | def normalize(self, mode="ALL"): 277 | mode = mode.upper() 278 | if mode == "ALL": 279 | self._tem_instrument.NormalizeAll() 280 | elif mode == "OBJECTIVE_CONDENSER": 281 | self._tem_illumination.Normalize(IlluminationNormalization.ALL) 282 | elif mode == "OBJECTIVE_PROJECTIVE": 283 | self._tem_projection.Normalize(ProjectionNormalization.ALL) 284 | else: 285 | try: 286 | illum_norm = IlluminationNormalization[mode] 287 | except KeyError: 288 | try: 289 | proj_norm = ProjectionNormalization[mode] 290 | except KeyError: 291 | raise ValueError("Unknown normalization mode: %s" % mode) 292 | else: 293 | self._tem_projection.Normalize(proj_norm) 294 | else: 295 | self._tem_illumination.Normalize(illum_norm) 296 | 297 | def get_projection_sub_mode(self): 298 | return self._tem_projection.SubMode.name 299 | 300 | def get_projection_mode(self): 301 | return self._tem_projection.Mode.name 302 | 303 | def set_projection_mode(self, mode): 304 | mode = parse_enum(ProjectionMode, mode) 305 | self._tem_projection.Mode = mode 306 | 307 | def get_projection_mode_string(self): 308 | return ProjectionSubMode(self._tem_projection.SubMode).name 309 | 310 | def get_magnification_index(self): 311 | return self._tem_projection.ProjectionIndex 312 | 313 | def set_magnification_index(self, index): 314 | index = int(index) 315 | self._tem_projection.ProjectionIndex = index 316 | 317 | def get_indicated_camera_length(self): 318 | return self._tem_projection.CameraLength 319 | 320 | def get_indicated_magnification(self): 321 | return self._tem_projection.Magnification 322 | 323 | def get_defocus(self): 324 | return self._tem_projection.Focus 325 | 326 | def set_defocus(self, value): 327 | self._tem_projection.Focus = float(value) 328 | 329 | def get_objective_excitation(self): 330 | return self._tem_projection.ObjectiveExcitation 331 | 332 | def get_intensity(self): 333 | return self._tem_illumination.Intensity 334 | 335 | def set_intensity(self, value): 336 | self._tem_illumination.Intensity = float(value) 337 | 338 | def get_objective_stigmator(self): 339 | return self._tem_projection.ObjectiveStigmator 340 | 341 | def set_objective_stigmator(self, value): 342 | self._tem_projection.ObjectiveStigmator = value 343 | 344 | def get_condenser_stigmator(self): 345 | return self._tem_illumination.CondenserStigmator 346 | 347 | def set_condenser_stigmator(self, value): 348 | self._tem_illumination.CondenserStigmator = value 349 | 350 | def get_diffraction_shift(self): 351 | return self._tem_projection.DiffractionShift 352 | 353 | def set_diffraction_shift(self, value): 354 | self._tem_projection.DiffractionShift = value 355 | 356 | def get_screen_current(self): 357 | return self._tem_camera.ScreenCurrent 358 | 359 | def get_screen_position(self): 360 | return self._tem_camera.MainScreen.name 361 | 362 | def set_screen_position(self, mode): 363 | mode = parse_enum(ScreenPosition, mode) 364 | self._tem_camera.MainScreen = mode 365 | 366 | def get_illumination_mode(self): 367 | return self._tem_illumination.Mode.name 368 | 369 | def set_illumination_mode(self, mode): 370 | mode = parse_enum(IlluminationMode, mode) 371 | self._tem_illumination.Mode = mode 372 | 373 | def get_condenser_mode(self): 374 | return self._tem_illumination.CondenserMode.name 375 | 376 | def set_condenser_mode(self, mode): 377 | mode = parse_enum(CondenserMode, mode) 378 | self._tem_illumination.CondenserMode = mode 379 | 380 | def get_stem_magnification(self): 381 | return self._tem_illumination.StemMagnification 382 | 383 | def set_stem_magnification(self, value): 384 | self._tem_illumination.StemMagnification = value 385 | 386 | def get_stem_rotation(self): 387 | return self._tem_illumination.StemRotation 388 | 389 | def set_stem_rotation(self, value): 390 | self._tem_illumination.StemRotation = value 391 | 392 | def get_illuminated_area(self): 393 | return self._tem_illumination.IlluminatedArea 394 | 395 | def set_illuminated_area(self, value): 396 | self._tem_illumination.IlluminatedArea = value 397 | 398 | def get_probe_defocus(self): 399 | return self._tem_illumination.ProbeDefocus 400 | 401 | def set_probe_defocus(self, value): 402 | self._tem_illumination.ProbeDefocus = value 403 | 404 | def get_convergence_angle(self): 405 | return self._tem_illumination.ConvergenceAngle 406 | 407 | def set_convergence_angle(self, value): 408 | self._tem_illumination.ConvergenceAngle = value 409 | 410 | def get_spot_size_index(self): 411 | return self._tem_illumination.SpotSizeIndex 412 | 413 | def set_spot_size_index(self, index): 414 | self._tem_illumination.SpotSizeIndex = index 415 | 416 | def get_dark_field_mode(self): 417 | return self._tem_illumination.DarkFieldMode.name 418 | 419 | def set_dark_field_mode(self, mode): 420 | mode = parse_enum(DarkFieldMode, mode) 421 | self._tem_illumination.DarkFieldMode = mode 422 | 423 | def get_beam_blanked(self): 424 | return self._tem_illumination.BeamBlanked 425 | 426 | def set_beam_blanked(self, mode): 427 | self._tem_illumination.BeamBlanked = mode 428 | 429 | def is_stem_available(self): 430 | return self._tem_control.StemAvailable 431 | 432 | def get_instrument_mode(self): 433 | return self._tem_control.InstrumentMode.name 434 | 435 | def set_instrument_mode(self, mode): 436 | mode = parse_enum(InstrumentMode, mode) 437 | self._tem_control.InstrumentMode = mode 438 | -------------------------------------------------------------------------------- /temscript/null_microscope.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | import numpy as np 4 | from math import pi 5 | 6 | from .enums import * 7 | from .base_microscope import BaseMicroscope, parse_enum 8 | 9 | 10 | def try_update(dest, source, key, cast=None, min_value=None, max_value=None, ignore_errors=False): 11 | try: 12 | value = source.pop(key) 13 | except KeyError: 14 | return False 15 | 16 | if callable(cast): 17 | try: 18 | value = cast(value) 19 | except (KeyError, ValueError): 20 | if ignore_errors: 21 | return False 22 | raise 23 | 24 | if min_value is not None: 25 | value = max(min_value, value) 26 | if max_value is not None: 27 | value = min(max_value, value) 28 | 29 | dest[key] = value 30 | return True 31 | 32 | 33 | def try_update_enum(dest, source, key, enum_type, ignore_errors=False): 34 | try: 35 | value = source.pop(key) 36 | except KeyError: 37 | return False 38 | 39 | try: 40 | value = parse_enum(enum_type, value) 41 | except (KeyError, ValueError): 42 | if ignore_errors: 43 | return False 44 | raise 45 | 46 | dest[key] = value 47 | return True 48 | 49 | 50 | def unpack_enums(mapping): 51 | result = {} 52 | for key, value in mapping.items(): 53 | if isinstance(value, Enum): 54 | value = value.name 55 | result[key] = value 56 | return result 57 | 58 | 59 | class NullMicroscope(BaseMicroscope): 60 | """ 61 | Microscope-like class which emulates an microscope. 62 | 63 | :param wait_exposure: Whether the acquire calls waits the exposure time until returning, emulating 64 | the timing of the real microscope 65 | :type wait_exposure: bool 66 | :param voltage: High tension value the microscope report in kV 67 | :type voltage: float 68 | """ 69 | STAGE_XY_RANGE = 1e-3 # meters 70 | STAGE_Z_RANGE = 0.3e-3 # meters 71 | STAGE_AB_RANGE = pi / 6.0 # radians 72 | 73 | CCD_SIZE = 2048 74 | CCD_BINNINGS = [1, 2, 4, 8] 75 | 76 | NORMALIZATION_MODES = ("SPOTSIZE", "INTENSITY", "CONDENSER", "MINI_CONDENSER", "OBJECTIVE", "PROJECTOR", 77 | "OBJECTIVE_CONDENSER", "OBJECTIVE_PROJECTOR", "ALL") 78 | 79 | def __init__(self, wait_exposure=None, voltage=200.0): 80 | self._column_valves = False 81 | self._stage_pos = {'x': 0.0, 'y': 0.0, 'z': 0.0, 'a': 0.0, 'b': 0.0} 82 | self._wait_exposure = bool(wait_exposure) if wait_exposure is not None else True 83 | self._ccd_param = { 84 | "image_size": AcqImageSize.FULL, 85 | "exposure(s)": 1.0, 86 | "binning": 1, 87 | "correction": AcqImageCorrection.DEFAULT, 88 | "exposure_mode": AcqExposureMode.NONE, 89 | "shutter_mode": AcqShutterMode.POST_SPECIMEN, 90 | "pre_exposure(s)": 0.0, 91 | "pre_exposure_pause(s)": 0.0 92 | } 93 | self._stem_acq_param = { 94 | "image_size": AcqImageSize.FULL, 95 | "dwell_time(s)": 1e-6, 96 | "binning": 1, 97 | } 98 | self._voltage = voltage 99 | self._image_shift = np.zeros(2, dtype=float) 100 | self._beam_shift = np.zeros(2, dtype=float) 101 | self._beam_tilt = np.zeros(2, dtype=float) 102 | self._condenser_stigmator = np.zeros(2, dtype=float) 103 | self._objective_stigmator = np.zeros(2, dtype=float) 104 | self._diffraction_shift = np.zeros(2, dtype=float) 105 | self._projection_sub_mode = ProjectionSubMode.SA 106 | self._projection_mode = ProjectionMode.IMAGING 107 | self._illumination_mode = IlluminationMode.MICROPROBE 108 | self._condenser_mode = CondenserMode.PARALLEL 109 | self._stem_magnification = 100000.0 110 | self._stem_rotation = 0.0 111 | self._illuminated_area = 1e-8 112 | self._probe_defocus = 0.0 113 | self._convergence_angle = 0.01 114 | self._spot_size = 1 115 | self._magnification_index = 10 116 | self._defocus = 0.0 117 | self._intensity = 0.0 118 | self._screen_position = ScreenPosition.DOWN 119 | self._dark_field_mode = DarkFieldMode.OFF 120 | self._beam_blanked = False 121 | 122 | def get_family(self): 123 | return "NULL" 124 | 125 | def get_microscope_id(self): 126 | import socket 127 | return socket.gethostname() 128 | 129 | def get_version(self): 130 | from .version import __version__ 131 | return __version__ 132 | 133 | def get_voltage(self): 134 | return self._voltage 135 | 136 | def get_vacuum(self): 137 | return { 138 | "status": VacuumStatus.READY.name, 139 | "column_valves_open": self._column_valves, 140 | "pvp_running": False, 141 | "gauges(Pa)": {}, 142 | } 143 | 144 | def get_column_valves_open(self): 145 | return self._column_valves 146 | 147 | def set_column_valves_open(self, state): 148 | self._column_valves = bool(state) 149 | 150 | def get_stage_holder(self): 151 | return StageHolderType.SINGLE_TILT.name 152 | 153 | def get_stage_status(self): 154 | return StageStatus.READY.name 155 | 156 | def get_stage_limits(self): 157 | return { 158 | "x": (-self.STAGE_XY_RANGE, +self.STAGE_XY_RANGE), 159 | "y": (-self.STAGE_XY_RANGE, +self.STAGE_XY_RANGE), 160 | "z": (-self.STAGE_Z_RANGE, +self.STAGE_Z_RANGE), 161 | "a": (-self.STAGE_AB_RANGE, +self.STAGE_AB_RANGE), 162 | "b": (-self.STAGE_AB_RANGE, +self.STAGE_AB_RANGE) 163 | } 164 | 165 | def get_stage_position(self): 166 | return dict(self._stage_pos) 167 | 168 | def _set_stage_position(self, pos=None, method="GO", speed=None): 169 | if method not in ["GO", "MOVE"]: 170 | raise ValueError("Unknown movement methods.") 171 | limit = self.get_stage_limits() 172 | for key in self._stage_pos.keys(): 173 | if key not in pos: 174 | continue 175 | mn, mx = limit[key] 176 | value = max(mn, min(mx, float(pos[key]))) 177 | self._stage_pos[key] = value 178 | 179 | def get_cameras(self): 180 | return { 181 | "CCD": { 182 | "type": "CAMERA", 183 | "width": self.CCD_SIZE, 184 | "height": self.CCD_SIZE, 185 | "pixel_size(um)": 24, 186 | "binnings": self.CCD_BINNINGS, 187 | "shutter_modes": ["POST_SPECIMEN"], 188 | "pre_exposure_limits": (0.0, 0.0), 189 | "pre_exposure_pause_limits": (0.0, 0.0), 190 | } 191 | } 192 | 193 | def get_stem_detectors(self): 194 | return {} 195 | 196 | def get_camera_param(self, name): 197 | if name == "CCD": 198 | return unpack_enums(self._ccd_param) 199 | else: 200 | raise KeyError("Unknown detector") 201 | 202 | def set_camera_param(self, name, param, ignore_errors=False): 203 | # Not implemented: raise error on unknown keys in param 204 | if name == "CCD": 205 | param = dict(param) 206 | try_update_enum(self._ccd_param, param, 'image_size', AcqImageSize, ignore_errors=ignore_errors) 207 | try_update(self._ccd_param, param, 'exposure(s)', cast=float, min_value=0.0, ignore_errors=ignore_errors) 208 | try_update(self._ccd_param, param, 'binning', cast=int, min_value=1, ignore_errors=ignore_errors) 209 | try_update_enum(self._ccd_param, param, 'correction', AcqImageCorrection, ignore_errors=ignore_errors) 210 | else: 211 | raise TypeError("Unknown detector.") 212 | 213 | def get_stem_detector_param(self, name): 214 | raise KeyError("Unknown detector") 215 | 216 | def set_stem_detector_param(self, name, values, ignore_errors=False): 217 | raise KeyError("Unknown detector") 218 | 219 | def get_stem_acquisition_param(self): 220 | return unpack_enums(self._stem_acq_param) 221 | 222 | def set_stem_acquisition_param(self, param, ignore_errors=False): 223 | # Not implemented: raise error on unknown keys in param 224 | param = dict(param) 225 | try_update_enum(self._stem_acq_param, param, 'image_size', AcqImageSize, ignore_errors=ignore_errors) 226 | try_update(self._stem_acq_param, param, 'dwell_time(s)', cast=float, min_value=1e-9, 227 | ignore_errors=ignore_errors) 228 | try_update(self._stem_acq_param, param, 'image_size', cast=int, min_value=1, ignore_errors=ignore_errors) 229 | if not ignore_errors and param: 230 | raise ValueError("Unknown keys in parameter dictionary.") 231 | 232 | def acquire(self, *args): 233 | result = {} 234 | detectors = set(args) 235 | for det in detectors: 236 | if det == "CCD": 237 | size = self.CCD_SIZE // self._ccd_param["binning"] 238 | if self._ccd_param["image_size"] == AcqImageSize.HALF: 239 | size //= 2 240 | elif self._ccd_param["image_size"] == AcqImageSize.QUARTER: 241 | size //= 4 242 | if self._wait_exposure: 243 | import time 244 | time.sleep(self._ccd_param["exposure(s)"]) 245 | result["CCD"] = np.zeros((size, size), dtype=np.int16) 246 | return result 247 | 248 | def get_image_shift(self): 249 | return tuple(self._image_shift) 250 | 251 | def set_image_shift(self, pos): 252 | pos = np.atleast_1d(pos) 253 | self._image_shift[...] = pos 254 | 255 | def get_beam_shift(self): 256 | return tuple(self._beam_shift) 257 | 258 | def set_beam_shift(self, pos): 259 | pos = np.atleast_1d(pos) 260 | self._beam_shift[...] = pos 261 | 262 | def get_beam_tilt(self): 263 | return tuple(self._beam_tilt) 264 | 265 | def set_beam_tilt(self, tilt): 266 | tilt = np.atleast_1d(tilt) 267 | self._beam_tilt[...] = tilt 268 | if np.allclose(self._beam_tilt, 0.0): 269 | self._dark_field_mode = DarkFieldMode.OFF 270 | elif self._dark_field_mode != DarkFieldMode.OFF: 271 | self._dark_field_mode = DarkFieldMode.CARTESIAN 272 | 273 | def normalize(self, mode="ALL"): 274 | if mode.upper() not in NullMicroscope.NORMALIZATION_MODES: 275 | raise ValueError("Unknown normalization mode: %s" % mode) 276 | 277 | def get_projection_sub_mode(self): 278 | return self._projection_sub_mode.name 279 | 280 | def get_projection_mode(self): 281 | return self._projection_mode.name 282 | 283 | def set_projection_mode(self, mode): 284 | mode = parse_enum(ProjectionMode, mode) 285 | self._projection_mode = mode 286 | 287 | def get_projection_mode_string(self): 288 | return "SA" 289 | 290 | def get_magnification_index(self): 291 | return self._magnification_index 292 | 293 | def set_magnification_index(self, index): 294 | self._magnification_index = index 295 | 296 | def get_indicated_camera_length(self): 297 | if self._projection_mode == ProjectionMode.DIFFRACTION: 298 | return self._magnification_index * 0.1 299 | else: 300 | return 0 301 | 302 | def get_indicated_magnification(self): 303 | if self._projection_mode == ProjectionMode.IMAGING: 304 | return self._magnification_index * 10000 305 | else: 306 | return 0 307 | 308 | def get_defocus(self): 309 | return self._defocus 310 | 311 | def set_defocus(self, value): 312 | self._defocus = float(value) 313 | 314 | def get_objective_excitation(self): 315 | return self._defocus 316 | 317 | def get_intensity(self): 318 | return self._intensity 319 | 320 | def set_intensity(self, value): 321 | self._intensity = float(value) 322 | 323 | def get_objective_stigmator(self): 324 | return tuple(self._objective_stigmator) 325 | 326 | def set_objective_stigmator(self, value): 327 | value = np.atleast_1d(value) 328 | self._objective_stigmator[...] = value 329 | 330 | def get_condenser_stigmator(self): 331 | return tuple(self._condenser_stigmator) 332 | 333 | def set_condenser_stigmator(self, value): 334 | value = np.atleast_1d(value) 335 | self._condenser_stigmator[...] = value 336 | 337 | def get_diffraction_shift(self): 338 | return tuple(self._diffraction_shift) 339 | 340 | def set_diffraction_shift(self, value): 341 | value = np.atleast_1d(value) 342 | self._diffraction_shift[...] = value 343 | 344 | def get_screen_current(self): 345 | return 1e-9 346 | 347 | def get_screen_position(self): 348 | return self._screen_position.name 349 | 350 | def set_screen_position(self, mode): 351 | mode = parse_enum(ScreenPosition, mode) 352 | self._screen_position = mode 353 | 354 | def get_illumination_mode(self): 355 | return self._illumination_mode.name 356 | 357 | def set_illumination_mode(self, mode): 358 | mode = parse_enum(IlluminationMode, mode) 359 | self._illumination_mode = mode 360 | 361 | def get_condenser_mode(self): 362 | return self._condenser_mode.name 363 | 364 | def set_condenser_mode(self, mode): 365 | mode = parse_enum(CondenserMode, mode) 366 | self._condenser_mode = mode 367 | 368 | def get_stem_magnification(self): 369 | return self._stem_magnification 370 | 371 | def set_stem_magnification(self, value): 372 | self._stem_magnification = float(value) 373 | 374 | def get_stem_rotation(self): 375 | return self._stem_rotation 376 | 377 | def set_stem_rotation(self, value): 378 | self._stem_rotation = float(value) 379 | 380 | def get_illuminated_area(self): 381 | return self._illuminated_area 382 | 383 | def set_illuminated_area(self, value): 384 | self._illuminated_area = float(value) 385 | 386 | def get_probe_defocus(self): 387 | return self._probe_defocus 388 | 389 | def set_probe_defocus(self, value): 390 | self._probe_defocus = float(value) 391 | 392 | def get_convergence_angle(self): 393 | return self._convergence_angle 394 | 395 | def set_convergence_angle(self, value): 396 | self._convergence_angle = float(value) 397 | 398 | def get_spot_size_index(self): 399 | return self._spot_size 400 | 401 | def set_spot_size_index(self, index): 402 | self._spot_size = min(max(index, 1), 11) 403 | 404 | def get_dark_field_mode(self): 405 | return self._dark_field_mode.name 406 | 407 | def set_dark_field_mode(self, mode): 408 | mode = parse_enum(DarkFieldMode, mode) 409 | self._dark_field_mode = mode 410 | 411 | def get_beam_blanked(self): 412 | return self._beam_blanked 413 | 414 | def set_beam_blanked(self, mode): 415 | self._beam_blanked = bool(mode) 416 | 417 | def is_stem_available(self): 418 | return False 419 | 420 | def get_instrument_mode(self): 421 | return InstrumentMode.TEM.name 422 | 423 | def set_instrument_mode(self, mode): 424 | mode = parse_enum(InstrumentMode, mode) 425 | if mode != InstrumentMode.TEM: 426 | raise ValueError("STEM not available.") 427 | -------------------------------------------------------------------------------- /temscript/remote_microscope.py: -------------------------------------------------------------------------------- 1 | import socket 2 | import json 3 | from http.client import HTTPConnection 4 | from urllib.parse import urlencode, quote_plus 5 | 6 | from .base_microscope import BaseMicroscope 7 | from .marshall import ExtendedJsonEncoder, unpack_array, gzip_decode, MIME_TYPE_PICKLE, MIME_TYPE_JSON 8 | 9 | 10 | class RemoteMicroscope(BaseMicroscope): 11 | """ 12 | Microscope-like class, which connects to a remote microscope server. 13 | 14 | :param address: (host, port) combination for the remote microscope. 15 | :type address: Tuple[str, int] 16 | :param transport: Underlying transport protocol, either 'JSON' (default) or 'PICKLE' 17 | :type transport: Literal['JSON', 'PICKLE'] 18 | """ 19 | def __init__(self, address, transport=None, timeout=None): 20 | self.address = address 21 | self.timeout = timeout 22 | self._conn = None 23 | if transport is None: 24 | transport = "JSON" 25 | if transport == "JSON": 26 | self.accepted_content = [MIME_TYPE_JSON] 27 | elif transport == "PICKLE": 28 | self.accepted_content = [MIME_TYPE_PICKLE] 29 | else: 30 | raise ValueError("Unknown transport protocol.") 31 | 32 | # Make connection 33 | self._conn = HTTPConnection(self.address[0], self.address[1], timeout=self.timeout) 34 | version_string = self.get_version() 35 | if int(version_string.split('.')[0]) < 2: 36 | raise ValueError("Expected microscope server version >= 2.0.0, actual version is %s" % version_string) 37 | 38 | def _request(self, method, endpoint, body=None, query=None, headers=None, accepted_response=None): 39 | """ 40 | Send request to server. 41 | 42 | If accepted_response is None, 200 is accepted for all methods, additionally 204 is accepted 43 | for methods, which pass a body (PUT, PATCH, POST) 44 | 45 | :param method: HTTP method to use, e.g. "GET" or "PUT" 46 | :type method: str 47 | :param endpoint: URL to request 48 | :type endpoint: str 49 | :param query: Optional dict or iterable of key-value-tuples to encode as query 50 | :type: Union[Dict[str, str], Iterable[Tuple[str, str]], None] 51 | :param body: Body to send 52 | :type body: Optional[Union[str, bytes]] 53 | :param headers: Optional dict of additional headers. 54 | :type headers: Optional[Dict[str, str]] 55 | :param accepted_response: Accepted response codes 56 | :type accepted_response: Optional[List[int]] 57 | 58 | :returns: response, decoded response body 59 | """ 60 | if accepted_response is None: 61 | accepted_response = [200] 62 | if method in ["PUT", "PATCH", "POST"]: 63 | accepted_response.append(204) 64 | 65 | # Create request 66 | headers = dict(headers) if headers is not None else dict() 67 | if "Accept" not in headers: 68 | headers["Accept"] = ",".join(self.accepted_content) 69 | if "Accept-Encoding" not in headers: 70 | headers["Accept-Encoding"] = "gzip" 71 | if query is not None: 72 | url = endpoint + '?' + urlencode(query) 73 | else: 74 | url = endpoint 75 | self._conn.request(method, url, body, headers) 76 | 77 | # Get response 78 | try: 79 | response = self._conn.getresponse() 80 | except socket.timeout: 81 | self._conn.close() 82 | self._conn = None 83 | raise 84 | 85 | if response.status not in accepted_response: 86 | if response.status == 404: 87 | raise KeyError("Failed remote call: %s" % response.reason) 88 | else: 89 | raise RuntimeError("Remote call returned status %d: %s" % (response.status, response.reason)) 90 | if response.status == 204: 91 | return response, None 92 | 93 | # Decode response 94 | content_length = response.getheader("Content-Length") 95 | if content_length is not None: 96 | encoded_body = response.read(int(content_length)) 97 | else: 98 | encoded_body = response.read() 99 | 100 | content_type = response.getheader("Content-Type") 101 | if content_type not in self.accepted_content: 102 | raise ValueError("Unexpected response type: %s", content_type) 103 | if response.getheader("Content-Encoding") == "gzip": 104 | encoded_body = gzip_decode(encoded_body) 105 | if content_type == MIME_TYPE_JSON: 106 | body = json.loads(encoded_body.decode("utf-8")) 107 | elif content_type == MIME_TYPE_PICKLE: 108 | import pickle 109 | body = pickle.loads(encoded_body) 110 | else: 111 | raise ValueError("Unsupported response type: %s", content_type) 112 | return response, body 113 | 114 | def _request_with_json_body(self, method, url, body, query=None, headers=None, accepted_response=None): 115 | """ 116 | Like :meth:`_request` but body is encoded as JSON. 117 | 118 | ..see :: :meth:`_request` 119 | """ 120 | if body is not None: 121 | headers = dict(headers) if headers is not None else dict() 122 | headers["Content-Type"] = MIME_TYPE_JSON 123 | encoder = ExtendedJsonEncoder() 124 | encoded_body = encoder.encode(body).encode("utf-8") 125 | else: 126 | encoded_body = None 127 | return self._request(method, url, body=encoded_body, query=query, headers=headers, 128 | accepted_response=accepted_response) 129 | 130 | def get_family(self): 131 | return self._request("GET", "/v1/family")[1] 132 | 133 | def get_microscope_id(self): 134 | return self._request("GET", "/v1/microscope_id")[1] 135 | 136 | def get_version(self): 137 | return self._request("GET", "/v1/version")[1] 138 | 139 | def get_voltage(self): 140 | return self._request("GET", "/v1/voltage")[1] 141 | 142 | def get_vacuum(self): 143 | return self._request("GET", "/v1/vacuum")[1] 144 | 145 | def get_column_valves_open(self): 146 | return self._request("GET", "/v1/column_valves_open")[1] 147 | 148 | def set_column_valves_open(self, state): 149 | self._request_with_json_body("PUT", "/v1/column_valves_open", state) 150 | 151 | def get_stage_holder(self): 152 | return self._request("GET", "/v1/stage_holder")[1] 153 | 154 | def get_stage_status(self): 155 | return self._request("GET", "/v1/stage_status")[1] 156 | 157 | def get_stage_limits(self): 158 | return self._request("GET", "/v1/stage_limits")[1] 159 | 160 | def get_stage_position(self): 161 | return self._request("GET", "/v1/stage_position")[1] 162 | 163 | def _set_stage_position(self, pos=None, method="GO", speed=None): 164 | query = {} 165 | if method is not None: 166 | query['method'] = method 167 | if speed is not None: 168 | query['speed'] = speed 169 | self._request_with_json_body("PUT", "/v1/stage_position", body=pos, query=query) 170 | 171 | def get_cameras(self): 172 | return self._request("GET", "/v1/cameras")[1] 173 | 174 | def get_stem_detectors(self): 175 | return self._request("GET", "/v1/stem_detectors")[1] 176 | 177 | def get_camera_param(self, name): 178 | return self._request("GET", "/v1/camera_param/" + quote_plus(name))[1] 179 | 180 | def set_camera_param(self, name, values, ignore_errors=None): 181 | query = None 182 | if ignore_errors is not None: 183 | query = {'ignore_errors': int(ignore_errors)} 184 | self._request_with_json_body("PUT", "/v1/camera_param/" + quote_plus(name), values, query=query) 185 | 186 | def get_stem_detector_param(self, name): 187 | return self._request("GET", "/v1/stem_detector_param/" + quote_plus(name))[1] 188 | 189 | def set_stem_detector_param(self, name, values, ignore_errors=None): 190 | query = None 191 | if ignore_errors is not None: 192 | query = {'ignore_errors': int(ignore_errors)} 193 | self._request_with_json_body("PUT", "/v1/stem_detector_param/" + quote_plus(name), values, query=query) 194 | 195 | def get_stem_acquisition_param(self): 196 | return self._request("GET", "/v1/stem_acquisition_param")[1] 197 | 198 | def set_stem_acquisition_param(self, values, ignore_errors=None): 199 | if ignore_errors is not None: 200 | query = {'ignore_errors': int(ignore_errors)} 201 | else: 202 | query = None 203 | self._request_with_json_body("PUT", "/v1/stem_acquisition_param", values, query=query) 204 | 205 | def acquire(self, *detectors): 206 | query = tuple(("detectors", det) for det in detectors) 207 | response, body = self._request("GET", "/v1/acquire", query=query) 208 | if response.getheader("Content-Type") == MIME_TYPE_JSON: 209 | body = {key: unpack_array(value) for key, value in body.items()} 210 | return body 211 | 212 | def get_image_shift(self): 213 | return self._request("GET", "/v1/image_shift")[1] 214 | 215 | def set_image_shift(self, pos): 216 | self._request_with_json_body("PUT", "/v1/image_shift", pos) 217 | 218 | def get_beam_shift(self): 219 | return self._request("GET", "/v1/beam_shift")[1] 220 | 221 | def set_beam_shift(self, pos): 222 | self._request_with_json_body("PUT", "/v1/beam_shift", pos) 223 | 224 | def get_beam_tilt(self): 225 | return self._request("GET", "/v1/beam_tilt")[1] 226 | 227 | def set_beam_tilt(self, pos): 228 | self._request_with_json_body("PUT", "/v1/beam_tilt", pos) 229 | 230 | def normalize(self, mode="ALL"): 231 | self._request_with_json_body("PUT", "/v1/normalize", mode) 232 | 233 | def get_projection_sub_mode(self): 234 | return self._request("GET", "/v1/projection_sub_mode")[1] 235 | 236 | def get_projection_mode(self): 237 | return self._request("GET", "/v1/projection_mode")[1] 238 | 239 | def set_projection_mode(self, mode): 240 | self._request_with_json_body("PUT", "/v1/projection_mode", mode) 241 | 242 | def get_projection_mode_string(self): 243 | return self._request("GET", "/v1/projection_mode_string")[1] 244 | 245 | def get_magnification_index(self): 246 | return self._request("GET", "/v1/magnification_index")[1] 247 | 248 | def set_magnification_index(self, index): 249 | self._request_with_json_body("PUT", "/v1/magnification_index", index) 250 | 251 | def get_indicated_camera_length(self): 252 | return self._request("GET", "/v1/indicated_camera_length")[1] 253 | 254 | def get_indicated_magnification(self): 255 | return self._request("GET", "/v1/indicated_magnification")[1] 256 | 257 | def get_defocus(self): 258 | return self._request("GET", "/v1/defocus")[1] 259 | 260 | def set_defocus(self, value): 261 | self._request_with_json_body("PUT", "/v1/defocus", value) 262 | 263 | def get_objective_excitation(self): 264 | return self._request("GET", "/v1/objective_excitation")[1] 265 | 266 | def get_intensity(self): 267 | return self._request("GET", "/v1/intensity")[1] 268 | 269 | def set_intensity(self, value): 270 | self._request_with_json_body("PUT", "/v1/intensity", value) 271 | 272 | def get_objective_stigmator(self): 273 | return self._request("GET", "/v1/objective_stigmator")[1] 274 | 275 | def set_objective_stigmator(self, value): 276 | self._request_with_json_body("PUT", "/v1/objective_stigmator", value) 277 | 278 | def get_condenser_stigmator(self): 279 | return self._request("GET", "/v1/condenser_stigmator")[1] 280 | 281 | def set_condenser_stigmator(self, value): 282 | self._request_with_json_body("PUT", "/v1/condenser_stigmator", value) 283 | 284 | def get_diffraction_shift(self): 285 | return self._request("GET", "/v1/diffraction_shift")[1] 286 | 287 | def set_diffraction_shift(self, value): 288 | self._request_with_json_body("PUT", "/v1/diffraction_shift", value) 289 | 290 | def get_screen_current(self): 291 | return self._request("GET", "/v1/screen_current")[1] 292 | 293 | def get_screen_position(self): 294 | return self._request("GET", "/v1/screen_position")[1] 295 | 296 | def set_screen_position(self, mode): 297 | self._request_with_json_body("PUT", "/v1/screen_position", mode) 298 | 299 | def get_illumination_mode(self): 300 | return self._request("GET", "/v1/illumination_mode")[1] 301 | 302 | def set_illumination_mode(self, mode): 303 | self._request_with_json_body("PUT", "/v1/illumination_mode", mode) 304 | 305 | def get_condenser_mode(self): 306 | return self._request("GET", "/v1/condenser_mode")[1] 307 | 308 | def set_condenser_mode(self, mode): 309 | self._request_with_json_body("PUT", "/v1/condenser_mode", mode) 310 | 311 | def get_stem_magnification(self): 312 | return self._request("GET", "/v1/stem_magnification")[1] 313 | 314 | def set_stem_magnification(self, value): 315 | self._request_with_json_body("PUT", "/v1/stem_magnification", value) 316 | 317 | def get_stem_rotation(self): 318 | return self._request("GET", "/v1/stem_rotation")[1] 319 | 320 | def set_stem_rotation(self, value): 321 | self._request_with_json_body("PUT", "/v1/stem_rotation", value) 322 | 323 | def get_illuminated_area(self): 324 | return self._request("GET", "/v1/illuminated_area")[1] 325 | 326 | def set_illuminated_area(self, value): 327 | self._request_with_json_body("PUT", "/v1/illuminated_area", value) 328 | 329 | def get_probe_defocus(self): 330 | return self._request("GET", "/v1/probe_defocus")[1] 331 | 332 | def set_probe_defocus(self, value): 333 | self._request_with_json_body("PUT", "/v1/probe_defocus", value) 334 | 335 | def get_convergence_angle(self): 336 | return self._request("GET", "/v1/convergence_angle")[1] 337 | 338 | def set_convergence_angle(self, value): 339 | self._request_with_json_body("PUT", "/v1/convergence_angle", value) 340 | 341 | def get_spot_size_index(self): 342 | return self._request("GET", "/v1/spot_size_index")[1] 343 | 344 | def set_spot_size_index(self, index): 345 | self._request_with_json_body("PUT", "/v1/spot_size_index", index) 346 | 347 | def get_dark_field_mode(self): 348 | return self._request("GET", "/v1/dark_field_mode")[1] 349 | 350 | def set_dark_field_mode(self, mode): 351 | self._request_with_json_body("PUT", "/v1/dark_field_mode", mode) 352 | 353 | def get_beam_blanked(self): 354 | return self._request("GET", "/v1/beam_blanked")[1] 355 | 356 | def set_beam_blanked(self, mode): 357 | self._request_with_json_body("PUT", "/v1/beam_blanked", mode) 358 | 359 | def is_stem_available(self): 360 | return self._request("GET", "/v1/stem_available")[1] 361 | 362 | def get_instrument_mode(self): 363 | return self._request("GET", "/v1/instrument_mode")[1] 364 | 365 | def set_instrument_mode(self, mode): 366 | self._request_with_json_body("PUT", "/v1/instrument_mode", mode) 367 | 368 | def get_state(self): 369 | return self._request("GET", "/v1/state")[1] 370 | -------------------------------------------------------------------------------- /temscript/server.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | import json 3 | from http.server import BaseHTTPRequestHandler, HTTPServer 4 | from urllib.parse import urlparse, parse_qs, unquote 5 | 6 | from .base_microscope import STAGE_AXES 7 | from .marshall import ExtendedJsonEncoder, gzip_encode, MIME_TYPE_PICKLE, MIME_TYPE_JSON, pack_array 8 | 9 | 10 | class MicroscopeHandler(BaseHTTPRequestHandler): 11 | GET_V1_FORWARD = ("family", "microscope_id", "version", "voltage", "vacuum", "stage_holder", 12 | "stage_status", "stage_position", "stage_limits", "detectors", "cameras", "stem_detectors", 13 | "stem_acquisition_param", "image_shift", "beam_shift", "beam_tilt", "projection_sub_mode", 14 | "projection_mode", "projection_mode_string", "magnification_index", "indicated_camera_length", 15 | "indicated_magnification", "defocus", "objective_excitation", "intensity", "objective_stigmator", 16 | "condenser_stigmator", "diffraction_shift", "screen_current", "screen_position", 17 | "illumination_mode", "condenser_mode", "illuminated_area", "probe_defocus", "convergence_angle", 18 | "stem_magnification", "stem_rotation", "spot_size_index", "dark_field_mode", "beam_blanked", 19 | "instrument_mode", 'optics_state', 'state', 'column_valves_open') 20 | 21 | PUT_V1_FORWARD = ("image_shift", "beam_shift", "beam_tilt", "projection_mode", "magnification_index", 22 | "defocus", "intensity", "diffraction_shift", "objective_stigmator", "condenser_stigmator", 23 | "screen_position", "illumination_mode", "spot_size_index", "dark_field_mode", 24 | "condenser_mode", "illuminated_area", "probe_defocus", "convergence_angle", 25 | "stem_magnification", "stem_rotation", "beam_blanked", "instrument_mode") 26 | 27 | def get_microscope(self): 28 | """Return microscope object from server.""" 29 | assert isinstance(self.server, MicroscopeServer) 30 | return self.server.microscope 31 | 32 | def get_accept_types(self): 33 | """Return list of accepted encodings.""" 34 | return [x.split(';', 1)[0].strip() for x in self.headers.get("Accept", "").split(",")] 35 | 36 | def build_response(self, response): 37 | """Encode response and send to client""" 38 | if response is None: 39 | self.send_response(204) 40 | self.end_headers() 41 | return 42 | 43 | try: 44 | accept_type = self.get_accept_types() 45 | if MIME_TYPE_PICKLE in accept_type: 46 | import pickle 47 | encoded_response = pickle.dumps(response, protocol=2) 48 | content_type = MIME_TYPE_PICKLE 49 | else: 50 | encoded_response = ExtendedJsonEncoder().encode(response).encode("utf-8") 51 | content_type = MIME_TYPE_JSON 52 | 53 | # Compression? 54 | accept_encoding = [x.split(';', 1)[0].strip() for x in self.headers.get("Accept-Encoding", "").split(",")] 55 | if len(encoded_response) > 256 and 'gzip' in accept_encoding: 56 | encoded_response = gzip_encode(encoded_response) 57 | content_encoding = 'gzip' 58 | else: 59 | content_encoding = None 60 | except Exception as exc: 61 | self.log_error("Exception raised during encoding of response: %s", repr(exc)) 62 | self.send_error(500, "Error handling request '%s': %s" % (self.path, str(exc))) 63 | else: 64 | self.send_response(200) 65 | if content_encoding: 66 | self.send_header('Content-Encoding', content_encoding) 67 | self.send_header('Content-Length', str(len(encoded_response))) 68 | self.send_header('Content-Type', content_type) 69 | self.end_headers() 70 | self.wfile.write(encoded_response) 71 | 72 | def do_GET_V1(self, endpoint, query): 73 | """Handle V1 GET requests""" 74 | if endpoint in self.GET_V1_FORWARD: 75 | response = getattr(self.get_microscope(), 'get_' + endpoint)() 76 | elif endpoint.startswith("detector_param/"): 77 | name = unquote(endpoint[15:]) 78 | response = self.get_microscope().get_detector_param(name) 79 | elif endpoint.startswith("camera_param/"): 80 | name = unquote(endpoint[13:]) 81 | response = self.get_microscope().get_camera_param(name) 82 | elif endpoint.startswith("stem_detector_param/"): 83 | name = unquote(endpoint[20:]) 84 | response = self.get_microscope().get_stem_detector_param(name) 85 | elif endpoint == "acquire": 86 | detectors = tuple(query.get("detectors", ())) 87 | response = self.get_microscope().acquire(*detectors) 88 | if MIME_TYPE_PICKLE not in self.get_accept_types(): 89 | response = {key: pack_array(value) for key, value in response.items()} 90 | elif endpoint == "stem_available": 91 | response = self.get_microscope().is_stem_available() 92 | else: 93 | raise KeyError("Unknown endpoint: '%s'" % endpoint) 94 | return response 95 | 96 | def do_PUT_V1(self, endpoint, query): 97 | """Handle V1 PUT requests""" 98 | length = int(self.headers['Content-Length']) 99 | if length > 4096: 100 | raise ValueError("Too much content...") 101 | content = self.rfile.read(length) 102 | decoded_content = json.loads(content.decode("utf-8")) 103 | 104 | # Check for known endpoints 105 | response = None 106 | if endpoint in self.PUT_V1_FORWARD: 107 | response = getattr(self.get_microscope(), 'set_' + endpoint)(decoded_content) 108 | elif endpoint == "stage_position": 109 | method = str(query["method"][0]) if "method" in query else None 110 | speed = float(query["speed"][0]) if "speed" in query else None 111 | pos = dict((k, decoded_content[k]) for k in decoded_content.keys() if k in STAGE_AXES) 112 | self.get_microscope().set_stage_position(pos, method=method, speed=speed) 113 | elif endpoint.startswith("camera_param/"): 114 | name = unquote(endpoint[13:]) 115 | ignore_errors = bool(query.get("ignore_errors", [False])[0]) 116 | response = self.get_microscope().set_camera_param(name, decoded_content, ignore_errors=ignore_errors) 117 | elif endpoint.startswith("stem_detector_param/"): 118 | name = unquote(endpoint[20:]) 119 | ignore_errors = bool(query.get("ignore_errors", [False])[0]) 120 | response = self.get_microscope().set_stem_detector_param(name, decoded_content, ignore_errors=ignore_errors) 121 | elif endpoint == "stem_acquisition_param": 122 | ignore_errors = bool(query.get("ignore_errors", [False])[0]) 123 | response = self.get_microscope().set_stem_acquisition_param(decoded_content, ignore_errors=ignore_errors) 124 | elif endpoint.startswith("detector_param/"): 125 | name = unquote(endpoint[15:]) 126 | response = self.get_microscope().set_detector_param(name, decoded_content) 127 | elif endpoint == "normalize": 128 | self.get_microscope().normalize(decoded_content) 129 | elif endpoint == "column_valves_open": 130 | state = bool(decoded_content) 131 | assert isinstance(self.server, MicroscopeServer) 132 | if self.server.allow_column_valves_open or not state: 133 | self.get_microscope().set_column_valves_open(state) 134 | else: 135 | raise ValueError("Opening of column valves is prohibited.") 136 | else: 137 | raise KeyError("Unknown endpoint: '%s'" % endpoint) 138 | return response 139 | 140 | # Handler for the GET requests 141 | def do_GET(self): 142 | try: 143 | request = urlparse(self.path) 144 | if request.path.startswith("/v1/"): 145 | response = self.do_GET_V1(request.path[4:], parse_qs(request.query)) 146 | else: 147 | raise KeyError('Unknown API version: %s' % self.path) 148 | except KeyError as exc: 149 | self.log_error("KeyError raised during handling of GET request '%s': %s", self.path, repr(exc)) 150 | self.send_error(404, str(exc)) 151 | except Exception as exc: 152 | self.log_error("Exception raised during handling of GET request '%s': %s", self.path, repr(exc)) 153 | self.send_error(500, "Error handling request '%s': %s" % (self.path, str(exc))) 154 | else: 155 | self.build_response(response) 156 | 157 | # Handler for the PUT requests 158 | def do_PUT(self): 159 | try: 160 | request = urlparse(self.path) 161 | if request.path.startswith("/v1/"): 162 | response = self.do_PUT_V1(request.path[4:], parse_qs(request.query)) 163 | else: 164 | raise KeyError('Unknown API version: %s' % self.path) 165 | except KeyError as exc: 166 | self.log_error("KeyError raised during handling of GET request '%s': %s" , self.path, repr(exc)) 167 | self.send_error(404, str(exc)) 168 | except Exception as exc: 169 | self.log_error("Exception raised during handling of GET request '%s': %s" , self.path, repr(exc)) 170 | self.send_error(500, "Error handling request '%s': %s" % (self.path, str(exc))) 171 | else: 172 | self.build_response(response) 173 | 174 | 175 | class MicroscopeServer(HTTPServer, object): 176 | def __init__(self, server_address=('', 8080), microscope_factory=None, allow_column_valves_open=True): 177 | """ 178 | Run a microscope server. 179 | 180 | :param server_address: (address, port) tuple 181 | :param microscope_factory: callable creating the BaseMicroscope instance to use 182 | :param allow_column_valves_open: Allow remote client to open column valves 183 | """ 184 | if microscope_factory is None: 185 | from .microscope import Microscope 186 | microscope_factory = Microscope 187 | self.microscope = microscope_factory() 188 | self.allow_column_valves_open = allow_column_valves_open 189 | super(MicroscopeServer, self).__init__(server_address, MicroscopeHandler) 190 | 191 | 192 | def run_server(argv=None): 193 | """ 194 | Main program for running the server 195 | 196 | :param argv: Arguments 197 | :type argv: List of str (see sys.argv) 198 | """ 199 | import argparse 200 | parser = argparse.ArgumentParser() 201 | parser.add_argument("-p", "--port", type=int, default=8080, 202 | help="Specify port on which the server is listening") 203 | parser.add_argument("--host", type=str, default='', 204 | help="Specify host address on which the the server is listening") 205 | parser.add_argument("--null", action='store_true', default=False, 206 | help="Use NullMicroscope instead of local microscope as backend") 207 | args = parser.parse_args(argv) 208 | 209 | if args.null: 210 | from .null_microscope import NullMicroscope 211 | microscope_factory = NullMicroscope 212 | else: 213 | microscope_factory = None 214 | 215 | # Create a web server and define the handler to manage the incoming request 216 | server = MicroscopeServer((args.host, args.port), microscope_factory=microscope_factory) 217 | try: 218 | print("Started httpserver on host '%s' port %d." % (args.host, args.port)) 219 | print("Press Ctrl+C to stop server.") 220 | 221 | # Wait forever for incoming http requests 222 | server.serve_forever() 223 | 224 | except KeyboardInterrupt: 225 | print('Ctrl+C received, shutting down the http server') 226 | 227 | finally: 228 | server.socket.close() 229 | 230 | return 0 231 | -------------------------------------------------------------------------------- /temscript/version.py: -------------------------------------------------------------------------------- 1 | __version__ = '2.1.1' 2 | --------------------------------------------------------------------------------