├── LICENSE ├── README.md ├── changelog ├── doc ├── .gitignore ├── Makefile └── source │ ├── _static │ ├── .gitignore │ ├── icon.ico │ ├── icon.png │ ├── illustration.png │ ├── logo.png │ └── logo.svg │ ├── _templates │ └── indexsidebar.html │ ├── api.rst │ ├── conf.py │ ├── easyleed.bib │ ├── easyleed │ ├── layout.html │ ├── static │ │ ├── alert_info_32.png │ │ ├── alert_warning_32.png │ │ ├── bg-page.png │ │ ├── bullet_orange.png │ │ └── easyleed.css_t │ └── theme.conf │ ├── index.rst │ ├── maninstall.rst │ └── userdoc.rst ├── environment.yml └── source ├── .gitignore ├── easyleed ├── __init__.py ├── __main__.py ├── base.py ├── defaultconfig.py ├── gui.py ├── io.py ├── kalman.py ├── qt.py └── test.py ├── pyproject.toml └── setup.py /LICENSE: -------------------------------------------------------------------------------- 1 | Easyleed - Low Energy Electron Diffraction I(E)-spectra analysis 2 | Copyright (C) 2010-2017 Andreas Mayer, Hanna Salopaasi, Nicola Ferralis 3 | 4 | This program is free software; you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation; either version 2 of the License, or 7 | (at your option) any later version. 8 | 9 | This program is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License (see below) for more details. 13 | 14 | GNU GENERAL PUBLIC LICENSE 15 | Version 2, June 1991 16 | 17 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 18 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 19 | Everyone is permitted to copy and distribute verbatim copies 20 | of this license document, but changing it is not allowed. 21 | 22 | Preamble 23 | 24 | The licenses for most software are designed to take away your 25 | freedom to share and change it. By contrast, the GNU General Public 26 | License is intended to guarantee your freedom to share and change free 27 | software--to make sure the software is free for all its users. This 28 | General Public License applies to most of the Free Software 29 | Foundation's software and to any other program whose authors commit to 30 | using it. (Some other Free Software Foundation software is covered by 31 | the GNU Lesser General Public License instead.) You can apply it to 32 | your programs, too. 33 | 34 | When we speak of free software, we are referring to freedom, not 35 | price. Our General Public Licenses are designed to make sure that you 36 | have the freedom to distribute copies of free software (and charge for 37 | this service if you wish), that you receive source code or can get it 38 | if you want it, that you can change the software or use pieces of it 39 | in new free programs; and that you know you can do these things. 40 | 41 | To protect your rights, we need to make restrictions that forbid 42 | anyone to deny you these rights or to ask you to surrender the rights. 43 | These restrictions translate to certain responsibilities for you if you 44 | distribute copies of the software, or if you modify it. 45 | 46 | For example, if you distribute copies of such a program, whether 47 | gratis or for a fee, you must give the recipients all the rights that 48 | you have. You must make sure that they, too, receive or can get the 49 | source code. And you must show them these terms so they know their 50 | rights. 51 | 52 | We protect your rights with two steps: (1) copyright the software, and 53 | (2) offer you this license which gives you legal permission to copy, 54 | distribute and/or modify the software. 55 | 56 | Also, for each author's protection and ours, we want to make certain 57 | that everyone understands that there is no warranty for this free 58 | software. If the software is modified by someone else and passed on, we 59 | want its recipients to know that what they have is not the original, so 60 | that any problems introduced by others will not reflect on the original 61 | authors' reputations. 62 | 63 | Finally, any free program is threatened constantly by software 64 | patents. We wish to avoid the danger that redistributors of a free 65 | program will individually obtain patent licenses, in effect making the 66 | program proprietary. To prevent this, we have made it clear that any 67 | patent must be licensed for everyone's free use or not licensed at all. 68 | 69 | The precise terms and conditions for copying, distribution and 70 | modification follow. 71 | 72 | GNU GENERAL PUBLIC LICENSE 73 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 74 | 75 | 0. This License applies to any program or other work which contains 76 | a notice placed by the copyright holder saying it may be distributed 77 | under the terms of this General Public License. The "Program", below, 78 | refers to any such program or work, and a "work based on the Program" 79 | means either the Program or any derivative work under copyright law: 80 | that is to say, a work containing the Program or a portion of it, 81 | either verbatim or with modifications and/or translated into another 82 | language. (Hereinafter, translation is included without limitation in 83 | the term "modification".) Each licensee is addressed as "you". 84 | 85 | Activities other than copying, distribution and modification are not 86 | covered by this License; they are outside its scope. The act of 87 | running the Program is not restricted, and the output from the Program 88 | is covered only if its contents constitute a work based on the 89 | Program (independent of having been made by running the Program). 90 | Whether that is true depends on what the Program does. 91 | 92 | 1. You may copy and distribute verbatim copies of the Program's 93 | source code as you receive it, in any medium, provided that you 94 | conspicuously and appropriately publish on each copy an appropriate 95 | copyright notice and disclaimer of warranty; keep intact all the 96 | notices that refer to this License and to the absence of any warranty; 97 | and give any other recipients of the Program a copy of this License 98 | along with the Program. 99 | 100 | You may charge a fee for the physical act of transferring a copy, and 101 | you may at your option offer warranty protection in exchange for a fee. 102 | 103 | 2. You may modify your copy or copies of the Program or any portion 104 | of it, thus forming a work based on the Program, and copy and 105 | distribute such modifications or work under the terms of Section 1 106 | above, provided that you also meet all of these conditions: 107 | 108 | a) You must cause the modified files to carry prominent notices 109 | stating that you changed the files and the date of any change. 110 | 111 | b) You must cause any work that you distribute or publish, that in 112 | whole or in part contains or is derived from the Program or any 113 | part thereof, to be licensed as a whole at no charge to all third 114 | parties under the terms of this License. 115 | 116 | c) If the modified program normally reads commands interactively 117 | when run, you must cause it, when started running for such 118 | interactive use in the most ordinary way, to print or display an 119 | announcement including an appropriate copyright notice and a 120 | notice that there is no warranty (or else, saying that you provide 121 | a warranty) and that users may redistribute the program under 122 | these conditions, and telling the user how to view a copy of this 123 | License. (Exception: if the Program itself is interactive but 124 | does not normally print such an announcement, your work based on 125 | the Program is not required to print an announcement.) 126 | 127 | These requirements apply to the modified work as a whole. If 128 | identifiable sections of that work are not derived from the Program, 129 | and can be reasonably considered independent and separate works in 130 | themselves, then this License, and its terms, do not apply to those 131 | sections when you distribute them as separate works. But when you 132 | distribute the same sections as part of a whole which is a work based 133 | on the Program, the distribution of the whole must be on the terms of 134 | this License, whose permissions for other licensees extend to the 135 | entire whole, and thus to each and every part regardless of who wrote it. 136 | 137 | Thus, it is not the intent of this section to claim rights or contest 138 | your rights to work written entirely by you; rather, the intent is to 139 | exercise the right to control the distribution of derivative or 140 | collective works based on the Program. 141 | 142 | In addition, mere aggregation of another work not based on the Program 143 | with the Program (or with a work based on the Program) on a volume of 144 | a storage or distribution medium does not bring the other work under 145 | the scope of this License. 146 | 147 | 3. You may copy and distribute the Program (or a work based on it, 148 | under Section 2) in object code or executable form under the terms of 149 | Sections 1 and 2 above provided that you also do one of the following: 150 | 151 | a) Accompany it with the complete corresponding machine-readable 152 | source code, which must be distributed under the terms of Sections 153 | 1 and 2 above on a medium customarily used for software interchange; or, 154 | 155 | b) Accompany it with a written offer, valid for at least three 156 | years, to give any third party, for a charge no more than your 157 | cost of physically performing source distribution, a complete 158 | machine-readable copy of the corresponding source code, to be 159 | distributed under the terms of Sections 1 and 2 above on a medium 160 | customarily used for software interchange; or, 161 | 162 | c) Accompany it with the information you received as to the offer 163 | to distribute corresponding source code. (This alternative is 164 | allowed only for noncommercial distribution and only if you 165 | received the program in object code or executable form with such 166 | an offer, in accord with Subsection b above.) 167 | 168 | The source code for a work means the preferred form of the work for 169 | making modifications to it. For an executable work, complete source 170 | code means all the source code for all modules it contains, plus any 171 | associated interface definition files, plus the scripts used to 172 | control compilation and installation of the executable. However, as a 173 | special exception, the source code distributed need not include 174 | anything that is normally distributed (in either source or binary 175 | form) with the major components (compiler, kernel, and so on) of the 176 | operating system on which the executable runs, unless that component 177 | itself accompanies the executable. 178 | 179 | If distribution of executable or object code is made by offering 180 | access to copy from a designated place, then offering equivalent 181 | access to copy the source code from the same place counts as 182 | distribution of the source code, even though third parties are not 183 | compelled to copy the source along with the object code. 184 | 185 | 4. You may not copy, modify, sublicense, or distribute the Program 186 | except as expressly provided under this License. Any attempt 187 | otherwise to copy, modify, sublicense or distribute the Program is 188 | void, and will automatically terminate your rights under this License. 189 | However, parties who have received copies, or rights, from you under 190 | this License will not have their licenses terminated so long as such 191 | parties remain in full compliance. 192 | 193 | 5. You are not required to accept this License, since you have not 194 | signed it. However, nothing else grants you permission to modify or 195 | distribute the Program or its derivative works. These actions are 196 | prohibited by law if you do not accept this License. Therefore, by 197 | modifying or distributing the Program (or any work based on the 198 | Program), you indicate your acceptance of this License to do so, and 199 | all its terms and conditions for copying, distributing or modifying 200 | the Program or works based on it. 201 | 202 | 6. Each time you redistribute the Program (or any work based on the 203 | Program), the recipient automatically receives a license from the 204 | original licensor to copy, distribute or modify the Program subject to 205 | these terms and conditions. You may not impose any further 206 | restrictions on the recipients' exercise of the rights granted herein. 207 | You are not responsible for enforcing compliance by third parties to 208 | this License. 209 | 210 | 7. If, as a consequence of a court judgment or allegation of patent 211 | infringement or for any other reason (not limited to patent issues), 212 | conditions are imposed on you (whether by court order, agreement or 213 | otherwise) that contradict the conditions of this License, they do not 214 | excuse you from the conditions of this License. If you cannot 215 | distribute so as to satisfy simultaneously your obligations under this 216 | License and any other pertinent obligations, then as a consequence you 217 | may not distribute the Program at all. For example, if a patent 218 | license would not permit royalty-free redistribution of the Program by 219 | all those who receive copies directly or indirectly through you, then 220 | the only way you could satisfy both it and this License would be to 221 | refrain entirely from distribution of the Program. 222 | 223 | If any portion of this section is held invalid or unenforceable under 224 | any particular circumstance, the balance of the section is intended to 225 | apply and the section as a whole is intended to apply in other 226 | circumstances. 227 | 228 | It is not the purpose of this section to induce you to infringe any 229 | patents or other property right claims or to contest validity of any 230 | such claims; this section has the sole purpose of protecting the 231 | integrity of the free software distribution system, which is 232 | implemented by public license practices. Many people have made 233 | generous contributions to the wide range of software distributed 234 | through that system in reliance on consistent application of that 235 | system; it is up to the author/donor to decide if he or she is willing 236 | to distribute software through any other system and a licensee cannot 237 | impose that choice. 238 | 239 | This section is intended to make thoroughly clear what is believed to 240 | be a consequence of the rest of this License. 241 | 242 | 8. If the distribution and/or use of the Program is restricted in 243 | certain countries either by patents or by copyrighted interfaces, the 244 | original copyright holder who places the Program under this License 245 | may add an explicit geographical distribution limitation excluding 246 | those countries, so that distribution is permitted only in or among 247 | countries not thus excluded. In such case, this License incorporates 248 | the limitation as if written in the body of this License. 249 | 250 | 9. The Free Software Foundation may publish revised and/or new versions 251 | of the General Public License from time to time. Such new versions will 252 | be similar in spirit to the present version, but may differ in detail to 253 | address new problems or concerns. 254 | 255 | Each version is given a distinguishing version number. If the Program 256 | specifies a version number of this License which applies to it and "any 257 | later version", you have the option of following the terms and conditions 258 | either of that version or of any later version published by the Free 259 | Software Foundation. If the Program does not specify a version number of 260 | this License, you may choose any version ever published by the Free Software 261 | Foundation. 262 | 263 | 10. If you wish to incorporate parts of the Program into other free 264 | programs whose distribution conditions are different, write to the author 265 | to ask for permission. For software which is copyrighted by the Free 266 | Software Foundation, write to the Free Software Foundation; we sometimes 267 | make exceptions for this. Our decision will be guided by the two goals 268 | of preserving the free status of all derivatives of our free software and 269 | of promoting the sharing and reuse of software generally. 270 | 271 | NO WARRANTY 272 | 273 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 274 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 275 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 276 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 277 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 278 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 279 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 280 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 281 | REPAIR OR CORRECTION. 282 | 283 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 284 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 285 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 286 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 287 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 288 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 289 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 290 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 291 | POSSIBILITY OF SUCH DAMAGES. 292 | 293 | END OF TERMS AND CONDITIONS 294 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | [![License](https://img.shields.io/pypi/l/easyleed.svg)](https://github.com/andim/easyleed/blob/master/LICENSE) 3 | [![Latest release](https://img.shields.io/pypi/v/easyleed.svg)](https://pypi.python.org/pypi/easyleed) 4 | [![Py2.7/3.x](https://img.shields.io/pypi/pyversions/easyleed.svg)](https://pypi.python.org/pypi/easyleed) 5 | ![Status](https://img.shields.io/pypi/status/easyleed.svg) 6 | 7 | 8 | # EasyLEED : Automated extraction of intensity-energy spectra from LEED patterns. 9 | 10 | EasyLEED facilitates data analysis of images obtained by low-energy electron diffraction, a common technique in surface science. It aims to automate the process of extracting intensity-energy spectra from a series of diffraction patterns acquired at different beam energies. At its core a tracking algorithm exploiting the specifics of the underlying physics (see [paper](http://dx.doi.org/10.1016/j.cpc.2012.02.019)) allows to link the position of the diffraction maxima between subsequent images. 11 | 12 | See also http://andim.github.io/easyleed/. 13 | 14 | ## Installation 15 | 16 | EasyLEED is on [PyPI](https://pypi.python.org/pypi/easyleed/) so you can install it using `pip install easyleed`. 17 | 18 | ## Documentation 19 | 20 | You can access the documentation [online](http://andim.github.io/easyleed/). If you install from source you can generate a local version by running `make html` from the `doc` directory. 21 | 22 | ## Support and contributing 23 | 24 | For bug reports and enhancement requests use the [Github issue tool](http://github.com/andim/easyleed/issues/new), or (even better!) open a [pull request](http://github.com/andim/easyleed/pulls) with relevant changes. If you have any questions don't hesitate to contact the authors by email (andimscience@gmail.com, feranick@hotmail.com) or Twitter ([@andimscience](http://twitter.com/andimscience)). 25 | -------------------------------------------------------------------------------- /changelog: -------------------------------------------------------------------------------- 1 | Changelog 2 | ========== 3 | 4 | Version 2.5 5 | ----------- 6 | 7 | (NF): 8 | - intensities are correctly saved as *.csv, along with spot and center locations 9 | - added dependency: use of pandas dataframes for internal data tracking 10 | - spots and center locations are saved/loaded as csv (old pkl format can still be read) 11 | - fix crash when deselecting average plot after clearing plot 12 | - UI: selecting a spot after analysis highlights the corresponding curve 13 | - UI: Removing a spot also removes the corresponding curve 14 | - Runs can now be paused-resumed with spots in different positions 15 | 16 | Version 2.4 17 | ----------- 18 | 19 | (NF): 20 | - revamp of configuration handling (now uses *.ini format) 21 | - fixed crash if no image selected in file open widget 22 | - changed filetype ending for spot position export to *.pkl 23 | - some improvements to plotting ui 24 | 25 | (AM): 26 | - added (optional) peak detection algorithms from scikit-image to be used in 27 | the tracking (beware: this feature is still experimental) 28 | - optional dependencies (skimage, fits) can now be installed via pip 29 | 30 | Version 2.3.4 31 | ------------- 32 | 33 | (Jan Lachnitt): 34 | - preserve order and numbering of spots everywhere 35 | 36 | (AM): 37 | - load config from anywhere on the path as "easyleedconfig" 38 | 39 | Version 2.3.3 40 | ------------- 41 | 42 | (Jan Lachnitt): 43 | - fix to open/save with Qt4 44 | - fix for 16-bit images 45 | - more robust default regex for energy in filename 46 | 47 | (AM): 48 | - bug fixes to plotting and parameter import/export 49 | 50 | Version 2.3.2 51 | ------------- 52 | 53 | - file loading now without needing to choose the file type 54 | 55 | Version 2.3.1 56 | ------------- 57 | 58 | - added support for Qt5 59 | - added filename defaults for the saving procedures 60 | - fixes to logging mechanism 61 | 62 | Version 2.3 63 | ----------- 64 | 65 | - GUI bug fixes for Python 3 66 | - added header to intensity save file 67 | 68 | Version 2.2 69 | ----------- 70 | 71 | - Python 3 support 72 | - stand-alone script now called with easyleed instead of easyleed.pyw 73 | 74 | Version 2.1 75 | ----------- 76 | 77 | - added support for different Qt wrappers 78 | - renamed run-gui.py to easyleed.pyw 79 | - added setup.py to allow easier installation 80 | 81 | 82 | Version 2.0 83 | ------------ 84 | 85 | (NF): 86 | - New energy slider for fast scrolling over opened frames. 87 | - A “custom energy button” is added to move to a particular energy framework. 88 | - Integration windows (when present) are tracked with the energy slider. 89 | - Redesigned Set Parameters panel. 90 | - New entry in Set Parameters panel for size of integration circle. 91 | - Size of default intensity integration circle can now be restored with Default button in Set Parameters 92 | - Set Preference panel is always visible (i.e. can be accessed with no images loaded). 93 | - Added "Apply" button in Set Preference, so that settings can be applied without closing the Set Preference panel. 94 | - Intensity files now have an extra tag in the filename 95 | * _bs.int: if the intensities were extracted with background subtraction. 96 | * _no-bs.int: if the intensities were not extracted with background subtraction. 97 | - Added option to display I(E) spectra during acquisition (live plotting). 98 | - Removed Plotting option panel (no longer needed). 99 | - Average intensities can be now plotted “on the fly” using a checkbox in the Plot. 100 | - Smoothed (cubic spline) average intensities can be now plotted using a checkbox in the Plot. 101 | * Smoothing parameters act “live” on the plot (just type and press Enter, the plot is automatically updated). 102 | - Average and smoothed averages can be saved into their own intensity files. 103 | - Improved control over clearing plots: there is now a dedicated button in Plot. 104 | - Added “Stop” button to stop acquisition (somehow missing). 105 | - Running an acquisition without any spot selected is prevented. 106 | - Added ability to take screenshots from menu (including integration windows and energy in filename). 107 | - Moved buttons “next, previous” close to the slider. 108 | - Added ability to acquire I(frame) - fix energy acquisitions. 109 | - Saving and loading custom settings now works properly. 110 | - Location of the integration windows can be saved and reloaded. 111 | - Location of the center of the screen can be saved and reloaded. 112 | - Hovering over integration windows shows the windows number for rapid identification of corresponding curve. 113 | - Legend can be displayed in plot with reference to the integration window. 114 | - Introduced toolbar in plotting window. 115 | - Reorganized position of windows/panels to avoid overlapping. 116 | - New About Box. 117 | - Restored working Help (redirects to main website - User Manual). 118 | - Extensive optimization of user workflow. 119 | - Several bug fixes and optimization. 120 | 121 | API: 122 | - New loader.goto(energy): load image at specified energy. 123 | - New loader.energySteps(energy): get # frame steps for custom beam energy. 124 | 125 | 126 | (AM): 127 | - added ability to set center by user 128 | - energy label now displayed atop of the image 129 | - plotting refactored (now faster) 130 | - new config parameter fitRegionFactor to set size of search region 131 | 132 | Changes in defaults: 133 | - default size of integration window 10 --> 20 134 | - Tracking input precision 5 --> 2 135 | - Tracking process noise in position 0.04 --> 0.1 136 | 137 | -------------------------------------------------------------------------------- /doc/.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | -------------------------------------------------------------------------------- /doc/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 | # Internal variables. 11 | PAPEROPT_a4 = -D latex_paper_size=a4 12 | PAPEROPT_letter = -D latex_paper_size=letter 13 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source 14 | # the i18n builder cannot share the environment and doctrees with the others 15 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source 16 | SOURCE = $(shell find ../source/ -name "*.py") 17 | 18 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext show uplad zipsource 19 | 20 | help: 21 | @echo "Please use \`make ' where is one of" 22 | @echo " html to make standalone HTML files" 23 | @echo " dirhtml to make HTML files named index.html in directories" 24 | @echo " singlehtml to make a single large HTML file" 25 | @echo " pickle to make pickle files" 26 | @echo " json to make JSON files" 27 | @echo " htmlhelp to make HTML files and a HTML help project" 28 | @echo " qthelp to make HTML files and a qthelp project" 29 | @echo " devhelp to make HTML files and a Devhelp project" 30 | @echo " epub to make an epub" 31 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 32 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 33 | @echo " text to make text files" 34 | @echo " man to make manual pages" 35 | @echo " texinfo to make Texinfo files" 36 | @echo " info to make Texinfo files and run them through makeinfo" 37 | @echo " gettext to make PO message catalogs" 38 | @echo " changes to make an overview of all changed/added/deprecated items" 39 | @echo " linkcheck to check all external links for integrity" 40 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 41 | 42 | clean: 43 | -rm -rf $(BUILDDIR)/* 44 | 45 | zipsource: source/_static/source.zip 46 | 47 | source/_static/source.zip: $(SOURCE) 48 | zip source/_static/source.zip $(SOURCE) 49 | 50 | html: zipsource 51 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 52 | @echo 53 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 54 | 55 | dirhtml: 56 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 57 | @echo 58 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 59 | 60 | singlehtml: 61 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 62 | @echo 63 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 64 | 65 | pickle: 66 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 67 | @echo 68 | @echo "Build finished; now you can process the pickle files." 69 | 70 | json: 71 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 72 | @echo 73 | @echo "Build finished; now you can process the JSON files." 74 | 75 | htmlhelp: 76 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 77 | @echo 78 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 79 | ".hhp project file in $(BUILDDIR)/htmlhelp." 80 | 81 | qthelp: 82 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 83 | @echo 84 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 85 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 86 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Easyleed.qhcp" 87 | @echo "To view the help file:" 88 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Easyleed.qhc" 89 | 90 | devhelp: 91 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 92 | @echo 93 | @echo "Build finished." 94 | @echo "To view the help file:" 95 | @echo "# mkdir -p $$HOME/.local/share/devhelp/Easyleed" 96 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Easyleed" 97 | @echo "# devhelp" 98 | 99 | epub: 100 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 101 | @echo 102 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 103 | 104 | text: 105 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 106 | @echo 107 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 108 | 109 | man: 110 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 111 | @echo 112 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 113 | 114 | texinfo: 115 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 116 | @echo 117 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 118 | @echo "Run \`make' in that directory to run these through makeinfo" \ 119 | "(use \`make info' here to do that automatically)." 120 | 121 | info: 122 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 123 | @echo "Running Texinfo files through makeinfo..." 124 | make -C $(BUILDDIR)/texinfo info 125 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 126 | 127 | gettext: 128 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 129 | @echo 130 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 131 | 132 | changes: 133 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 134 | @echo 135 | @echo "The overview file is in $(BUILDDIR)/changes." 136 | 137 | linkcheck: 138 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 139 | @echo 140 | @echo "Link check complete; look for any errors in the above output " \ 141 | "or in $(BUILDDIR)/linkcheck/output.txt." 142 | 143 | doctest: 144 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 145 | @echo "Testing of doctests in the sources finished, look at the " \ 146 | "results in $(BUILDDIR)/doctest/output.txt." 147 | 148 | show: 149 | firefox build/html/index.html & 150 | 151 | ghpages: html 152 | cp -r build/html/* ../../easyleed-github-pages/ 153 | 154 | -------------------------------------------------------------------------------- /doc/source/_static/.gitignore: -------------------------------------------------------------------------------- 1 | source.zip 2 | -------------------------------------------------------------------------------- /doc/source/_static/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andim/easyleed/bbe4acd2b5eeb0f5d50a8d018780ace3afd7ff73/doc/source/_static/icon.ico -------------------------------------------------------------------------------- /doc/source/_static/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andim/easyleed/bbe4acd2b5eeb0f5d50a8d018780ace3afd7ff73/doc/source/_static/icon.png -------------------------------------------------------------------------------- /doc/source/_static/illustration.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andim/easyleed/bbe4acd2b5eeb0f5d50a8d018780ace3afd7ff73/doc/source/_static/illustration.png -------------------------------------------------------------------------------- /doc/source/_static/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andim/easyleed/bbe4acd2b5eeb0f5d50a8d018780ace3afd7ff73/doc/source/_static/logo.png -------------------------------------------------------------------------------- /doc/source/_static/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 22 | 24 | 26 | 32 | 33 | 35 | 41 | 42 | 44 | 50 | 51 | 53 | 59 | 60 | 62 | 68 | 69 | 71 | 77 | 78 | 80 | 86 | 87 | 89 | 95 | 96 | 98 | 104 | 105 | 107 | 113 | 114 | 116 | 122 | 123 | 125 | 131 | 132 | 134 | 140 | 141 | 143 | 149 | 150 | 152 | 158 | 159 | 161 | 167 | 168 | 170 | 176 | 177 | 179 | 185 | 186 | 188 | 194 | 195 | 197 | 203 | 204 | 206 | 212 | 213 | 215 | 221 | 222 | 224 | 230 | 231 | 233 | 239 | 240 | 242 | 248 | 249 | 251 | 257 | 258 | 260 | 266 | 267 | 269 | 275 | 276 | 278 | 284 | 285 | 287 | 293 | 294 | 296 | 302 | 303 | 305 | 311 | 312 | 314 | 320 | 321 | 323 | 329 | 330 | 332 | 338 | 339 | 341 | 347 | 348 | 350 | 356 | 357 | 359 | 365 | 366 | 368 | 374 | 375 | 377 | 383 | 384 | 386 | 392 | 393 | 395 | 401 | 402 | 404 | 410 | 411 | 413 | 419 | 420 | 422 | 428 | 429 | 430 | 448 | 450 | 451 | 453 | image/svg+xml 454 | 456 | 457 | 458 | 459 | 460 | 465 | 470 | 478 | 479 | 484 | 492 | 493 | 498 | 506 | 507 | 512 | 520 | 521 | 526 | 534 | 535 | 540 | 548 | 549 | 552 | EasyLEED Low Energy Electron DiffractionI(E)-spectra analysis 581 | 582 | 590 | 594 | 602 | 610 | 618 | 626 | 634 | 642 | 643 | 647 | 655 | 663 | 671 | 679 | 687 | 695 | 696 | 701 | 709 | 710 | 715 | 723 | 724 | 729 | 737 | 738 | 739 | 740 | -------------------------------------------------------------------------------- /doc/source/_templates/indexsidebar.html: -------------------------------------------------------------------------------- 1 | 2 |

Timeline

3 | 4 | 15 | 16 | (see also the changelog) 17 | 18 |

Contact

19 | 20 | If you have any questions, comments or bug reports please contact us! 21 | 22 |

Links

23 | 24 | 28 | 29 | -------------------------------------------------------------------------------- /doc/source/api.rst: -------------------------------------------------------------------------------- 1 | The EasyLEED API 2 | ================== 3 | 4 | .. automodule:: easyleed 5 | :members: 6 | -------------------------------------------------------------------------------- /doc/source/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Easyleed documentation build configuration file, created by 4 | # sphinx-quickstart on Sun Jun 3 18:46:14 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 | # If extensions (or modules to document with autodoc) are in another directory, 17 | # add these directories to sys.path here. If the directory is relative to the 18 | # documentation root, use os.path.abspath to make it absolute, like shown here. 19 | sys.path.insert(0, os.path.abspath('../../source/')) 20 | import easyleed 21 | 22 | # -- General configuration ----------------------------------------------------- 23 | 24 | # If your documentation needs a minimal Sphinx version, state it here. 25 | #needs_sphinx = '1.0' 26 | 27 | # Add any Sphinx extension module names here, as strings. They can be extensions 28 | # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. 29 | extensions = ['sphinx.ext.autodoc', 'sphinx.ext.todo', 'sphinx.ext.imgmath', 'sphinx.ext.viewcode'] 30 | 31 | # Add any paths that contain templates here, relative to this directory. 32 | templates_path = ['_templates'] 33 | 34 | # The suffix of source filenames. 35 | source_suffix = '.rst' 36 | 37 | # The encoding of source files. 38 | #source_encoding = 'utf-8-sig' 39 | 40 | # The master toctree document. 41 | master_doc = 'index' 42 | 43 | # General information about the project. 44 | project = u'EasyLEED' 45 | copyright = u'2010-18, %s' % easyleed.__author__ 46 | 47 | # The version info for the project you're documenting, acts as replacement for 48 | # |version| and |release|, also used in various other places throughout the 49 | # built documents. 50 | # 51 | # The short X.Y version. 52 | version = easyleed.__version__ 53 | # The full version, including alpha/beta/rc tags. 54 | release = version 55 | 56 | # The language for content autogenerated by Sphinx. Refer to documentation 57 | # for a list of supported languages. 58 | #language = None 59 | 60 | # There are two options for replacing |today|: either, you set today to some 61 | # non-false value, then it is used: 62 | #today = '' 63 | # Else, today_fmt is used as the format for a strftime call. 64 | today_fmt = '%Y-%m-%d' 65 | 66 | # List of patterns, relative to source directory, that match files and 67 | # directories to ignore when looking for source files. 68 | exclude_patterns = [] 69 | 70 | # The reST default role (used for this markup: `text`) to use for all documents. 71 | #default_role = None 72 | 73 | # If true, '()' will be appended to :func: etc. cross-reference text. 74 | #add_function_parentheses = True 75 | 76 | # If true, the current module name will be prepended to all description 77 | # unit titles (such as .. function::). 78 | #add_module_names = True 79 | 80 | # If true, sectionauthor and moduleauthor directives will be shown in the 81 | # output. They are ignored by default. 82 | #show_authors = False 83 | 84 | # The name of the Pygments (syntax highlighting) style to use. 85 | #pygments_style = 'sphinx' 86 | 87 | # A list of ignored prefixes for module index sorting. 88 | #modindex_common_prefix = [] 89 | 90 | 91 | # -- Options for HTML output --------------------------------------------------- 92 | 93 | 94 | # Add any paths that contain custom themes here, relative to this directory. 95 | html_theme_path = ["."] 96 | 97 | # The theme to use for HTML and HTML Help pages. See the documentation for 98 | # a list of builtin themes. 99 | html_theme = 'easyleed' 100 | 101 | # Theme options are theme-specific and customize the look and feel of a theme 102 | # further. For a list of options available for each theme, see the 103 | # documentation. 104 | html_theme_options = { 'full_logo' : True } 105 | 106 | # The name for this set of Sphinx documents. If None, it defaults to 107 | # " v documentation". 108 | #html_title = None 109 | 110 | # A shorter title for the navigation bar. Default is the same as html_title. 111 | #html_short_title = None 112 | 113 | # The name of an image file (relative to this directory) to place at the top 114 | # of the sidebar. 115 | html_logo = '_static/logo.png' 116 | 117 | # The name of an image file (within the static path) to use as favicon of the 118 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 119 | # pixels large. 120 | html_favicon = '_static/icon.ico' 121 | 122 | # Add any paths that contain custom static files (such as style sheets) here, 123 | # relative to this directory. They are copied after the builtin static files, 124 | # so a file named "default.css" will overwrite the builtin "default.css". 125 | html_static_path = ['_static'] 126 | 127 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 128 | # using the given strftime format. 129 | html_last_updated_fmt = '%Y-%m-%d' 130 | 131 | # If true, SmartyPants will be used to convert quotes and dashes to 132 | # typographically correct entities. 133 | #html_use_smartypants = True 134 | 135 | # Custom sidebar templates, maps document names to template names. 136 | html_sidebars = { 'index' : ['indexsidebar.html'], '**': [] } 137 | 138 | # Additional templates that should be rendered to pages, maps page names to 139 | # template names. 140 | #html_additional_pages = {} 141 | 142 | # If false, no module index is generated. 143 | #html_domain_indices = True 144 | 145 | # If false, no index is generated. 146 | #html_use_index = True 147 | 148 | # If true, the index is split into individual pages for each letter. 149 | #html_split_index = False 150 | 151 | # If true, links to the reST sources are added to the pages. 152 | html_show_sourcelink = False 153 | 154 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 155 | #html_show_sphinx = True 156 | 157 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 158 | #html_show_copyright = True 159 | 160 | # If true, an OpenSearch description file will be output, and all pages will 161 | # contain a tag referring to it. The value of this option must be the 162 | # base URL from which the finished HTML is served. 163 | #html_use_opensearch = '' 164 | 165 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 166 | html_file_suffix = '.html' 167 | -------------------------------------------------------------------------------- /doc/source/easyleed.bib: -------------------------------------------------------------------------------- 1 | @article{mayer-2012, 2 | title={A novel method for the extraction of intensity--energy spectra from low-energy electron diffraction patterns}, 3 | author={Mayer, Andreas and Salopaasi, Hanna and Pussi, Katariina and Diehl, Renee D}, 4 | journal={Computer Physics Communications}, 5 | volume={183}, 6 | number={7}, 7 | pages={1443--1447}, 8 | year={2012}, 9 | publisher={Elsevier} 10 | } 11 | 12 | -------------------------------------------------------------------------------- /doc/source/easyleed/layout.html: -------------------------------------------------------------------------------- 1 | {# 2 | easyleed/layout.html 3 | ~~~~~~~~~~~~~~~~~ 4 | 5 | Sphinx layout template for the easyleed theme. 6 | 7 | :copyright: Copyright 2012 by Andreas Mayer. 8 | :license: BSD, see LICENSE for details. 9 | #} 10 | {% extends "basic/layout.html" %} 11 | {% set title = 'EasyLEED: LEED I(E)-spectra analysis' %} 12 | 13 | {% set script_files = script_files + ['_static/theme_extras.js'] %} 14 | {% set css_files = css_files + ['_static/print.css'] %} 15 | 16 | {# do not display relbars #} 17 | {% block relbar1 %}{% endblock %} 18 | {% block relbar2 %} 19 | Fork me on GitHub 21 | {% endblock %} 22 | {%- block sidebarlogo %}{% endblock %} 23 | 24 | {% macro nav() %} 25 | {% endmacro %} 26 | 27 | {% block content %} 28 |
29 | {%- block haikuheader %} 30 | {%- if theme_full_logo != "false" %} 31 | 32 | 33 | 34 | {%- else %} 35 | {%- if logo -%} 36 | 37 | {%- endif -%} 38 |

39 | {{ shorttitle|e }}

40 |

{{ title|striptags|e }}

41 | {%- endif %} 42 | {%- endblock %} 43 |
44 |
45 | {{ nav() }} 46 |
47 | {% block sidebar1 %}{{ sidebar() }}{% endblock %} 48 |
49 |
50 | {%- if render_sidebar %} 51 |
52 | {%- endif %} 53 |
54 | {% block body %}{% endblock %} 55 |
56 | {%- if render_sidebar %} 57 |
58 | {%- endif %} 59 |
60 |
61 |
62 | {{ nav() }} 63 |
64 | {% endblock %} 65 | -------------------------------------------------------------------------------- /doc/source/easyleed/static/alert_info_32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andim/easyleed/bbe4acd2b5eeb0f5d50a8d018780ace3afd7ff73/doc/source/easyleed/static/alert_info_32.png -------------------------------------------------------------------------------- /doc/source/easyleed/static/alert_warning_32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andim/easyleed/bbe4acd2b5eeb0f5d50a8d018780ace3afd7ff73/doc/source/easyleed/static/alert_warning_32.png -------------------------------------------------------------------------------- /doc/source/easyleed/static/bg-page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andim/easyleed/bbe4acd2b5eeb0f5d50a8d018780ace3afd7ff73/doc/source/easyleed/static/bg-page.png -------------------------------------------------------------------------------- /doc/source/easyleed/static/bullet_orange.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andim/easyleed/bbe4acd2b5eeb0f5d50a8d018780ace3afd7ff73/doc/source/easyleed/static/bullet_orange.png -------------------------------------------------------------------------------- /doc/source/easyleed/static/easyleed.css_t: -------------------------------------------------------------------------------- 1 | /* 2 | * easyleed.css_t 3 | * ~~~~~~~~~~~ 4 | * 5 | * Sphinx stylesheet -- easyleed theme. 6 | * 7 | * Adapted from http://haiku-os.org/docs/Haiku-doc.css. 8 | * 9 | * Original copyright message: 10 | * 11 | * Copyright 2008-2009, Haiku. All rights reserved. 12 | * Distributed under the terms of the MIT License. 13 | * 14 | * Authors: 15 | * Francois Revol 16 | * Stephan Assmus 17 | * Braden Ewing 18 | * Humdinger 19 | * 20 | * :copyright: Copyright 2012 by Andreas Mayer. 21 | * :license: BSD, see LICENSE for details. 22 | * 23 | */ 24 | 25 | @import url("basic.css"); 26 | 27 | html { 28 | margin: 0px; 29 | padding: 0px; 30 | background: #FFF url(bg-page.png) top left repeat-x; 31 | } 32 | 33 | body { 34 | line-height: 1.5; 35 | margin: auto; 36 | padding: 0px; 37 | font-family: "DejaVu Sans", Arial, Helvetica, sans-serif; 38 | min-width: 59em; 39 | max-width: 70em; 40 | color: {{ theme_textcolor }}; 41 | } 42 | 43 | div.footer { 44 | padding: 8px; 45 | font-size: 11px; 46 | text-align: center; 47 | letter-spacing: 0.5px; 48 | } 49 | 50 | /* link colors and text decoration */ 51 | 52 | a:link { 53 | font-weight: bold; 54 | text-decoration: none; 55 | color: {{ theme_linkcolor }}; 56 | } 57 | 58 | a:visited { 59 | font-weight: bold; 60 | text-decoration: none; 61 | color: {{ theme_visitedlinkcolor }}; 62 | } 63 | 64 | a:hover, a:active { 65 | text-decoration: underline; 66 | color: {{ theme_hoverlinkcolor }}; 67 | } 68 | 69 | /* Some headers act as anchors, don't give them a hover effect */ 70 | 71 | h1 a:hover, a:active { 72 | text-decoration: none; 73 | color: {{ theme_headingcolor }}; 74 | } 75 | 76 | h2 a:hover, a:active { 77 | text-decoration: none; 78 | color: {{ theme_headingcolor }}; 79 | } 80 | 81 | h3 a:hover, a:active { 82 | text-decoration: none; 83 | color: {{ theme_headingcolor }}; 84 | } 85 | 86 | h4 a:hover, a:active { 87 | text-decoration: none; 88 | color: {{ theme_headingcolor }}; 89 | } 90 | 91 | a.headerlink { 92 | color: #a7ce38; 93 | padding-left: 5px; 94 | } 95 | 96 | a.headerlink:hover { 97 | color: #a7ce38; 98 | } 99 | 100 | /* basic text elements */ 101 | 102 | div.content { 103 | margin-top: 1em; 104 | margin-left: 2em; 105 | margin-right: 1em; 106 | margin-bottom: 2em; 107 | font-size: 0.9em; 108 | } 109 | 110 | /* heading and navigation */ 111 | 112 | div.header { 113 | position: relative; 114 | left: 0px; 115 | top: 0px; 116 | height: 85px; 117 | /* background: #eeeeee; */ 118 | padding: 0 40px; 119 | } 120 | div.header h1 { 121 | font-size: 1.6em; 122 | font-weight: normal; 123 | letter-spacing: 1px; 124 | color: {{ theme_headingcolor }}; 125 | border: 0; 126 | margin: 0; 127 | padding-top: 15px; 128 | } 129 | div.header h1 a { 130 | font-weight: normal; 131 | color: {{ theme_headingcolor }}; 132 | } 133 | div.header h2 { 134 | font-size: 1.3em; 135 | font-weight: normal; 136 | letter-spacing: 1px; 137 | text-transform: uppercase; 138 | color: #aaa; 139 | border: 0; 140 | margin-top: -3px; 141 | padding: 0; 142 | } 143 | 144 | div.header img.rightlogo { 145 | float: right; 146 | } 147 | 148 | 149 | div.title { 150 | font-size: 1.3em; 151 | font-weight: bold; 152 | color: {{ theme_headingcolor }}; 153 | border-bottom: dotted thin #e0e0e0; 154 | margin-bottom: 25px; 155 | } 156 | div.topnav { 157 | /* background: #e0e0e0; */ 158 | } 159 | div.topnav p { 160 | margin-top: 0; 161 | margin-left: 40px; 162 | margin-right: 40px; 163 | margin-bottom: 0px; 164 | text-align: right; 165 | font-size: 0.8em; 166 | } 167 | div.bottomnav { 168 | background: #eeeeee; 169 | } 170 | div.bottomnav p { 171 | margin-right: 40px; 172 | text-align: right; 173 | font-size: 0.8em; 174 | } 175 | 176 | a.uplink { 177 | font-weight: normal; 178 | } 179 | 180 | 181 | /* contents box */ 182 | 183 | table.index { 184 | margin: 0px 0px 30px 30px; 185 | padding: 1px; 186 | border-width: 1px; 187 | border-style: dotted; 188 | border-color: #e0e0e0; 189 | } 190 | table.index tr.heading { 191 | background-color: #e0e0e0; 192 | text-align: center; 193 | font-weight: bold; 194 | font-size: 1.1em; 195 | } 196 | table.index tr.index { 197 | background-color: #eeeeee; 198 | } 199 | table.index td { 200 | padding: 5px 20px; 201 | } 202 | 203 | table.index a:link, table.index a:visited { 204 | font-weight: normal; 205 | text-decoration: none; 206 | color: {{ theme_linkcolor }}; 207 | } 208 | table.index a:hover, table.index a:active { 209 | text-decoration: underline; 210 | color: {{ theme_hoverlinkcolor }}; 211 | } 212 | 213 | /* Sidebar settings */ 214 | 215 | div.bodywrapper { 216 | margin: 0 calc({{ theme_sidebarwidth|toint + 1 }}px + 1em) 0 0; 217 | border-right: 1px solid #ccc; 218 | } 219 | 220 | div.body { 221 | margin: 0; 222 | padding: 0.5em 2em 2em 2em; 223 | } 224 | 225 | div.sphinxsidebarwrapper { 226 | padding: 0; 227 | } 228 | 229 | div.sphinxsidebar { 230 | margin: 0; 231 | padding: 0.5em 1em 1em 0; 232 | width: theme_sidebarwidth; 233 | float: right; 234 | font-size: 1em; 235 | text-align: left; 236 | } 237 | 238 | div.sphinxsidebar h3, div.sphinxsidebar h4 { 239 | margin: 1em 0 0.5em 0; 240 | font-size: 1em; 241 | padding: 0.1em 0 0.1em 0.5em; 242 | color: white; 243 | border: 1px solid #86989B; 244 | background-color: #AFC1C4; 245 | } 246 | 247 | div.sphinxsidebar h3 a { 248 | color: white; 249 | } 250 | 251 | div.sphinxsidebar p { 252 | color: black; 253 | text-align: left; 254 | } 255 | 256 | div.sphinxsidebar ul { 257 | padding-left: 1.5em; 258 | margin-top: 7px; 259 | margin-left: 8em 260 | padding: 0; 261 | line-height: 130%; 262 | } 263 | 264 | div.sphinxsidebar ul ul { 265 | margin-left: 20px; 266 | } 267 | 268 | /* Haiku User Guide styles and layout */ 269 | 270 | /* Rounded corner boxes */ 271 | /* Common declarations */ 272 | div.admonition { 273 | -webkit-border-radius: 10px; 274 | -khtml-border-radius: 10px; 275 | -moz-border-radius: 10px; 276 | border-radius: 10px; 277 | border-style: dotted; 278 | border-width: thin; 279 | border-color: #dcdcdc; 280 | padding: 10px 15px 10px 15px; 281 | margin-bottom: 15px; 282 | margin-top: 15px; 283 | } 284 | div.note { 285 | padding: 10px 15px 10px 80px; 286 | background: #e4ffde url(alert_info_32.png) 15px 15px no-repeat; 287 | min-height: 42px; 288 | } 289 | div.warning { 290 | padding: 10px 15px 10px 80px; 291 | background: #fffbc6 url(alert_warning_32.png) 15px 15px no-repeat; 292 | min-height: 42px; 293 | } 294 | div.seealso { 295 | background: #e4ffde; 296 | } 297 | 298 | /* More layout and styles */ 299 | h1 { 300 | font-size: 1.3em; 301 | font-weight: bold; 302 | color: {{ theme_headingcolor }}; 303 | border-bottom: dotted thin #e0e0e0; 304 | margin-top: 30px; 305 | } 306 | 307 | h2 { 308 | font-size: 1.2em; 309 | font-weight: normal; 310 | color: {{ theme_headingcolor }}; 311 | border-bottom: dotted thin #e0e0e0; 312 | margin-top: 30px; 313 | } 314 | 315 | h3 { 316 | font-size: 1.1em; 317 | font-weight: normal; 318 | color: {{ theme_headingcolor }}; 319 | margin-top: 30px; 320 | } 321 | 322 | h4 { 323 | font-size: 1.0em; 324 | font-weight: normal; 325 | color: {{ theme_headingcolor }}; 326 | margin-top: 30px; 327 | } 328 | 329 | p { 330 | text-align: justify; 331 | } 332 | 333 | p.last { 334 | margin-bottom: 0; 335 | } 336 | 337 | ol { 338 | padding-left: 20px; 339 | } 340 | 341 | ul { 342 | padding-left: 5px; 343 | margin-top: 3px; 344 | } 345 | 346 | li { 347 | line-height: 1.3; 348 | } 349 | 350 | div.content ul > li { 351 | -moz-background-clip:border; 352 | -moz-background-inline-policy:continuous; 353 | -moz-background-origin:padding; 354 | background: transparent url(bullet_orange.png) no-repeat scroll left 0.45em; 355 | list-style-image: none; 356 | list-style-type: none; 357 | padding: 0 0 0 1.666em; 358 | margin-bottom: 3px; 359 | } 360 | 361 | td { 362 | vertical-align: top; 363 | } 364 | 365 | tt { 366 | background-color: #e2e2e2; 367 | font-size: 1.0em; 368 | font-family: monospace; 369 | } 370 | 371 | pre { 372 | border-color: #0c3762; 373 | border-style: dotted; 374 | border-width: thin; 375 | margin: 0 0 12px 0; 376 | padding: 0.8em; 377 | background-color: #f0f0f0; 378 | } 379 | 380 | hr { 381 | border-top: 1px solid #ccc; 382 | border-bottom: 0; 383 | border-right: 0; 384 | border-left: 0; 385 | margin-bottom: 10px; 386 | margin-top: 20px; 387 | } 388 | 389 | /* printer only pretty stuff */ 390 | @media print { 391 | .noprint { 392 | display: none; 393 | } 394 | /* for acronyms we want their definitions inlined at print time */ 395 | acronym[title]:after { 396 | font-size: small; 397 | content: " (" attr(title) ")"; 398 | font-style: italic; 399 | } 400 | /* and not have mozilla dotted underline */ 401 | acronym { 402 | border: none; 403 | } 404 | div.topnav, div.bottomnav, div.header, table.index { 405 | display: none; 406 | } 407 | div.content { 408 | margin: 0px; 409 | padding: 0px; 410 | } 411 | html { 412 | background: #FFF; 413 | } 414 | } 415 | 416 | .viewcode-back { 417 | font-family: "DejaVu Sans", Arial, Helvetica, sans-serif; 418 | } 419 | 420 | div.viewcode-block:target { 421 | background-color: #f4debf; 422 | border-top: 1px solid #ac9; 423 | border-bottom: 1px solid #ac9; 424 | margin: -1px -12px; 425 | padding: 0 12px; 426 | } 427 | -------------------------------------------------------------------------------- /doc/source/easyleed/theme.conf: -------------------------------------------------------------------------------- 1 | [theme] 2 | inherit = basic 3 | stylesheet = easyleed.css 4 | pygments_style = autumn 5 | 6 | [options] 7 | full_logo = true 8 | textcolor = #333333 9 | headingcolor = #0c3762 10 | linkcolor = #dc3c01 11 | visitedlinkcolor = #892601 12 | hoverlinkcolor = #ff4500 13 | -------------------------------------------------------------------------------- /doc/source/index.rst: -------------------------------------------------------------------------------- 1 | Intro 2 | ===== 3 | 4 | The intensity of diffraction maxima in low energy electron diffraction patterns changes with the energy of the incident beam. By varying the beam energy and recording the intensity of the maxima at each step, structural information about the analyzed surface can be obtained. The purpose of the EasyLEED program is to facilitate the extraction of intensity-energy spectra from the experimental images. 5 | 6 | The user selects the spots he wants to track and the software then tries to automatically track the spots throughout all beam energies. An algorithm to determine the position of a spot in an image from the intensity information is combined with a dynamical model of the spot movement between successive beam energies to yield superior tracking performance. 7 | 8 | .. image:: /_static/illustration.* 9 | :scale: 75% 10 | 11 | Quickstart 12 | ========== 13 | 14 | EasyLEED is available from `PyPI `_ and can thus by installed by executing the following command in a console. 15 | 16 | :: 17 | 18 | $ pip install easyleed 19 | 20 | Once installed you can launch the program with the command 21 | 22 | :: 23 | 24 | $ easyleed 25 | 26 | Open a series of diffraction images at successive energies and click on a diffraction spot you want to track. Click run and of you go! 27 | 28 | Documentation 29 | ============= 30 | 31 | .. toctree:: 32 | :maxdepth: 1 33 | 34 | maninstall 35 | userdoc 36 | api 37 | 38 | 39 | Support & Contributing 40 | ====================== 41 | 42 | If you have problems or have suggestions of how to improve the software, please write us an `email `_ or `open an issue on Github `_. 43 | 44 | We would also be very happy about direct contributions that enhance and extend the functionality of EasyLEED. Simply fork the project on Github, hack away, and then open a pull request. 45 | 46 | 47 | Citing EasyLEED 48 | =============== 49 | 50 | If our software is useful to you, feel free to cite it: The algorithm on which the I(E)-spectra extraction relies is described in the following article (:download:`Download Bibtex-File`): 51 | 52 | A. Mayer, H. Salopaasi, K. Pussi, R.D. Diehl. A novel method for the extraction of intensity-energy spectra from low-energy electron diffraction patterns. Comput. Phys. Commun. 183, 1443-1447 (2012) 53 | 54 | The paper is available `at the publisher's website `_. If you are unable to access the paper at your institution, feel free to contact us via email for a preprint. 55 | 56 | Contributors 57 | ============ 58 | Development of EasyLEED was started by Andreas Mayer while working in Renee Diehl's lab (Penn State). Hanna Salopaasi has contributed to the user interface while working in Katariina Pussi's lab (Lappeenranta University of Technology). UI, core and usability improvements were contributed by Nicola Ferralis (Massachusetts Institute of Technology). 59 | -------------------------------------------------------------------------------- /doc/source/maninstall.rst: -------------------------------------------------------------------------------- 1 | Manual Installation 2 | =================== 3 | 4 | Download 5 | -------- 6 | 7 | EasyLEED is open-source software licensed under the GPL v2. The software can be downloaded as a zip-file (:download:`Download ZIP-File <_static/source.zip>`, last update |today|). Alternatively the developer version of the code can be obtained by cloning the git repository: ``_ 8 | 9 | Dependencies 10 | ------------ 11 | 12 | EasyLEED is written in `Python `_ and relies on the following libraries: 13 | 14 | - Python 2.7 or 3.5/3.6 ``_ 15 | - Qt4 or Qt5 ``_, along with either the respective PyQt version ``_ or PySide ``_ 16 | - Numpy >1.5 and Scipy >0.9 ``_ 17 | - Matplotlib >0.9 ``_ 18 | - Pillow (for .tif, .png, .jpg) ``_ 19 | - Pandas ``_ 20 | 21 | There are also two optional dependencies: 22 | - PyFITS ``_ for loading .fits file 23 | - Scikit-image ``_ for more functions for extracting spot positions. 24 | 25 | You can install these two dependencies using pip by indicating the extra dependencies in bracket `pip install easyleed[fits,skimage]`. 26 | 27 | Installing dependencies on Windows 28 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 29 | 30 | The simplest way to get all the required python packages at once is to install the Python distribution `Anaconda `_. 31 | 32 | 33 | Installing dependencies on Mac OSX 34 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 35 | 36 | All required packages can be obtained through `MacPorts `_. After installing macports, individual libraries are installed with the following: 37 | 38 | :: 39 | 40 | sudo port install py-pyqt4 41 | sudo port install py-numpy 42 | sudo port install py-scipy 43 | sudo port install py-matplotlib 44 | sudo port install py-pillow 45 | sudo port install py-pandas 46 | 47 | Note: when using python 3, replace `py-` with `py36-` 48 | 49 | Run 50 | --- 51 | 52 | After downloading the zip-file extract its content to a directory. If you have already installed the dependencies, you are ready to go and can open the graphical user interface by running ``easyleed``. 53 | -------------------------------------------------------------------------------- /doc/source/userdoc.rst: -------------------------------------------------------------------------------- 1 | User's Guide 2 | ============ 3 | 4 | .. warning:: This guide is incomplete and not always up-to-date with the newest version of the software. 5 | If you are running into trouble using the software we are happy to provide further assistance via email. 6 | 7 | We would also be happy about volunteers who want to contribute to EasyLEED by extending this guide! 8 | 9 | Here is a short step-by-step guide: 10 | 11 | - Open the images you want to work with using "File --> Open..." or use "Open" in the toolbar. Only the images selected in the file dialog will be processed later on. 12 | - Navigate through the images using "Process --> Previous Image" and "Process --> Next Image”, or buttons in the status bar, or with the slider until you have found the image at which you want to start the analysis. If you know the energy corresponding to the image of interest, you can enter it to select the corresponding image by pressing the button labeled “eV” and by entering the relative energy. 13 | - Select the position of the spots by a left click. You can remove a spot by selecting it, and pressing the "Delete" key (fn+Delete on Macs). 14 | - The center of the pattern can be set for an optimized spot tracking with a right click. 15 | - You can change the position of the selected spot with the arrow keys. The size of the integration window can be changed using + or -. (optional) 16 | - Change the tracking parameters using "Process -- Set Parameters" or pressing "Set Parameters" in the toolbar. (Optional) 17 | - Start the tracking of the spots using "Process --> Run" or pressing "Run" in the toolbar. The I(E) plot starts automatically. 18 | - When pressing "Pause" during a run, spots can be moved. When the run is resumed, the spots will move from the new location. 19 | - I(E) averages can be plotted directly from the check box in the Plot window. 20 | - Smoothed I(E) averages can be plotted directly from the check box in the Plot window (uses cubic spline). 21 | * Any change in smoothing parameters in settings is immediately applied in the plot. 22 | - Tooltips in the image in correspondence to the spots show the spot number. The same number is displayed in the legend in the plot. 23 | - Click the stop button in the status bar to stop processing. (optional) 24 | - Save the generated intensities using "File --> Save Intensities...". This will also save both spot and center location. 25 | - Plot the generated intensities using "Process --> Plot" or the "Plot" option in the toolbar. 26 | - Save the generated plot image using "File --> Save Plot" 27 | - Selecting a spot after a run will highlight the corresponding curve in the plot. 28 | 29 | 30 | - Parameter settings can also be saved/loaded using an ini-format through the "Set Parameters" dialog 31 | - The extracted spot and center positions can also be saved and reloaded as csv files. 32 | 33 | Parameter settings 34 | ------------------ 35 | 36 | It might be useful to get a rough understanding of the underlying algorithm for intuition into how to set parameters. We recommend reading [our paper]() for an introduction to the ideas behind EasyLEED. 37 | 38 | Briefly, in the algorithm a trade-off is made between the reliance on the fitted vs predicted spot positions. Fitted positions are roughly the maxima of intensity of the spots. The prediction is made based on a simple dynamical model of the spots moving towards the center as beam energy is increased. How much the algorithm relies on one over the other depends on an assumed variance of both predictions. The variance of predictions increases if there is more "process noise", i.e. the larger you set those parameters the more the algorithm is going to rely on its fitted position. 39 | 40 | The algorithm also uses the user input of initial spot positions (and if set center position). The weight put on this information is controlled by the parameter "User input precision". Larger values of this parameter lead to less weight being placed on this prior information. 41 | 42 | Sometimes spots vanish so they cannot be fit accurately. The algorithm should then purely rely on the prediction. The "Minimal R^2 to accept fit" specifies the quality of fit below which to reject the position from the fit. Similarly the fit might go awry and converge to a different spot. The parameter "Size of the validation region" sets a cutoff on how much the fitted position can differ from the predicted position, before it is discarded. 43 | 44 | File naming policy 45 | ------------------ 46 | 47 | EasyLEED tries to infer the beam energy from the filename. 48 | 49 | Examples of valid file names in the default configuration: 50 | 51 | - file30.jpg --> at 30eV beam energy 52 | - file30.5.jpg --> at 30.5eV beam energy 53 | 54 | For advanced users: 55 | You can change the regular expression that is used for parsing energies from filenames in the config file. 56 | 57 | Custom configuration 58 | -------------------- 59 | 60 | Most of the default parameters of the algorithm as well as many UI tweaks are controlled via a config file. This config file is saved in your home folder as "easyleed.ini". You can edit this file to make persistent changes to the parameters used at startup. 61 | 62 | Qt Wrapper 63 | ---------- 64 | 65 | Different python wrappers for the `Qt` library exist (`PyQt5`, `PyQt4` and `PySide`). All can be used with EasyLEED. To select which one to use set the `QT_API` environment variable to either `pyqt` or `pyside`. 66 | -------------------------------------------------------------------------------- /environment.yml: -------------------------------------------------------------------------------- 1 | name: easyleed 2 | dependencies: 3 | - python=3.* 4 | - numpy 5 | - scipy 6 | - matplotlib 7 | - pillow 8 | - pyqt=4.* 9 | - pip: 10 | - pyfits 11 | - easyleed 12 | 13 | -------------------------------------------------------------------------------- /source/.gitignore: -------------------------------------------------------------------------------- 1 | config.py 2 | MANIFEST 3 | dist/ 4 | build/ 5 | *.egg-info/ 6 | -------------------------------------------------------------------------------- /source/easyleed/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | The EasyLEED package is divided into several subpackages: 3 | 4 | - base: Core functionality (fitting procedures, Tracker class, etc.) 5 | - kalman: Implementation of Kalman filter classes 6 | - io: Input/Output functionality (reading FITS, PIL, and IMG files) 7 | - gui: Graphical User Interface 8 | 9 | .. automodule:: easyleed.base 10 | :members: 11 | 12 | .. automodule:: easyleed.kalman 13 | :members: 14 | 15 | .. automodule:: easyleed.io 16 | :members: 17 | 18 | .. automodule:: easyleed.gui 19 | :members: 20 | 21 | """ 22 | 23 | __version__ = "2.5.2" 24 | __author__ = "Andreas Mayer, Hanna Salopaasi, Nicola Ferralis" 25 | 26 | from .defaultconfig import * 27 | import os.path 28 | 29 | config = Configuration() 30 | if os.path.isfile(config.configFile) is False: 31 | print("Configuration file does not exist: Creating one.") 32 | config.createConfig() 33 | config.readConfig(config.configFile) 34 | 35 | import logging 36 | logging.basicConfig(filename=config.loggingFilename, level=int(config.loggingLevel)) 37 | logger = logging.getLogger() 38 | 39 | from . import defaultconfig 40 | from . import gui 41 | 42 | from . import kalman 43 | from . import io 44 | from . import base 45 | from . import test 46 | from . import qt 47 | from . import gui 48 | -------------------------------------------------------------------------------- /source/easyleed/__main__.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | 3 | import sys 4 | from .qt.widgets import QApplication 5 | from .gui import MainWindow 6 | 7 | def main(): 8 | app = QApplication(sys.argv) 9 | form = MainWindow() 10 | form.show() 11 | app.exec_() 12 | 13 | if __name__ == "__main__": 14 | main() 15 | -------------------------------------------------------------------------------- /source/easyleed/base.py: -------------------------------------------------------------------------------- 1 | """ 2 | easyleed.base 3 | ------------- 4 | 5 | Base class providing common functionality for analyzing Leed patterns. 6 | 7 | """ 8 | 9 | import numpy as np 10 | from scipy import optimize 11 | import math 12 | 13 | from . import config 14 | from . import kalman 15 | from . import logger 16 | 17 | 18 | class SpotModel: 19 | """ Data model for a Spot that stores all the information in various lists. 20 | """ 21 | 22 | def __init__(self): 23 | self.x = [] 24 | self.y = [] 25 | self.intensity = [] 26 | self.energy = [] 27 | self.radius = [] 28 | 29 | def update(self, x, y, intensity, energy, radius): 30 | self.x.append(x) 31 | self.y.append(y) 32 | self.intensity.append(intensity) 33 | self.energy.append(energy) 34 | self.radius.append(radius) 35 | 36 | 37 | class Tracker: 38 | """ Tracks spots through intensity information and velocity prediction. """ 39 | def __init__(self, x_in, y_in, radius, energy, x_c=None, y_c=None, 40 | input_precision=1, window_scaling=False): 41 | """ x_in, y_in: start position of spot """ 42 | self.radius = radius 43 | self.init_tracker(x_in, y_in, radius, energy, x_c, y_c, 44 | input_precision, window_scaling) 45 | 46 | def init_tracker(self, x_in, y_in, radius, energy, x_c, y_c, 47 | input_precision, window_scaling): 48 | if x_c and y_c: 49 | self.x, self.y = x_in - x_c, y_in - y_c 50 | self.r = (self.x**2 + self.y**2)**.5 51 | self.v = - 0.5 * self.r / energy 52 | # calculate std. dev. of velocity guess 53 | # by propagation of uncertainty from the input precision 54 | v_precision = 2**.5 * 0.5 * input_precision / energy 55 | self.phi = np.arctan2(self.y, self.x) 56 | cov_input = np.diag([input_precision, input_precision, v_precision, v_precision])**2 57 | self.kalman = kalman.PVKalmanFilter2(x_in, y_in, cov_input, energy, vx_in=self.v * np.cos(self.phi), vy_in=self.v * np.sin(self.phi)) 58 | else: 59 | cov_input = np.diag([input_precision, input_precision, 1000, 1000]) 60 | self.kalman = kalman.PVKalmanFilter2(x_in, y_in, cov_input, energy) 61 | 62 | self.window_scaling = window_scaling 63 | if self.window_scaling: 64 | self.c_size = energy**0.5 * self.radius 65 | 66 | 67 | def feed_image(self, image): 68 | npimage, energy = image 69 | if (not config.GraphicsScene_intensTimeOn) and self.window_scaling: 70 | self.radius = self.c_size / energy**0.5 71 | if self.radius < config.Tracking_minWindowSize: 72 | self.radius = config.Tracking_minWindowSize 73 | if not config.GraphicsScene_intensTimeOn: 74 | processNoise = np.diag([config.Tracking_processNoisePosition, config.Tracking_processNoisePosition, 75 | config.Tracking_processNoiseVelocity, config.Tracking_processNoiseVelocity]) 76 | self.kalman.predict(energy, processNoise) 77 | x_p, y_p = self.kalman.get_position() 78 | guess = guesser(npimage, x_p, y_p, self.radius) 79 | if guess is not None: 80 | x_th, y_th, guess_cov = guess 81 | # spot in validation region? (based on residual covariance) 82 | if self.kalman.measurement_distance((x_th, y_th), guess_cov) > config.Tracking_gamma: 83 | print(" No spot in validation gate") 84 | else: 85 | self.kalman.update([x_th, y_th], guess_cov) 86 | x, y = self.kalman.get_position() 87 | intensity = calc_intensity(npimage, x, y, self.radius, background_substraction=config.Processing_backgroundSubstractionOn) 88 | return x, y, intensity, energy, self.radius 89 | 90 | def guess_from_Gaussian(image, *args, **kwargs): 91 | """ Guess position of spot from a Gaussian fit. """ 92 | # construct circle where data is fit 93 | radius = 0.5 * min(image.shape) 94 | distances = calc_distances(image.shape, radius-0.5, radius-0.5, radius) 95 | circle = distances <= radius**2 96 | 97 | # generate good guesses for the Gaussian distribution 98 | background = np.min(image) 99 | params = moments(image-background) 100 | params.append(background) 101 | errfunc = lambda p: np.ravel(gaussian2d(*p)(*np.indices(image.shape))[circle] - image[circle]) 102 | # fit Gaussian 103 | maxfev = 200 104 | try: 105 | output = optimize.leastsq(errfunc, params, full_output=True, maxfev=maxfev) 106 | except: 107 | return None 108 | p_opt = output[0] 109 | p_cov = output[1] 110 | infodict = output[2] 111 | if infodict["nfev"] >= maxfev or p_cov is None: 112 | print(" Fit failed") 113 | return None 114 | # residual sum of squares sum (x_i - f_i)^2 115 | sum_of_squares_regression = (errfunc(p_opt)**2).sum() 116 | # variance of the data sum (x_i - )^2 117 | sum_of_squares_total = ((image[circle]-np.mean(image[circle]))**2).sum() 118 | # calculate R^2 119 | Rsq = 1 - sum_of_squares_regression / sum_of_squares_total 120 | if Rsq < config.Tracking_minRsq: 121 | print(" R^2 too low") 122 | return None 123 | # estimate sigma^2 from a chi^2 equivalent 124 | s_sq = sum_of_squares_regression/(len(image[circle].flatten())-len(params)) 125 | p_cov *= s_sq 126 | p_cov = p_cov[1:3, 1:3] 127 | x_res = p_opt[1] 128 | y_res = p_opt[2] 129 | return (x_res, y_res), p_cov 130 | 131 | guesser_routines = {'Gaussian fit' : guess_from_Gaussian} 132 | 133 | 134 | try: 135 | import skimage.feature 136 | 137 | logger.info('imported scikit image package') 138 | 139 | def guess_from_blob_dog(image, *args, **kwargs): 140 | A = skimage.feature.blob_dog(image) 141 | if not A.shape[0]: 142 | print(" No blob found") 143 | return None 144 | print(' Blobs found', A) 145 | return (A[0, 1], A[0, 0]), np.diag([2, 2]) 146 | 147 | def guess_from_blob_log(image, *args, **kwargs): 148 | A = skimage.feature.blob_log(image, threshold=0.1) 149 | if not A.shape[0]: 150 | print(" No blob found") 151 | return None 152 | print(' Blobs found', A) 153 | return (A[0, 1], A[0, 0]), np.diag([2, 2]) 154 | 155 | guesser_routines['Blob dog'] = guess_from_blob_dog 156 | guesser_routines['Blob log'] = guess_from_blob_log 157 | 158 | except ImportError: 159 | pass 160 | 161 | 162 | def guesser(npimage, x_in, y_in, radius): 163 | def failure(reason): 164 | logger.info(" No guess, because " + reason) 165 | print(reason) 166 | return None 167 | 168 | # try to get patch from image around estimated position 169 | try: 170 | func=guesser_routines[config.Tracking_guessFunc] 171 | fit_region_factor=config.Tracking_fitRegionFactor 172 | x_min, x_max, y_min, y_max = adjust_slice(npimage, 173 | x_in-fit_region_factor*radius, 174 | x_in+fit_region_factor*radius+1, 175 | y_in-fit_region_factor*radius, 176 | y_in+fit_region_factor*radius+1) 177 | except IndexError: 178 | return failure(" Position outside image") 179 | image = npimage[y_min:y_max, x_min:x_max] 180 | 181 | result = func(image, x_mid=x_in-x_min, y_mid=y_in-y_min, size=radius) 182 | if result is None: 183 | return failure(" Fit failed") 184 | pos, cov = result 185 | y_res, x_res = pos 186 | x_res += x_min 187 | y_res += y_min 188 | 189 | return x_res, y_res, cov 190 | 191 | 192 | def gaussian2d(height, center_x, center_y, width_x, width_y=None, 193 | offset=0): 194 | """Returns a two dimensional gaussian function with the given parameters""" 195 | if width_y is None: 196 | width_y = width_x 197 | return lambda x, y: np.asarray(height * np.exp(-(((center_x - x) / width_x)**2 + 198 | ((center_y - y) / width_y)**2) / 2)) + \ 199 | offset 200 | 201 | 202 | def moments(data): 203 | """ Calculates the moments of 2d data. 204 | Returns [height, x, y, width_x, width_y] 205 | the gaussian parameters of a 2D distribution by calculating its 206 | moments. """ 207 | total = data.sum() 208 | X, Y = np.indices(data.shape) 209 | x = (X*data).sum()/total 210 | y = (Y*data).sum()/total 211 | if math.isnan(x): 212 | x = 0 213 | if math.isnan(y): 214 | y = 0 215 | col = data[:, int(y)] 216 | width_x = np.sqrt(abs((np.arange(col.size)-y)**2*col).sum()/col.sum()) 217 | row = data[int(x), :] 218 | width_y = np.sqrt(abs((np.arange(row.size)-x)**2*row).sum()/row.sum()) 219 | height = data.max() 220 | return [height, x, y, width_x, width_y] 221 | 222 | 223 | def adjust_slice(image, x_sl_min, x_sl_max, y_sl_min, y_sl_max): 224 | """ 225 | Adjusts slice if it is trying to get pieces outside the image. 226 | 227 | >>> image = np.ones((2, 2)) 228 | >>> adjust_slice(image, 0, 1.5, 0, 2) 229 | (0, 1, 0, 2) 230 | >>> adjust_slice(image, -5.5, 2, -0.5, 10) 231 | (0, 2, 0, 2) 232 | """ 233 | 234 | ymax, xmax = image.shape 235 | adjusted = False 236 | indices = [int(x_sl_min), int(x_sl_max), int(y_sl_min), int(y_sl_max)] 237 | for i, value in enumerate(indices): 238 | if value < 0: 239 | indices[i] = 0 240 | adjusted = True 241 | for i, value in enumerate(indices): 242 | if i < 2: 243 | if value > xmax: 244 | indices[i] = xmax 245 | adjusted = True 246 | else: 247 | if value > ymax: 248 | indices[i] = ymax 249 | adjusted = True 250 | if adjusted: 251 | logger.warning(" slice had to be adjusted to fit image.") 252 | if not int(indices[0] - indices[1]) or not int(indices[2] - indices[3]): 253 | raise IndexError() 254 | return tuple(indices) 255 | 256 | 257 | def calc_distances(shape, x, y, squared=True): 258 | """ 259 | Helper function that returns an array of distances to x, y. 260 | This array can be useful for fancy indexing of numpy arrays. 261 | 262 | squared: return the squared distance (default: True) 263 | """ 264 | yind, xind = np.indices(shape) 265 | distSquare = ((yind - y)**2 + (xind - x)**2) 266 | if not squared: 267 | return distSquare**.5 268 | return distSquare 269 | 270 | 271 | def signal_to_background(npimage, x, y, radius): 272 | distSquare = calc_distances(npimage.shape, x, y) 273 | signal = np.mean(npimage[distSquare <= radius**2]) 274 | # average background intensity over annulus with equal area 275 | background = np.mean(npimage[np.logical_and(distSquare >= radius**2, distSquare <= 2 * radius**2)]) 276 | return signal/background 277 | 278 | 279 | def calc_intensity(npimage, x, y, radius, background_substraction=config.Processing_backgroundSubstractionOn): 280 | """ Calculates the intensity of a spot. 281 | 282 | npimage: numpy array of intensity values 283 | x, y: position of the spot 284 | radius: radius of the spot 285 | background_substraction: boolean to turn substraction on/off 286 | """ 287 | distSquare = calc_distances(npimage.shape, x, y, squared=True) 288 | intensities = npimage[distSquare <= radius**2] 289 | intensity = np.sum(intensities) 290 | if background_substraction: 291 | # average background intensity over annulus with approximately equal area 292 | background_intensities = npimage[np.logical_and(distSquare >= radius**2, distSquare <= 2 * radius**2)] 293 | area = len(intensities) 294 | intensity -= np.mean(background_intensities) * area 295 | return intensity 296 | -------------------------------------------------------------------------------- /source/easyleed/defaultconfig.py: -------------------------------------------------------------------------------- 1 | ''' 2 | configuration 3 | ------------------ 4 | Class for handling configuration settings 5 | ''' 6 | 7 | import configparser, logging, os 8 | import numpy as np 9 | from pathlib import Path 10 | from datetime import datetime 11 | from . import __version__ 12 | 13 | class Configuration(): 14 | def __init__(self): 15 | self.home = str(Path.home())+"/" 16 | self.configFile = self.home+"easyleed.ini" 17 | self.loggingFilename = "easyleed.log" 18 | self.conf = configparser.ConfigParser() 19 | self.conf.optionxform = str 20 | 21 | def createConfig(self): 22 | """Create configuration file""" 23 | try: 24 | self.defineIOConfig() 25 | self.defineGUIConfig() 26 | self.defineTrackingConfig() 27 | self.defineProcessingConfig() 28 | self.defineSystemConfig() 29 | with open(self.configFile, 'w') as configfile: 30 | self.conf.write(configfile) 31 | except: 32 | print("Error in creating configuration file") 33 | 34 | # Hardcoded default definitions for the configuration file 35 | def defineIOConfig(self): 36 | self.conf['IO'] = { 37 | 'IO_energyRegex' : "[0-9]+(\.[0-9]*)?([Ee](|\+|-)[0-9]+)?(?=[^.0-9]*\.[A-Za-z0-9]+$)", 38 | } 39 | def defineGUIConfig(self): 40 | self.conf['GUI'] = { 41 | 'GraphicsScene_defaultRadius' : 40.0, 42 | 'GraphicsScene_livePlottingOn' : True, 43 | 'GraphicsScene_intensTimeOn' : False, 44 | 'GraphicsScene_plotAverage' : False, 45 | 'GraphicsScene_plotSmoothAverage': False, 46 | 'GraphicsScene_smoothPoints' : 4, 47 | 'GraphicsScene_smoothSpline' : 50, 48 | 'QGraphicsMovableItem_bigMove' : 1, 49 | 'QGraphicsMovableItem_smallMove' : 0.1, 50 | 'QGraphicsSpotItem_spotSizeChange' : 1, 51 | 'QGraphicsCenterItem_size' : 5, 52 | } 53 | def defineTrackingConfig(self): 54 | self.conf['Tracking'] = { 55 | 'Tracking_inputPrecision' : 2.0, 56 | 'Tracking_windowScalingOn' : True, 57 | 'Tracking_minWindowSize' : 0.0, 58 | 'Tracking_guessFunc' : "Gaussian fit", 59 | 'Tracking_processNoisePosition' : 0.1, 60 | 'Tracking_processNoiseVelocity' : 0.0, 61 | 'Tracking_gamma' : 8.0, 62 | 'Tracking_minRsq' : 0.8, 63 | 'Tracking_fitRegionFactor' : 1.5, 64 | } 65 | 66 | def defineProcessingConfig(self): 67 | self.conf['Processing'] = { 68 | 'Processing_backgroundSubstractionOn' : True, 69 | } 70 | 71 | def defineSystemConfig(self): 72 | self.conf['System'] = { 73 | 'appVersion' : __version__, 74 | 'loggingLevel' : logging.INFO, 75 | 'loggingFilename' : self.loggingFilename, 76 | } 77 | 78 | # Read configuration file into usable variables 79 | def readConfig(self, configFile): 80 | self.conf.read(configFile) 81 | self.sysConfig = self.conf['System'] 82 | self.appVersion = self.sysConfig['appVersion'] 83 | try: 84 | self.IOConfig = self.conf['IO'] 85 | self.GUIConfig = self.conf['GUI'] 86 | self.trackingConfig = self.conf['Tracking'] 87 | self.processingConfig = self.conf['Processing'] 88 | 89 | ##################### 90 | #### IO related #### 91 | #################### 92 | # regular expression to extract energy from filename (not used for img files) 93 | # match the last (floating-point) number before file extension 94 | self.IO_energyRegex = self.IOConfig['IO_energyRegex'] 95 | 96 | ##################### 97 | #### GUI related #### 98 | ##################### 99 | ## GraphicsScene ## 100 | # default radius of a new spot 101 | self.GraphicsScene_defaultRadius = self.conf.getfloat('GUI', 'GraphicsScene_defaultRadius') 102 | # Live IV plotting during acquisition 103 | self.GraphicsScene_livePlottingOn = self.conf.getboolean('GUI', 'GraphicsScene_livePlottingOn') 104 | # Acquire I(time) at fixed energy 105 | self.GraphicsScene_intensTimeOn = self.conf.getboolean('GUI', 'GraphicsScene_intensTimeOn') 106 | # Plot averages 107 | self.GraphicsScene_plotAverage = self.conf.getboolean('GUI', 'GraphicsScene_plotAverage') 108 | # Plot smoothAverages 109 | self.GraphicsScene_plotSmoothAverage = self.conf.getboolean('GUI', 'GraphicsScene_plotSmoothAverage') 110 | # Interval of points to be rescaled for smoothing average 111 | self.GraphicsScene_smoothPoints = self.conf.getint('GUI', 'GraphicsScene_smoothPoints') 112 | # Amount of smoothing to perform during the spline fit. 113 | # The default value of s is s=m-\sqrt{2m} where m is the number of data-points being fit. 114 | self.GraphicsScene_smoothSpline = self.conf.getint('GUI', 'GraphicsScene_smoothSpline') 115 | 116 | ## QGraphicsMovableItem ## 117 | # change in position per key press (Arrow keys) 118 | self.QGraphicsMovableItem_bigMove = self.conf.getfloat('GUI', 'QGraphicsMovableItem_bigMove') 119 | # change in position per key press if Ctrl pressed 120 | self.QGraphicsMovableItem_smallMove = self.conf.getfloat('GUI', 'QGraphicsMovableItem_smallMove') 121 | 122 | ## QGraphicsSpotItem ## 123 | # change in radius of the spot per key press (+/-) in pixel 124 | self.QGraphicsSpotItem_spotSizeChange = self.conf.getfloat('GUI', 'QGraphicsSpotItem_spotSizeChange') 125 | 126 | ## QGraphicsCenterItem ## 127 | self.QGraphicsCenterItem_size = self.conf.getfloat('GUI', 'QGraphicsCenterItem_size') 128 | 129 | ########################## 130 | #### Tracking related #### 131 | ########################## 132 | 133 | # precision of the user input (standard deviation in pixel) 134 | self.Tracking_inputPrecision = self.conf.getfloat('Tracking', 'Tracking_inputPrecision') 135 | # scale the integration window with changing energy 136 | self.Tracking_windowScalingOn = self.conf.getboolean('Tracking', 'Tracking_windowScalingOn') 137 | # minimal radius of the integration window (in pixel) 138 | self.Tracking_minWindowSize = self.conf.getfloat('Tracking', 'Tracking_minWindowSize') 139 | # function for spot identification 140 | self.Tracking_guessFunc = self.trackingConfig['Tracking_guessFunc'] 141 | # Kalman tracker process noise 142 | self.Tracking_processNoisePosition = self.conf.getfloat('Tracking', 'Tracking_processNoisePosition') 143 | self.Tracking_processNoiseVelocity = self.conf.getfloat('Tracking', 'Tracking_processNoiseVelocity') 144 | # size of validation region 145 | # Ideal assumptions D_M^2 ~ Chi^2 with two degrees of freedom 146 | # cdf Chi^2 with two degrees of freedom is 1 - exp(-x/2) 147 | self.Tracking_gamma = self.conf.getfloat('Tracking', 'Tracking_gamma') 148 | # Minimal coefficient of determination R^2 for fit 149 | self.Tracking_minRsq = self.conf.getfloat('Tracking', 'Tracking_minRsq') 150 | # factor by which the fitted region is bigger than the radius 151 | self.Tracking_fitRegionFactor = self.conf.getfloat('Tracking', 'Tracking_fitRegionFactor') 152 | 153 | ############################ 154 | #### Processing related #### 155 | ############################ 156 | # substract the background from the intensity measurements 157 | self.Processing_backgroundSubstractionOn = self.conf.getboolean('Processing', 'Processing_backgroundSubstractionOn') 158 | 159 | ############################ 160 | #### System related #### 161 | ############################ 162 | self.loggingLevel = self.conf.getint('System', 'loggingLevel') 163 | 164 | except: 165 | print("Configuration file is for an earlier version of the software") 166 | oldConfigFile = str(os.path.splitext(configFile)[0] + "_" +\ 167 | str(datetime.now().strftime('%Y%m%d-%H%M%S'))+".ini") 168 | print("Old config file backup: ",oldConfigFile) 169 | os.rename(configFile, oldConfigFile ) 170 | print("Creating a new config file.") 171 | self.createConfig() 172 | self.readConfig(configFile) 173 | 174 | # Save current parameters in configuration file 175 | def saveConfig(self, configFile): 176 | try: 177 | with open(configFile, 'w') as configfile: 178 | self.conf.write(configfile) 179 | except: 180 | print("Error in saving parameters") 181 | -------------------------------------------------------------------------------- /source/easyleed/io.py: -------------------------------------------------------------------------------- 1 | """ 2 | easyleed.io 3 | ------------- 4 | 5 | Import routines for different LEED file formats 6 | 7 | """ 8 | 9 | import numpy as np 10 | from .qt import QtGui as qtgui 11 | 12 | # load regular expression package (for parsing of energy from file name) 13 | import re 14 | import os.path 15 | 16 | from . import logger 17 | 18 | #### load packages for available file types #### 19 | formats_available = ['IMG'] 20 | try: 21 | import pyfits 22 | formats_available.append("FITS") 23 | except: 24 | logger.warning("The pyfits package is not installed.") 25 | # try to import PIL in two possible ways (dependent on PIL version) 26 | try: 27 | from PIL import Image 28 | formats_available.append("PIL") 29 | except: 30 | try: 31 | import Image 32 | formats_available.append("PIL") 33 | except: 34 | logger.warning("The pillow package is not installed.") 35 | 36 | class ImageLoader(object): 37 | """ Abstract base class for a class loading LEED images. 38 | 39 | Subclasses need to provide 40 | - get_image(image_path) 41 | 42 | Subclasses may override (default: from filename with regex) 43 | - get_energy(image_path) 44 | """ 45 | def __init__(self, image_paths, regex): 46 | # build a dictionary with energy as key and imagePath as value 47 | self.regex = regex 48 | self.files = {} 49 | for image_path in image_paths: 50 | energy = self.get_energy(image_path) 51 | self.files[energy] = image_path 52 | self.energies = sorted(self.files.keys()) 53 | self.restart() 54 | 55 | def get_energy(self, image_path): 56 | m = re.search(self.regex, image_path) 57 | if m is None: 58 | raise IOError('Invalid filename. Check naming policy.') 59 | return float(m.group()) 60 | 61 | def current_energy(self): 62 | """ Get current energy. """ 63 | return self.energies[self.index] 64 | 65 | def __iter__(self): 66 | return self 67 | 68 | def restart(self): 69 | """ Start at lowest energy again. """ 70 | self.index = -1 71 | 72 | def previous(self): 73 | """ Get image at next lower beam energy. """ 74 | if self.index == 0: 75 | raise StopIteration("there is no previous image") 76 | else: 77 | self.index -= 1 78 | energy = self.energies[self.index] 79 | return self.get_image(self.files[energy]), energy 80 | 81 | def __next__(self): 82 | """ Get image at next higher beam energy. """ 83 | if self.index < len(self.energies)-1: 84 | self.index += 1 85 | energy = self.energies[self.index] 86 | return self.get_image(self.files[energy]), energy 87 | else: 88 | raise StopIteration() 89 | 90 | next = __next__ 91 | 92 | def goto(self, energy): 93 | """ Get image at given beam energy. """ 94 | self.index = self.energies.index(energy) 95 | return self.get_image(self.files[energy]), energy 96 | 97 | # FIXME: untested 98 | def custom_iter(self, energies): 99 | """ Returns an iterator to iter over the given energies.""" 100 | non_elements = set(energies) - set(self.energies) 101 | if non_elements: 102 | raise Exception("ImageLoader doesn't have the following elements: %s" % (list(non_elements))) 103 | for energy in energies: 104 | yield self.get_image(energy), energy 105 | 106 | 107 | class ImgImageLoader(ImageLoader): 108 | """ Load .img image files (HotLeed format). """ 109 | 110 | extensions = ["img"] 111 | 112 | def get_energy(self, image_path): 113 | with open(image_path, "rb") as f: 114 | return self.load_header(f)["Beam Voltage (eV)"] 115 | 116 | @staticmethod 117 | def load_header(f): 118 | # find header length 119 | line = f.readline() 120 | while not b"Header length:" in line: 121 | line = f.readline() 122 | header_length = int(line.split(b": ")[1].strip()) 123 | # jump back to beginning 124 | f.seek(0) 125 | # read in header 126 | header_raw = f.read(header_length) 127 | ## process header ## 128 | # dict containing names of all interesting entrys 129 | header = {b"Beam Voltage (eV)": 0, b"Date": "", b"Comment": "", 130 | b"x1": 0, b"y1": 0, b"x2": 0, b"y2": 0, b"Number of frames": 0, 131 | b'length' : header_length} 132 | headerlines = header_raw.split(b"\n") 133 | for line in headerlines: 134 | parts = line.split(b": ") 135 | if parts[0] in header.keys(): 136 | # convert int entrys 137 | if type(header[parts[0]]) == type(1): 138 | header[parts[0]] = int(parts[1]) 139 | # convert string entrys 140 | elif type(header[parts[0]]) == type(""): 141 | header[parts[0]] = parts[1].strip() 142 | return header 143 | 144 | @staticmethod 145 | def get_image(image_path): 146 | with open(image_path, "rb") as f: 147 | header = ImgImageLoader.load_header(f) 148 | # jump to begin of image 149 | f.seek(header[b'length']) 150 | # read in image 151 | content = f.read() 152 | # make numpy array from image 153 | image = np.frombuffer(content, dtype=np.uint16) 154 | # calculate size of image from header information 155 | size = (header[b"y2"]-header[b"y1"]+1, header[b"x2"]-header[b"x1"]+1) 156 | # reshape image as 2d array 157 | image = image.reshape((size)) 158 | return image 159 | 160 | 161 | class FitsImageLoader(ImageLoader): 162 | """ Load .fits image files. """ 163 | 164 | extensions = ["fit", "fits"] 165 | 166 | @staticmethod 167 | def get_image(image_path): 168 | hdulist = pyfits.open(image_path) 169 | data = hdulist[0].data 170 | hdulist.close() 171 | return data 172 | 173 | 174 | class PILImageLoader(ImageLoader): 175 | """ Load image files supported by Python Imaging Library (PIL). """ 176 | 177 | extensions = ["tif", "tiff", "png", "jpg", "bmp"] 178 | 179 | @staticmethod 180 | def get_image(image_path): 181 | im = Image.open(image_path) 182 | sc = len(im.mode) == 1 or im.mode.find(';') == 1 # is the image single-channel? 183 | return np.asarray(im if sc else im.convert('L')) 184 | 185 | class ImageFormat: 186 | """ Class describing an image format. """ 187 | def __init__(self, abbrev, loader): 188 | """ 189 | abbrev: abbreviation (e.g. FITS) 190 | loader: ImageLoader subclass for this format 191 | """ 192 | self.abbrev = abbrev 193 | self.loader = loader 194 | self.extensions = loader.extensions 195 | 196 | def __str__(self): 197 | return "{0}-Files ({1})".format(self.abbrev, " ".join(self.extensions)) 198 | 199 | def extensions_wildcard(self): 200 | return ['*.%s' % ext for ext in self.extensions] 201 | 202 | """ Dictionary of available ImageFormats. """ 203 | IMAGE_FORMATS = [format_ for format_ in \ 204 | [ImageFormat("PIL", PILImageLoader), 205 | ImageFormat("FITS", FitsImageLoader), 206 | ImageFormat("IMG", ImgImageLoader)] \ 207 | if format_.abbrev in formats_available] 208 | 209 | class AllImageLoader(ImageLoader): 210 | 211 | @staticmethod 212 | def supported_extensions(): 213 | extensions = [] 214 | for image_format in IMAGE_FORMATS: 215 | extensions.extend(image_format.extensions_wildcard()) 216 | return extensions 217 | 218 | def get_image(self, image_path): 219 | extension = os.path.splitext(image_path)[1][1:] 220 | for image_format in IMAGE_FORMATS: 221 | loader = image_format.loader 222 | if extension in loader.extensions: 223 | return loader.get_image(image_path) 224 | raise IOError('The filetype is not supported') 225 | 226 | 227 | def normalize255(array): 228 | """ Returns a normalized array of uint8.""" 229 | nmin, nmax = array.min(), array.max() 230 | if nmin: 231 | array = array - nmin 232 | scale = 255.0 / (nmax - nmin) 233 | if scale != 1.0: 234 | array = array * scale 235 | return array.astype("uint8") 236 | 237 | 238 | qtGreyColorTable = [qtgui.qRgb(i, i, i) for i in range(256)] 239 | 240 | def npimage2qimage(npimage): 241 | """ Converts numpy grayscale image to qimage.""" 242 | h, w = npimage.shape 243 | npimage = normalize255(npimage) 244 | # second w to avoid problems if image is not 32-bit aligned 245 | # --> indicates bytesPerLine 246 | qimage = qtgui.QImage(npimage.data, w, h, w, qtgui.QImage.Format_Indexed8) 247 | qimage.setColorTable(qtGreyColorTable) 248 | return qimage 249 | -------------------------------------------------------------------------------- /source/easyleed/kalman.py: -------------------------------------------------------------------------------- 1 | """ 2 | easyleed.kalman 3 | ---------------- 4 | 5 | Kalman filters for tracking the spots 6 | 7 | """ 8 | 9 | import numpy as np 10 | 11 | class AbstractKalmanFilter(object): 12 | """ Abstract implementation of a Kalman filter. 13 | 14 | Matrices and Vectors can be given in any input format np.matrix() understands. 15 | Vectors are internally transposed and should therefore be given as column vectors. 16 | """ 17 | 18 | def __init__(self, x, P, H): 19 | """ Initialize Kalman filter. 20 | 21 | x: start state vector 22 | P: start state covariance matrix 23 | H: measurement matrix 24 | """ 25 | 26 | super(AbstractKalmanFilter, self).__init__() 27 | self.x = np.asmatrix(x).T 28 | self.P = np.asmatrix(P) 29 | self.H = np.asmatrix(H) 30 | # identity matrix of state vector size 31 | self._1 = np.asmatrix(np.identity(max(self.x.shape))) 32 | 33 | def predict(self, F, Q=np.zeros((4, 4))): 34 | """ Predict next state. 35 | 36 | F: state transition matrix 37 | Q: process covariance matrix 38 | """ 39 | 40 | F = np.asmatrix(F) 41 | Q = np.asmatrix(Q) 42 | self.x = F * self.x 43 | self.P = F * self.P * F.T + Q 44 | 45 | def predict_measurement_covariance(self, R=None): 46 | """ Returns the covariance matrix of a predicted measurement. """ 47 | if not R is None: 48 | return self.H * self.P * self.H.T + R 49 | else: 50 | return self.H * self.P * self.H.T 51 | 52 | def predict_measurement(self): 53 | """ Returns the predicted measurement. """ 54 | return self.H * self.x 55 | 56 | def update(self, z, R): 57 | """ Update state estimate. 58 | 59 | z: measurement vector 60 | R: measurement covariance matrix 61 | """ 62 | z = np.asmatrix(z).T 63 | R = np.asmatrix(R) 64 | K = self.P * self.H.T * self.predict_measurement_covariance(R).I 65 | # print np.sum((np.asarray(z).flatten() - np.asarray(self.predict_measurement()).flatten())**2), \ 66 | # np.sum(np.diag(self.predict_measurement_covariance(R))) 67 | self.x = self.x + K * (z - self.predict_measurement()) 68 | self.P = (self._1 - K * self.H) * self.P 69 | 70 | def measurement_distance(self, z, R=None): 71 | """ Returns the squared Mahalanobis distance of the given measurement. 72 | 73 | z: measurement vector 74 | """ 75 | z = np.asmatrix(z).T 76 | R = np.diag([0, 0]) if R is None else np.asarray(R) 77 | z_predicted = self.predict_measurement() 78 | # calculate the measurement residual 79 | residual = z - z_predicted 80 | return residual.T * self.predict_measurement_covariance(R).I * residual 81 | 82 | class AbstractPVKalmanFilter(AbstractKalmanFilter): 83 | """ Kalman filter for 2d-tracking using position and velocity as state variables.""" 84 | def __init__(self, x_in, y_in, P, time, vx_in=0, vy_in=0): 85 | self.old_time = time 86 | x = [x_in, y_in, vx_in, vy_in] 87 | H = [[1, 0, 0, 0], [0, 1, 0, 0]] 88 | super(AbstractPVKalmanFilter, self).__init__(x, P, H) 89 | 90 | def get_position(self): 91 | return float(self.x[0]), float(self.x[1]) 92 | 93 | def get_position_err(self): 94 | return float(self.P[0, 0])**0.5, float(self.P[1, 1])**0.5 95 | 96 | class PVKalmanFilter0(AbstractPVKalmanFilter): 97 | def predict(self, time, *args, **kwargs): 98 | dt = time - self.old_time 99 | F = [[1, 0, dt, 0], [0, 1, 0, dt], [0, 0, 1, 0], [0, 0, 0, 1]] 100 | super(PVKalmanFilter0, self).predict(F, *args, **kwargs) 101 | self.old_time = time 102 | 103 | class PVKalmanFilter1(AbstractPVKalmanFilter): 104 | def predict(self, time, *args, **kwargs): 105 | dt = time - self.old_time 106 | a = - 1.5 / self.old_time 107 | v_up = 1 + a * dt 108 | pos_up = dt + 0.5 * a * dt**2 109 | F = [[1, 0, pos_up, 0], [0, 1, 0, pos_up], [0, 0, v_up, 0], [0, 0, 0, v_up]] 110 | super(PVKalmanFilter1, self).predict(F, *args, **kwargs) 111 | self.old_time = time 112 | 113 | class PVKalmanFilter2(AbstractPVKalmanFilter): 114 | def predict(self, time, *args, **kwargs): 115 | dt = time - self.old_time 116 | a = - 1.5 / self.old_time 117 | a_dot = 1.875 / self.old_time**2 118 | v_up = 1 + a * dt + a_dot * dt**2 119 | pos_up = dt + 0.5 * a * dt**2 + (1.0 / 3.0) * a_dot * dt**3 120 | F = [[1, 0, pos_up, 0], [0, 1, 0, pos_up], [0, 0, v_up, 0], [0, 0, 0, v_up]] 121 | super(PVKalmanFilter2, self).predict(F, *args, **kwargs) 122 | self.old_time = time 123 | 124 | class PVKalmanFilter3(AbstractPVKalmanFilter): 125 | def predict(self, time, *args, **kwargs): 126 | dt = time - self.old_time 127 | a = - 1.5 / self.old_time 128 | a_dot = 1.875 / self.old_time**2 129 | a_ddot = - 2.1875 / self.old_time**3 130 | v_up = 1 + a * dt + a_dot * dt**2 + a_ddot * dt**3 131 | pos_up = dt + 0.5 * a * dt**2 + (1.0 / 3.0) * a_dot * dt**3 + (1.0 / 4.0) * a_ddot * dt**4 132 | F = [[1, 0, pos_up, 0], [0, 1, 0, pos_up], [0, 0, v_up, 0], [0, 0, 0, v_up]] 133 | super(PVKalmanFilter3, self).predict(F, *args, **kwargs) 134 | self.old_time = time 135 | -------------------------------------------------------------------------------- /source/easyleed/qt.py: -------------------------------------------------------------------------------- 1 | """ 2 | easyleed.qt 3 | ------------ 4 | 5 | A Qt API selector that can be used to switch between PyQt and PySide wrappers. 6 | 7 | Thanks to Liam Deacon for this workaround. 8 | 9 | See also https://github.com/matplotlib/matplotlib/blob/master/lib/matplotlib/backends/qt_compat.py 10 | """ 11 | 12 | import sys 13 | import os 14 | from . import logger 15 | 16 | env_api = os.environ.get('QT_API', 'pyqt') 17 | if '--pyside' in sys.argv: 18 | variant = 'pyside' 19 | elif '--pyqt4' in sys.argv: 20 | variant = 'pyqt4' 21 | elif '--pyqt5' in sys.argv: 22 | variant = 'pyqt5' 23 | elif env_api in ['pyside', 'pyqt']: 24 | variant = env_api 25 | else: 26 | raise ImportError('unrecognized python Qt bindings') 27 | # This will be passed on to new versions of matplotlib (name for pyqt4 is simply pyqt) 28 | os.environ['QT_API'] = 'pyqt' if variant == 'pyqt4' else variant 29 | logger.info("The chosen qt variant is %s." % variant) 30 | 31 | if variant == 'pyside': 32 | from PySide import QtCore, QtGui 33 | QtCore.QT_VERSION_STR = QtCore.__version__ 34 | QtCore.QT_VERSION = tuple(int(c) for c in QtCore.__version__.split('.')) 35 | elif variant == 'pyqt4': 36 | from PyQt4 import QtCore, QtGui 37 | elif variant == 'pyqt5': 38 | from PyQt5 import QtCore, QtGui, QtWidgets 39 | elif variant == 'pyqt': 40 | try: 41 | from PyQt5 import QtCore, QtGui, QtWidgets 42 | variant = 'pyqt5' 43 | except: 44 | from PyQt4 import QtCore, QtGui 45 | variant = 'pyqt4' 46 | logger.info("Qt variant specified as pyqt, using %s." % variant) 47 | else: 48 | raise ImportError("Qt variant not specified") 49 | 50 | sys.modules[__name__ + '.QtCore'] = QtCore 51 | sys.modules[__name__ + '.QtGui'] = QtGui 52 | sys.modules[__name__ + '.widgets'] = QtGui if variant == 'pyqt4' else QtWidgets 53 | QtCore.QString = str 54 | 55 | def get_qt_binding_name(): 56 | return variant 57 | 58 | 59 | def qt_filedialog_convert(output): 60 | try: 61 | # in qt5 returns are filename and filetype 62 | filename, filetype = output 63 | except ValueError: 64 | # in qt4 returns are filename only 65 | filename = output 66 | if isinstance(filename, (list,str)): # qt5 67 | return filename 68 | if isinstance(filename, QtCore.QStringList): # qt4 69 | return [str(item) for item in filename] 70 | return str(filename) # also qt4 71 | 72 | __all__ = [QtGui, QtCore, get_qt_binding_name, qt_filedialog_convert] 73 | -------------------------------------------------------------------------------- /source/easyleed/test.py: -------------------------------------------------------------------------------- 1 | from __future__ import division 2 | 3 | from functools import partial 4 | import itertools 5 | import collections 6 | import math 7 | import logging 8 | import time 9 | 10 | import numpy as np 11 | np.seterr(all="ignore") 12 | import scipy.optimize 13 | 14 | from .base import * 15 | 16 | logging.basicConfig(level=logging.INFO) 17 | 18 | #### background_funcs #### 19 | class Background: 20 | """ Wraps a background function for easier construction and speeds things up for constant background""" 21 | def __init__(self, func, size, is_constant=False): 22 | self.is_constant = is_constant 23 | func = partial(func, size) 24 | if self.is_constant: 25 | self.image = func() 26 | else: 27 | self.func = func 28 | def __call__(self): 29 | if self.is_constant: 30 | return self.image.copy() 31 | else: 32 | return self.func() 33 | 34 | def back_uniform(size, level=0): 35 | return np.ones(size) * level 36 | 37 | def back_poisson(size, mu=1): 38 | return np.random.poisson(mu, size) 39 | 40 | def back_normal(size, mu=2, sigma=1): 41 | return abs(sigma * np.random.randn(*size) + mu) 42 | 43 | BACKGROUND_NORMAL = Background(partial(back_normal, mu=4, sigma=3), (400, 400), False) 44 | ######################### 45 | 46 | 47 | #### intensity_funcs #### 48 | def constant_factory(value, *args, **kwargs): 49 | return eat_args(next(itertools.repeat(value))) 50 | 51 | def eat_args(func): 52 | def new(*args, **kwargs): 53 | return func() 54 | return new 55 | 56 | def step_function(x, step_x = 100, value=1000): 57 | """ Heavyside step function making the step at step_x""" 58 | if x < step_x: 59 | return value 60 | else: 61 | return 0.0 62 | 63 | def sine_intensity(x, freq=10, value=1000): 64 | omega = 2 * np.pi / freq 65 | return (0.5 * np.cos(omega * x) + 0.5) * value 66 | ######################### 67 | 68 | #### energy_funcs #### 69 | def linear(x, m=1.0, b=0.0): 70 | return m * x + b 71 | ####################### 72 | 73 | def draw_gauss(x_spot, y_spot, sigma, npimage , integral = 1, multiplicator = None, sigma_cutoff = 4): 74 | """ Draw an gaussian spot at x_spot, y_spot with width sigma and integral intensity.""" 75 | if multiplicator is None: 76 | multiplicator = integral / (2 * sigma**2 * np.pi) 77 | npimage += gaussian2d_rot(multiplicator, x_spot, y_spot, sigma)(*np.indices(npimage.shape)) 78 | 79 | class Spot: 80 | def __init__(self, start_point, end_point, energy, intensity_func=constant_factory(1000), size=3, variable_size=False, energy_func=float): 81 | """ start_points, end_point: array-like 82 | variable_size: indicates whether to change the size of the spot. 83 | energy_func to distort used energy 84 | """ 85 | self.end_point = np.asarray(end_point) 86 | start_point = np.asarray(start_point) 87 | self.direction = (start_point - end_point).astype(float) 88 | distance = np.linalg.norm(self.direction) 89 | self.direction /= distance 90 | # distance scaling constant 91 | self.c = distance * energy**0.5 92 | self.intensity_func = intensity_func 93 | self.energy_func = energy_func 94 | self.sigma = size 95 | self.variable_size = variable_size 96 | if self.variable_size: 97 | # size scaling constant 98 | self.c_size = energy * self.sigma 99 | 100 | def compute_position(self, energy): 101 | energy = self.energy_func(energy) 102 | distance = self.c / energy**0.5 103 | result = distance * self.direction + self.end_point 104 | return result[0], result[1] 105 | 106 | def draw(self, npimage, energy): 107 | """ Draw an gaussian spot with integrated intensity from intensity_func.""" 108 | x_spot, y_spot = self.compute_position(energy) 109 | if self.variable_size: 110 | self.sigma = self.c_size / energy 111 | draw_gauss(x_spot, y_spot, self.sigma, npimage, integral=self.intensity_func(energy)) 112 | 113 | class ImageGenerator: 114 | """ Generate test images.""" 115 | def __init__(self, inputDir = "", 116 | background = Background(np.zeros, (500, 500), True), 117 | energies=range(75, 125), 118 | spots = [Spot((30.0, 30.0), (200, 200), 75, constant_factory(1000))]): 119 | """ input dir only for compatibility with ImageLoader, 120 | """ 121 | self.energies = sorted(energies) 122 | self.spots = spots 123 | self.background = background 124 | def __iter__(self): 125 | for energy in self.energies: 126 | npimage = self.background() 127 | for spot in self.spots: 128 | spot.draw(npimage, energy) 129 | yield npimage, energy 130 | 131 | class TestTracking: 132 | def __init__(self): 133 | size = (200, 200) 134 | end_point = (200, 200) 135 | self.radii = collections.defaultdict(constant_factory(10)) 136 | default_range = range(60, 140) 137 | 138 | self.kwarg_dict = {} 139 | self.kwarg_dict["minimal"] = \ 140 | dict(background = Background(partial(back_uniform, level=0), size, is_constant=True), 141 | energies = default_range, 142 | spots = [Spot((100, 50), end_point, default_range[0], intensity_func=constant_factory(1000))]) 143 | 144 | # backgrounds 145 | self.kwarg_dict["back_uniform"] = \ 146 | dict(background = Background(partial(back_uniform, level=4), size, is_constant=True), 147 | energies = default_range, 148 | spots = [Spot((30, 30), end_point, default_range[0], intensity_func=constant_factory(1000))]) 149 | self.kwarg_dict["back_poisson"] = \ 150 | dict(background = Background(partial(back_poisson, mu=4), size, is_constant=False), 151 | energies = default_range, 152 | spots = [Spot((30, 30), end_point, default_range[0], intensity_func=constant_factory(1000))]) 153 | self.kwarg_dict["back_normal"] = \ 154 | dict(background = Background(partial(back_normal, mu=4, sigma=3), size, is_constant=False), 155 | energies = default_range, 156 | spots = [Spot((30, 30), end_point, default_range[0], intensity_func=constant_factory(1000))]) 157 | 158 | # intensities 159 | self.kwarg_dict["intensity_step"] = \ 160 | dict(background = Background(partial(back_normal, mu=4, sigma=3), size, is_constant=False), 161 | energies = default_range, 162 | spots = [Spot((30, 30), end_point, default_range[0], intensity_func = partial(step_function, step_x=100))]) 163 | self.kwarg_dict["intensity_sine"] = \ 164 | dict(background = Background(partial(back_normal, mu=4, sigma=3), size, is_constant=False), 165 | energies = default_range, 166 | spots = [Spot((30, 30), end_point, default_range[0], intensity_func = partial(sine_intensity, 5))]) 167 | 168 | # energy 169 | # self.kwarg_dict["eV_stepsize"] = dict(background = Background(partial(back_normal, mu=4, sigma=3), size, is_constant=False), 170 | # energies = range(75, 125, 2), 171 | # spots = [Spot((30, 30), end_point, 75, intensity_func=partial(constant, value=1000))]) 172 | self.kwarg_dict["eV_uncalibrated"] = dict(background = Background(partial(back_normal, mu=4, sigma=3), size, is_constant=False), 173 | energies = default_range, 174 | spots = [Spot((30, 30), end_point, default_range[0], intensity_func=constant_factory(1000), 175 | energy_func=partial(linear, m=1.01, b=1.0))]) 176 | 177 | # point 178 | self.kwarg_dict["point_light"] = \ 179 | dict(background = Background(partial(back_normal, mu=4, sigma=3), size, is_constant=False), 180 | energies = default_range, 181 | spots = [Spot((30, 30), end_point, default_range[0], intensity_func=constant_factory(250))]) 182 | self.kwarg_dict["point_two"] = \ 183 | dict(background = Background(partial(back_normal, mu=4, sigma=3), size, is_constant=False), 184 | energies = default_range, 185 | spots = [Spot((30, 30), end_point, default_range[0], intensity_func=constant_factory(1000), size=2), 186 | Spot((38, 38), end_point, default_range[0], intensity_func=constant_factory(1000), size=2)]) 187 | # self.kwarg_dict["points_scaling"] = \ 188 | # dict(background = Background(partial(back_normal, mu=4, sigma=3), size, is_constant=False), 189 | # energies = default_range, 190 | # spots = [Spot((30, 30), end_point, default_range[0], intensity_func=partial(constant, value=1000), variable_size=True)]) 191 | self.kwarg_dict["point_small"] = \ 192 | dict(background = Background(partial(back_normal, mu=4, sigma=3), size, is_constant=False), 193 | energies = default_range, 194 | spots = [Spot((30, 30), end_point, default_range[0], intensity_func=constant_factory(250), size=1)]) 195 | self.radii["point_small"] = 5 196 | 197 | self.xss = [] 198 | self.yss = [] 199 | self.intensitiess = [] 200 | 201 | def run_all(self, output="full"): 202 | """ output in ["summary", "full", None] """ 203 | for key in sorted(self.kwarg_dict.keys()): 204 | if output == "full": 205 | print() 206 | print(key) 207 | # this is a hack as run is now an iterator for display 208 | [item for item in self.run(key)] 209 | if output == "full": 210 | self.print_error(-1) 211 | 212 | def run(self, name, output=False): 213 | imageGenerator = ImageGenerator(**self.kwarg_dict[name]) 214 | npimage, energy = iter(imageGenerator).next() 215 | trackers = [] 216 | energy = imageGenerator.energies[0] 217 | for spot in imageGenerator.spots: 218 | x, y = spot.compute_position(energy) 219 | trackers.append((Tracker(x, y, self.radii[name], energy), spot)) 220 | images = iter(imageGenerator) 221 | xs = [] 222 | ys = [] 223 | intensities = [] 224 | for image in images: 225 | if output: 226 | xs_out = [] 227 | ys_out = [] 228 | for tracker, spot in trackers: 229 | x, y, intensity, energy, radius = tracker.feed_image(image) 230 | if output: 231 | xs_out.append(x) 232 | ys_out.append(y) 233 | x_true, y_true = spot.compute_position(image[1]) 234 | intensity_true = spot.intensity_func(image[1]) 235 | xs.append((x, x_true)) 236 | ys.append((y, y_true)) 237 | intensities.append((intensity, intensity_true)) 238 | if output: 239 | yield image[0], xs_out, ys_out 240 | self.xss.append(xs) 241 | self.yss.append(ys) 242 | self.intensitiess.append(intensities) 243 | 244 | def print_error(self, index, round_=4): 245 | bias_pos = (compute_bias(self.xss[index])**2 + compute_bias(self.yss[index])**2)**0.5 246 | stddev_pos = (compute_stddev(self.xss[index])**2 + compute_stddev(self.yss[index])**2)**0.5 247 | print_bias_stddev(bias_pos, stddev_pos, "position") 248 | print_bias_stddev(compute_bias(self.intensitiess[index]), compute_stddev(self.intensitiess[index]), "intensity") 249 | 250 | class TestIdentification: 251 | def __init__(self, x, y, intensity, size, sigma_back, runs = 100): 252 | self.background = partial(back_normal, (10, 10), mu=10, sigma=sigma_back) 253 | self.spot = partial(draw_gauss, x, y, size, multiplicator=intensity) 254 | self.runs = runs 255 | 256 | def __iter__(self): 257 | for i in range(self.runs): 258 | npimage = self.background() 259 | self.spot(npimage) 260 | yield npimage 261 | 262 | #### test helper #### 263 | def compute_bias(values): 264 | measured, true = zip(*values) 265 | return np.mean(measured) - np.mean(true) 266 | 267 | def compute_stddev(values): 268 | diff = [(measured - true)**2 for measured, true in values] 269 | return np.mean(diff)**0.5 270 | 271 | def print_bias_stddev(bias, stddev, prefix="", round_=4): 272 | if not prefix == "": 273 | prefix = prefix + " " 274 | print(prefix + "bias %s, sigma %s" % (round(bias, round_), round(stddev, round_))) 275 | ###################### 276 | 277 | #### main methods #### 278 | def tracking(test=None): 279 | tester = TestTracking() 280 | if test is None: 281 | tester.run_all() 282 | else: 283 | [item for item in tester.run(test)] 284 | 285 | def identification(sys_args): 286 | x_true, y_true = 5, 5 287 | sigma_back = 1 288 | nRuns = 100 289 | for intensity in np.arange(10, 1, -1): 290 | print(intensity / sigma_back), 291 | tester = TestIdentification(x_true, y_true, intensity, 3, sigma_back, nRuns) 292 | xs = [] 293 | ys = [] 294 | xerr = np.zeros((nRuns)) 295 | snr = np.zeros((nRuns)) 296 | for i, image in enumerate(tester): 297 | try: 298 | z, pcov = guess_from_Gaussian(image, x_true, y_true, 10) 299 | x, y = z 300 | xerr[i] = (pcov[0, 0] + pcov[1, 1])**.5 301 | snr[i] = signal_to_noise(image, x, y, 3) 302 | xs.append((x, x_true)) 303 | ys.append((y, y_true)) 304 | except: 305 | print(i, "failed") 306 | bias_pos = (compute_bias(xs)**2 + compute_bias(ys)**2)**0.5 307 | stddev_pos = (compute_stddev(xs)**2 + compute_stddev(ys)**2)**0.5 308 | print_bias_stddev(bias_pos, stddev_pos) 309 | print(np.mean(xerr), np.mean(snr)) 310 | 311 | def calc_v(z_r, z_c, z_dev, z_c_dev, E, dE): 312 | # make sure the state variables are numpy array 313 | z_r, z_c, z_dev, z_c_dev = np.asarray(z_r), np.asarray(z_c), np.asarray(z_dev), np.asarray(z_c_dev) 314 | # calculate the distance vector = real coordinate - centrum coordinate 315 | z = z_r - z_c 316 | z_dev = (z_dev**2 + z_c_dev**2)**.5 317 | v = - 0.5 * z / E 318 | v_dev = - 0.5 * z_dev / E 319 | return v[0], v[1], v_dev[0], v_dev[1] 320 | 321 | def bootstrap(data, axis = 0, n = 10000): 322 | return np.mean(np.asarray([simple_bootstrap(data, axis = axis, n = 100) for i in range(int(n/100))]), axis = 0) 323 | 324 | def simple_bootstrap(data, axis = 0, n = 100): 325 | this_data = data[np.random.randint(data.shape[axis], size = (data.shape[axis], n))] 326 | bootstrap_means = np.mean(this_data, axis = axis) 327 | return np.std(bootstrap_means, axis = axis, ddof = 1) 328 | 329 | def display(sys_args): 330 | import gobject 331 | import gtk 332 | 333 | import matplotlib 334 | matplotlib.use('GTKAgg') 335 | import matplotlib.cm as cm 336 | 337 | import matplotlib.pyplot as plt 338 | 339 | fig = plt.figure(1) 340 | ax = fig.add_subplot(111) 341 | 342 | def animate(): 343 | tester = TestTracking() 344 | tester_run = tester.run(sys_args[2], output=True) 345 | image, xs, ys = tester_run.next() 346 | im = ax.imshow(image, cmap=cm.gray, interpolation="nearest") 347 | plot = ax.plot(ys, xs, "go") 348 | for image, xs, ys in tester_run: 349 | ax.set_xlim(0, 200) 350 | ax.set_ylim(0, 200) 351 | time.sleep(0.1) 352 | im.set_data(image) 353 | plot[0].set_data(ys, xs) 354 | fig.canvas.draw() 355 | raise SystemExit 356 | 357 | gobject.idle_add(animate) 358 | plt.show() 359 | 360 | def profile(sys_args): 361 | import cProfile 362 | tester = TestTracking() 363 | command = """tester.run_all()""" 364 | cProfile.runctx(command, globals(), locals(), "profile.dat") 365 | ####################### 366 | 367 | if __name__ == "__main__": 368 | import sys 369 | 370 | sys.stderr = open("errs.log", "w") 371 | 372 | #TODO: There must be a better way to do this! 373 | 374 | if len(sys.argv) == 1: 375 | print("not enough arguments") 376 | 377 | elif sys.argv[1] == "tracking": 378 | if len(sys.argv) == 3: 379 | tracking(sys.argv[2]) 380 | else: 381 | tracking() 382 | 383 | elif sys.argv[1] == "kalman": 384 | kalman(sys.argv) 385 | 386 | elif sys.argv[1] == "identification": 387 | identification(sys.argv) 388 | 389 | elif sys.argv[1] == "display": 390 | display(sys.argv) 391 | 392 | elif sys.argv[1] == "profile": 393 | profile(sys.argv) 394 | 395 | else: 396 | print("unknown command") 397 | -------------------------------------------------------------------------------- /source/pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["hatchling"] 3 | build-backend = "hatchling.build" 4 | 5 | [project] 6 | name = "EasyLEED" 7 | version = "2.5.2" 8 | dependencies = [ 9 | "numpy", 10 | "matplotlib", 11 | "scipy", 12 | "pillow", 13 | "pathlib", 14 | "pandas", 15 | "scikit-image", 16 | "PyQt5" 17 | ] 18 | 19 | authors = [{ name="Andreas Mayer", email="andimscience@gmail.com" }, 20 | {name="Hanna Salopaasi"}, 21 | {name= "Nicola Ferralis"}, 22 | ] 23 | 24 | maintainers = [ 25 | {name = "Andreas Mayer", email = "andimscience@gmail.com"} 26 | ] 27 | 28 | description = "Automated extraction of intensity-energy spectra from low-energy electron diffraction patterns" 29 | 30 | readme = "../README.md" 31 | 32 | license = {text = "GNU General Public License v2 (GPLv2)"} 33 | 34 | requires-python = ">=3.9" 35 | classifiers = [ 36 | "Programming Language :: Python :: 3", 37 | "License :: OSI Approved :: GNU General Public License v2 (GPLv2)", 38 | "Operating System :: OS Independent", 39 | "Development Status :: 5 - Production/Stable", 40 | "Programming Language :: Python", 41 | "Programming Language :: Python :: 3", 42 | "Programming Language :: Python :: 3.9", 43 | "Programming Language :: Python :: 3.10", 44 | "Programming Language :: Python :: 3.11", 45 | "Programming Language :: Python :: 3.12", 46 | "Intended Audience :: Science/Research", 47 | "Topic :: Scientific/Engineering :: Chemistry", 48 | "Topic :: Scientific/Engineering :: Physics", 49 | ] 50 | 51 | [project.optional-dependencies] 52 | skimage = ["scikit-image"] 53 | fits = ["pyfits"] 54 | 55 | [project.gui-scripts] 56 | easyleed = "easyleed.__main__:main" 57 | 58 | [project.urls] 59 | Homepage = "https://andim.github.io/easyleed/" 60 | Documentation = "https://qimmuno.com/easyleed/userdoc.html" 61 | Repository = "https://github.com/andim/easyleed.git" 62 | Issues = "https://andim.github.io/easyleed/issues" 63 | Changelog = "https://github.com/andim/easyleed/blob/master/changelog" 64 | -------------------------------------------------------------------------------- /source/setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | setup( 4 | name='EasyLEED', 5 | packages=['easyleed'], 6 | install_requires=['numpy', 'matplotlib', 'scipy', 'pillow', 'pathlib', 'pandas'], 7 | extras_require={'skimage': ["scikit-image"], 'fits' : ["pyfits"]}, 8 | # scripts=['easyleed.pyw'], 9 | entry_points={'gui_scripts' : ['easyleed = easyleed.__main__:main']}, 10 | version='2.5.2', 11 | description='Automated extraction of intensity-energy spectra from low-energy electron diffraction patterns', 12 | long_description= """ 13 | EasyLEED facilitates data analysis of images obtained by low-energy electron diffraction, a common technique in surface science. It aims to automate the process of extracting intensity-energy spectra from a series of diffraction patterns acquired at different beam energies. At its core a tracking algorithm exploiting the specifics of the underlying physics (see `paper `_) allows to link the position of the diffraction maxima between subsequent images. 14 | 15 | For more info please see https://andim.github.io/easyleed/ or contact the authors via Email (andimscience@gmail.com) or Twitter (https://twitter.com/andimscience) 16 | """, 17 | author='Andreas Mayer, Hanna Salopaasi, Nicola Ferralis', 18 | author_email='andimscience@gmail.com', 19 | url='https://andim.github.io/easyleed/', 20 | download_url='https://github.com/andim/easyleed/archive/2.5.2.tar.gz', 21 | keywords=['LEED', 'surface science', 'image analysis', 'I(E) spectra', 'spot tracking'], 22 | license='GPLv2', 23 | platforms='any', 24 | classifiers=[ 25 | 'License :: OSI Approved :: GNU General Public License v2 (GPLv2)', 26 | 'Development Status :: 5 - Production/Stable', 27 | 'Programming Language :: Python', 28 | 'Programming Language :: Python :: 2', 29 | 'Programming Language :: Python :: 2.7', 30 | 'Programming Language :: Python :: 3', 31 | 'Programming Language :: Python :: 3.5', 32 | 'Programming Language :: Python :: 3.6', 33 | 'Programming Language :: Python :: 3.7', 34 | 'Programming Language :: Python :: 3.8', 35 | 'Programming Language :: Python :: 3.9', 36 | 'Programming Language :: Python :: 3.10', 37 | 'Programming Language :: Python :: 3.11', 38 | 'Programming Language :: Python :: 3.12', 39 | 'Intended Audience :: Science/Research', 40 | 'Topic :: Scientific/Engineering :: Chemistry', 41 | 'Topic :: Scientific/Engineering :: Physics', 42 | ], 43 | ) 44 | --------------------------------------------------------------------------------