diff --git a/CMakeLists.txt b/CMakeLists.txt index 07017d5d4..bb05ed3a6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -37,7 +37,8 @@ option (SLS_USE_GUI "GUI" OFF) option (SLS_USE_TESTS "TESTS" ON) option (SLS_USE_INTEGRATION_TESTS "Integration Tests" ON) -option(SLS_USE_SANITIZER OFF) +option(SLS_USE_SANITIZER "Sanitizers for debugging" OFF) +option(SLS_USE_PYTHON "Python bindings" OFF) set(CMAKE_EXPORT_COMPILE_COMMANDS ON) @@ -111,7 +112,9 @@ if (SLS_USE_INTEGRATION_TESTS) add_subdirectory(integrationTests) endif (SLS_USE_INTEGRATION_TESTS) - +if (SLS_USE_PYTHON) + add_subdirectory(python) +endif(SLS_USE_PYTHON) if(SLS_MASTER_PROJECT) diff --git a/python/LICENSE b/python/LICENSE new file mode 100644 index 000000000..94a9ed024 --- /dev/null +++ b/python/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/python/README.md b/python/README.md new file mode 100644 index 000000000..619ff6def --- /dev/null +++ b/python/README.md @@ -0,0 +1,63 @@ +# sls_detector: Python interface to slsDetectorPackage +Python interface to the Sls Detector Software. + +### Documentation ### +Sphinx built documentation is available here: +[https://slsdetectorgroup.github.io/sls_detector/](https://slsdetectorgroup.github.io/sls_detector/) + + +### Install using conda ### + +Binaries are available using conda. This installs both the detector software and the Python +interface. + +```bash +#Add conda channels +conda config --add channels conda-forge +conda config --add channels slsdetectorgroup + +#Install latest version +conda install sls_detector + +#Install specific version +conda install sls_detector=3.0.1 + +#Scientific Linux 6 version (GLIBC2.12) +conda install sls_detector=SL6_3.0.1 +``` + +### Building using conda-build ### + +Needs [sls_detector_software](https://github.com/slsdetectorgroup/sls_detector_software) installed. + +```bash +#Clone source code +git clone https://github.com/slsdetectorgroup/sls_detector.git + +#Checkout the branch needed +git checkout 3.0.1 + +#Build and install the local version +conda-build sls_detector +conda install --use-local sls_detector + + +``` +### Developer build ### + +IF you if you are developing and are making constant changes to the code it's a bit cumbersome +to build with conda and install. Then an easier way is to build the C/C++ parts in the package +directory and temporary add this to the path + +```bash +#in path/to/sls_detector +python setup.py build_ext --inplace +``` +Then in your Python script +```python + +import sys +sys.path.append('/path/to/sls_detector') +from sls_detector import Detector +``` + diff --git a/python/_sls_detector.cpython-37m-x86_64-linux-gnu.so b/python/_sls_detector.cpython-37m-x86_64-linux-gnu.so new file mode 100755 index 000000000..fdc85cc22 Binary files /dev/null and b/python/_sls_detector.cpython-37m-x86_64-linux-gnu.so differ diff --git a/python/conda-recipe/bld.bat b/python/conda-recipe/bld.bat new file mode 100644 index 000000000..602113031 --- /dev/null +++ b/python/conda-recipe/bld.bat @@ -0,0 +1,2 @@ +"%PYTHON%" setup.py install +if errorlevel 1 exit 1 \ No newline at end of file diff --git a/python/conda-recipe/build.sh b/python/conda-recipe/build.sh new file mode 100644 index 000000000..b3bcf1398 --- /dev/null +++ b/python/conda-recipe/build.sh @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +${PYTHON} setup.py install # Python command to install the script \ No newline at end of file diff --git a/python/conda-recipe/conda_build_config.yaml b/python/conda-recipe/conda_build_config.yaml new file mode 100644 index 000000000..c3ae13fbe --- /dev/null +++ b/python/conda-recipe/conda_build_config.yaml @@ -0,0 +1,4 @@ +python: + - 3.5 + - 3.6 + - 3.7 diff --git a/python/conda-recipe/conda_env.txt b/python/conda-recipe/conda_env.txt new file mode 100644 index 000000000..dc0caac26 --- /dev/null +++ b/python/conda-recipe/conda_env.txt @@ -0,0 +1,4 @@ +# This file may be used to create an environment using: +# $ conda create --name --file +# platform: osx-64 + diff --git a/python/conda-recipe/meta.yaml b/python/conda-recipe/meta.yaml new file mode 100644 index 000000000..b8a1020a8 --- /dev/null +++ b/python/conda-recipe/meta.yaml @@ -0,0 +1,43 @@ +package: + name: sls_detector + version: 4.0.1 + +build: + number: 0 + +source: + path: .. + +requirements: + build: + - {{ compiler('c') }} + - {{ compiler('cxx') }} + - cmake + - python {{ python }} + - libpng >=1.6.32,<1.6.35 + - setuptools + - sls_detector_lib 4.0.1 + - pyzmq + - pybind11 + + host: + - python + - pybind11 + + run: + - python + - numpy + - sls_detector_lib 4.0.1 + - pyzmq + - libstdcxx-ng + - libgcc-ng + + +test: + imports: + - sls_detector + + + +about: + summary: "Interacting with detectors" diff --git a/python/setup.py b/python/setup.py new file mode 100644 index 000000000..b306185e3 --- /dev/null +++ b/python/setup.py @@ -0,0 +1,149 @@ +""" +Setup file for sls_detector +Build upon the pybind11 example found here: https://github.com/pybind/python_example +""" +from setuptools import setup, Extension, find_packages +from setuptools.command.build_ext import build_ext +import sys +import setuptools +import os + +__version__ = '4.0.0' + + +def get_conda_path(): + """ + Keep this a function if we need some fancier logic later + """ + print('Prefix: ', os.environ['CONDA_PREFIX']) + return os.environ['CONDA_PREFIX'] + + +class get_pybind_include(object): + """Helper class to determine the pybind11 include path + The purpose of this class is to postpone importing pybind11 + until it is actually installed, so that the ``get_include()`` + method can be invoked. """ + + def __init__(self, user=False): + self.user = user + + def __str__(self): + import pybind11 + return pybind11.get_include(self.user) + + +# ext_modules = [ +# Extension( +# '_sls_detector', +# ['src/main.cpp'], +# include_dirs=[ +# # Path to pybind11 headers +# get_pybind_include(), +# get_pybind_include(user=True), +# os.path.join(get_sls_path(), 'slsDetectorSoftware/multiSlsDetector'), +# os.path.join(get_sls_path(), 'slsReceiverSoftware/include/'), +# os.path.join(get_sls_path(),'slsDetectorSoftware/commonFiles/'), +# os.path.join(get_sls_path(), 'slsDetectorSoftware/slsDetector'), +# os.path.join(get_sls_path(), 'slsDetectorSoftware/slsDetectorAnalysis'), +# os.path.join(get_sls_path(), 'slsDetectorSoftware/slsReceiverInterface/'), +# +# ], +# libraries = ['SlsDetector', 'zmq'], +# library_dirs = [os.path.join(get_sls_path(),'build/bin'), +# os.path.join(get_sls_path(),'slsReceiverSoftware/include')], +# +# language='c++' +# ), +# ] + +ext_modules = [ + Extension( + '_sls_detector', + ['src/main.cpp'], + include_dirs=[ + # Path to pybind11 headers + get_pybind_include(), + get_pybind_include(user=True), + os.path.join(get_conda_path(), 'include/slsDetectorPackage'), + + ], + libraries=['SlsDetector', 'SlsReceiver', 'zmq'], + library_dirs=[ + os.path.join(get_conda_path(), 'lib'), + os.path.join(get_conda_path(), 'bin'), + ], + + language='c++' + ), +] + + +# As of Python 3.6, CCompiler has a `has_flag` method. +# cf http://bugs.python.org/issue26689 +def has_flag(compiler, flagname): + """Return a boolean indicating whether a flag name is supported on + the specified compiler. + """ + import tempfile + with tempfile.NamedTemporaryFile('w', suffix='.cpp') as f: + f.write('int main (int argc, char **argv) { return 0; }') + try: + compiler.compile([f.name], extra_postargs=[flagname]) + except setuptools.distutils.errors.CompileError: + return False + return True + + +def cpp_flag(compiler): + """Return the -std=c++[11/14] compiler flag. + The c++14 is prefered over c++11 (when it is available). + """ + if has_flag(compiler, '-std=c++14'): + return '-std=c++14' + elif has_flag(compiler, '-std=c++11'): + return '-std=c++11' + else: + raise RuntimeError('Unsupported compiler -- at least C++11 support ' + 'is needed!') + + +class BuildExt(build_ext): + """A custom build extension for adding compiler-specific options.""" + c_opts = { + 'msvc': ['/EHsc'], + 'unix': [], + } + + if sys.platform == 'darwin': + c_opts['unix'] += ['-stdlib=libc++', '-mmacosx-version-min=10.7'] + + def build_extensions(self): + ct = self.compiler.compiler_type + opts = self.c_opts.get(ct, []) + if ct == 'unix': + opts.append('-DVERSION_INFO="%s"' % self.distribution.get_version()) + opts.append(cpp_flag(self.compiler)) + if has_flag(self.compiler, '-fvisibility=hidden'): + opts.append('-fvisibility=hidden') + elif ct == 'msvc': + opts.append('/DVERSION_INFO=\\"%s\\"' % self.distribution.get_version()) + for ext in self.extensions: + ext.extra_compile_args = opts + build_ext.build_extensions(self) + + +setup( + name='sls_detector', + version=__version__, + author='Erik Frojdh', + author_email='erik.frojdh@psi.ch', + url='https://github.com/slsdetectorgroup/sls_detector', + description='Detector API for SLS Detector Group detectors', + long_description='', + packages=find_packages(exclude=['contrib', 'docs', 'tests']), + ext_modules=ext_modules, + install_requires=['pybind11>=2.2'], + cmdclass={'build_ext': BuildExt}, + zip_safe=False, +) diff --git a/python/simple-integration-tests/eiger/__pycache__/config_test.cpython-37-PYTEST.pyc b/python/simple-integration-tests/eiger/__pycache__/config_test.cpython-37-PYTEST.pyc new file mode 100644 index 000000000..5ebee4b77 Binary files /dev/null and b/python/simple-integration-tests/eiger/__pycache__/config_test.cpython-37-PYTEST.pyc differ diff --git a/python/simple-integration-tests/eiger/__pycache__/fixtures.cpython-37.pyc b/python/simple-integration-tests/eiger/__pycache__/fixtures.cpython-37.pyc new file mode 100644 index 000000000..1d94f49b2 Binary files /dev/null and b/python/simple-integration-tests/eiger/__pycache__/fixtures.cpython-37.pyc differ diff --git a/python/simple-integration-tests/eiger/__pycache__/test_dynamic_range.cpython-37-PYTEST.pyc b/python/simple-integration-tests/eiger/__pycache__/test_dynamic_range.cpython-37-PYTEST.pyc new file mode 100644 index 000000000..c8e505b16 Binary files /dev/null and b/python/simple-integration-tests/eiger/__pycache__/test_dynamic_range.cpython-37-PYTEST.pyc differ diff --git a/python/simple-integration-tests/eiger/__pycache__/test_eiger_specific.cpython-37-PYTEST.pyc b/python/simple-integration-tests/eiger/__pycache__/test_eiger_specific.cpython-37-PYTEST.pyc new file mode 100644 index 000000000..2ca8265b7 Binary files /dev/null and b/python/simple-integration-tests/eiger/__pycache__/test_eiger_specific.cpython-37-PYTEST.pyc differ diff --git a/python/simple-integration-tests/eiger/__pycache__/test_firmware.cpython-37-PYTEST.pyc b/python/simple-integration-tests/eiger/__pycache__/test_firmware.cpython-37-PYTEST.pyc new file mode 100644 index 000000000..bc17adbf6 Binary files /dev/null and b/python/simple-integration-tests/eiger/__pycache__/test_firmware.cpython-37-PYTEST.pyc differ diff --git a/python/simple-integration-tests/eiger/__pycache__/test_general.cpython-37-PYTEST.pyc b/python/simple-integration-tests/eiger/__pycache__/test_general.cpython-37-PYTEST.pyc new file mode 100644 index 000000000..f0a0f3613 Binary files /dev/null and b/python/simple-integration-tests/eiger/__pycache__/test_general.cpython-37-PYTEST.pyc differ diff --git a/python/simple-integration-tests/eiger/__pycache__/test_load_config.cpython-37-PYTEST.pyc b/python/simple-integration-tests/eiger/__pycache__/test_load_config.cpython-37-PYTEST.pyc new file mode 100644 index 000000000..404399401 Binary files /dev/null and b/python/simple-integration-tests/eiger/__pycache__/test_load_config.cpython-37-PYTEST.pyc differ diff --git a/python/simple-integration-tests/eiger/__pycache__/test_network.cpython-37-PYTEST.pyc b/python/simple-integration-tests/eiger/__pycache__/test_network.cpython-37-PYTEST.pyc new file mode 100644 index 000000000..7972e53f5 Binary files /dev/null and b/python/simple-integration-tests/eiger/__pycache__/test_network.cpython-37-PYTEST.pyc differ diff --git a/python/simple-integration-tests/eiger/__pycache__/test_paths_and_files.cpython-37-PYTEST.pyc b/python/simple-integration-tests/eiger/__pycache__/test_paths_and_files.cpython-37-PYTEST.pyc new file mode 100644 index 000000000..f5f84fe14 Binary files /dev/null and b/python/simple-integration-tests/eiger/__pycache__/test_paths_and_files.cpython-37-PYTEST.pyc differ diff --git a/python/simple-integration-tests/eiger/__pycache__/test_threshold.cpython-37-PYTEST.pyc b/python/simple-integration-tests/eiger/__pycache__/test_threshold.cpython-37-PYTEST.pyc new file mode 100644 index 000000000..256e89eea Binary files /dev/null and b/python/simple-integration-tests/eiger/__pycache__/test_threshold.cpython-37-PYTEST.pyc differ diff --git a/python/simple-integration-tests/eiger/__pycache__/test_time.cpython-37-PYTEST.pyc b/python/simple-integration-tests/eiger/__pycache__/test_time.cpython-37-PYTEST.pyc new file mode 100644 index 000000000..59f2fcd57 Binary files /dev/null and b/python/simple-integration-tests/eiger/__pycache__/test_time.cpython-37-PYTEST.pyc differ diff --git a/python/simple-integration-tests/eiger/__pycache__/test_trimbits_and_dacs.cpython-37-PYTEST.pyc b/python/simple-integration-tests/eiger/__pycache__/test_trimbits_and_dacs.cpython-37-PYTEST.pyc new file mode 100644 index 000000000..db4e43254 Binary files /dev/null and b/python/simple-integration-tests/eiger/__pycache__/test_trimbits_and_dacs.cpython-37-PYTEST.pyc differ diff --git a/python/simple-integration-tests/eiger/__pycache__/test_version_numbers.cpython-37-PYTEST.pyc b/python/simple-integration-tests/eiger/__pycache__/test_version_numbers.cpython-37-PYTEST.pyc new file mode 100644 index 000000000..0acf8a738 Binary files /dev/null and b/python/simple-integration-tests/eiger/__pycache__/test_version_numbers.cpython-37-PYTEST.pyc differ diff --git a/python/simple-integration-tests/eiger/config_test.py b/python/simple-integration-tests/eiger/config_test.py new file mode 100644 index 000000000..bd19367de --- /dev/null +++ b/python/simple-integration-tests/eiger/config_test.py @@ -0,0 +1,17 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +Created on Tue Nov 14 16:49:07 2017 + +@author: l_frojdh +""" + +fw_version = 22 +detector_type = 'Eiger' +known_hostnames = ['beb083', 'beb098'] +image_size = (512,1024) #rows, cols +module_geometry = (1,2) #horizontal, vertical + +#Remember to change these in the settings file as well! +settings_path = '/home/l_frojdh/slsDetectorPackage/settingsdir/eiger' +file_path = '/home/l_frojdh/out' \ No newline at end of file diff --git a/python/simple-integration-tests/eiger/fixtures.py b/python/simple-integration-tests/eiger/fixtures.py new file mode 100644 index 000000000..a59fa6961 --- /dev/null +++ b/python/simple-integration-tests/eiger/fixtures.py @@ -0,0 +1,27 @@ +import pytest + +from sls_detector import Detector + +@pytest.fixture +def detector(): + from sls_detector import Detector + return Detector() + +@pytest.fixture +def eiger(): + from sls_detector import Eiger + d = Eiger() + d.n_frames = 1 + d.exposure_time = 1 + d.file_write = False + return d + + +@pytest.fixture +def jungfrau(): + from sls_detector import Jungfrau + return Jungfrau() + +detector_type = Detector().detector_type +eigertest = pytest.mark.skipif(detector_type != 'Eiger', reason = 'Only valid for Eiger') +jungfrautest = pytest.mark.skipif(detector_type != 'Jungfrau', reason = 'Only valid for Jungfrau') \ No newline at end of file diff --git a/python/simple-integration-tests/eiger/settingsdir/standard/5000eV/noise.sn083 b/python/simple-integration-tests/eiger/settingsdir/standard/5000eV/noise.sn083 new file mode 100644 index 000000000..71f43f7bd Binary files /dev/null and b/python/simple-integration-tests/eiger/settingsdir/standard/5000eV/noise.sn083 differ diff --git a/python/simple-integration-tests/eiger/settingsdir/standard/5000eV/noise.sn098 b/python/simple-integration-tests/eiger/settingsdir/standard/5000eV/noise.sn098 new file mode 100644 index 000000000..71f43f7bd Binary files /dev/null and b/python/simple-integration-tests/eiger/settingsdir/standard/5000eV/noise.sn098 differ diff --git a/python/simple-integration-tests/eiger/settingsdir/standard/5000eV/trimbits.sn000 b/python/simple-integration-tests/eiger/settingsdir/standard/5000eV/trimbits.sn000 new file mode 100644 index 000000000..086352948 Binary files /dev/null and b/python/simple-integration-tests/eiger/settingsdir/standard/5000eV/trimbits.sn000 differ diff --git a/python/simple-integration-tests/eiger/settingsdir/standard/6000eV/noise.sn083 b/python/simple-integration-tests/eiger/settingsdir/standard/6000eV/noise.sn083 new file mode 100644 index 000000000..a22532e3f Binary files /dev/null and b/python/simple-integration-tests/eiger/settingsdir/standard/6000eV/noise.sn083 differ diff --git a/python/simple-integration-tests/eiger/settingsdir/standard/6000eV/noise.sn098 b/python/simple-integration-tests/eiger/settingsdir/standard/6000eV/noise.sn098 new file mode 100644 index 000000000..a22532e3f Binary files /dev/null and b/python/simple-integration-tests/eiger/settingsdir/standard/6000eV/noise.sn098 differ diff --git a/python/simple-integration-tests/eiger/settingsdir/standard/6000eV/trimbits.sn000 b/python/simple-integration-tests/eiger/settingsdir/standard/6000eV/trimbits.sn000 new file mode 100644 index 000000000..086352948 Binary files /dev/null and b/python/simple-integration-tests/eiger/settingsdir/standard/6000eV/trimbits.sn000 differ diff --git a/python/simple-integration-tests/eiger/settingsdir/standard/7000eV/noise.sn083 b/python/simple-integration-tests/eiger/settingsdir/standard/7000eV/noise.sn083 new file mode 100644 index 000000000..4a57a7341 Binary files /dev/null and b/python/simple-integration-tests/eiger/settingsdir/standard/7000eV/noise.sn083 differ diff --git a/python/simple-integration-tests/eiger/settingsdir/standard/7000eV/noise.sn098 b/python/simple-integration-tests/eiger/settingsdir/standard/7000eV/noise.sn098 new file mode 100644 index 000000000..4a57a7341 Binary files /dev/null and b/python/simple-integration-tests/eiger/settingsdir/standard/7000eV/noise.sn098 differ diff --git a/python/simple-integration-tests/eiger/settingsdir/standard/7000eV/trimbits.sn000 b/python/simple-integration-tests/eiger/settingsdir/standard/7000eV/trimbits.sn000 new file mode 100644 index 000000000..086352948 Binary files /dev/null and b/python/simple-integration-tests/eiger/settingsdir/standard/7000eV/trimbits.sn000 differ diff --git a/python/simple-integration-tests/eiger/test.config b/python/simple-integration-tests/eiger/test.config new file mode 100644 index 000000000..75fc82b40 --- /dev/null +++ b/python/simple-integration-tests/eiger/test.config @@ -0,0 +1,33 @@ +detsizechan 1024 512 + +#hostname for top+bottom+ +hostname beb083+beb098+ + +#top +0:rx_tcpport 1954 +0:lock 0 +0:rx_udpport 50010 +0:rx_udpport2 50011 +0:rx_hostname mpc2048 +0:flippeddatax 0 + +#bottom +1:rx_tcpport 1955 +1:lock 0 +1:rx_udpport 50004 +1:rx_udpport2 50005 +1:rx_hostname mpc2048 +1:flippeddatax 1 + +settingsdir /home/l_frojdh/slsDetectorPackage/settingsdir/eiger +outdir /home/l_frojdh/out +vthreshold 1500 +vtr 4000 +dr 32 + +threaded 1 +tengiga 0 +vhighvoltage 150 +iodelay 660 + +#gappixels 1 diff --git a/python/simple-integration-tests/eiger/test.par b/python/simple-integration-tests/eiger/test.par new file mode 100644 index 000000000..7fef9adcb --- /dev/null +++ b/python/simple-integration-tests/eiger/test.par @@ -0,0 +1,2 @@ +vrf 3000 +vthreshold 1800 diff --git a/python/simple-integration-tests/eiger/test_dynamic_range.py b/python/simple-integration-tests/eiger/test_dynamic_range.py new file mode 100644 index 000000000..1a162fca6 --- /dev/null +++ b/python/simple-integration-tests/eiger/test_dynamic_range.py @@ -0,0 +1,44 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +Testing setting dynamic range for Eiger. +If the detector is not Eiger the tests are skipped +""" +import pytest +import config_test +from fixtures import detector, eiger, jungfrau, eigertest, jungfrautest +from sls_detector.errors import DetectorValueError + + +@eigertest +def test_set_dynamic_range_and_make_acq(eiger): + eiger.exposure_time = 0.5 + eiger.n_frames = 2 + for dr in [4, 8, 16, 32]: + eiger.dynamic_range = dr + assert eiger.dynamic_range == dr + eiger.acq() + assert eiger.frames_caught == 2 + + +@eigertest +def test_set_dynamic_range_raises(eiger): + with pytest.raises(DetectorValueError): + eiger.dynamic_range = 1 + with pytest.raises(DetectorValueError): + eiger.dynamic_range = 75 + with pytest.raises(DetectorValueError): + eiger.dynamic_range = -3 + with pytest.raises(DetectorValueError): + eiger.dynamic_range = 12 + +@eigertest +def test_set_dynamic_range_reduces_speed(eiger): + eiger.readout_clock = 'Full Speed' + eiger.dynamic_range = 32 + assert eiger.dynamic_range == 32 + assert eiger.readout_clock == 'Quarter Speed' + + eiger.dynamic_range = 16 + assert eiger.dynamic_range == 16 + assert eiger.readout_clock == 'Half Speed' diff --git a/python/simple-integration-tests/eiger/test_eiger_specific.py b/python/simple-integration-tests/eiger/test_eiger_specific.py new file mode 100644 index 000000000..31f656ab6 --- /dev/null +++ b/python/simple-integration-tests/eiger/test_eiger_specific.py @@ -0,0 +1,119 @@ +import pytest +import config_test +import time +from sls_detector.errors import DetectorValueError + +from fixtures import eiger, eigertest + + + + + +@eigertest +def test_set_matrix_reset(eiger): + eiger.eiger_matrix_reset = False + assert eiger.eiger_matrix_reset == False + eiger.eiger_matrix_reset = True + assert eiger.eiger_matrix_reset == True + +@eigertest +def test_set_tx_delay_left_single(eiger): + eiger.tx_delay.left[0] = 130 + assert eiger.tx_delay.left[0] == 130 + eiger.tx_delay.left[1] = 150 + assert eiger.tx_delay.left[1] == 150 + eiger.tx_delay.left[0] = 0 + eiger.tx_delay.left[1] = 0 + assert eiger.tx_delay.left[0] == 0 + assert eiger.tx_delay.left[1] == 0 + +@eigertest +def test_set_tx_delay_right_single(eiger): + eiger.tx_delay.right[0] = 130 + assert eiger.tx_delay.right[0] == 130 + eiger.tx_delay.right[1] = 150 + assert eiger.tx_delay.right[1] == 150 + eiger.tx_delay.right[0] = 0 + eiger.tx_delay.right[1] = 0 + assert eiger.tx_delay.right[0] == 0 + assert eiger.tx_delay.right[1] == 0 + +@eigertest +def test_set_tx_delay_frame_single(eiger): + eiger.tx_delay.frame[0] = 500 + eiger.tx_delay.frame[1] = 600 + assert eiger.tx_delay.frame[0] == 500 + assert eiger.tx_delay.frame[1] == 600 + + eiger.tx_delay.frame[0] = 0 + eiger.tx_delay.frame[1] = 0 + assert eiger.tx_delay.frame[0] == 0 + assert eiger.tx_delay.frame[1] == 0 + +@eigertest +def test_tx_delay_from_list(eiger): + eiger.tx_delay.left = [123,456] + assert eiger.tx_delay.left[:] == [123,456] + eiger.tx_delay.right = [789,100] + assert eiger.tx_delay.right[:] == [789,100] + eiger.tx_delay.frame = [1000,90000] + assert eiger.tx_delay.frame[:] == [1000,90000] + + eiger.tx_delay.left = [0, 0] + eiger.tx_delay.right = [0, 0] + eiger.tx_delay.frame = [0, 0] + assert eiger.tx_delay.left[:] == [0, 0] + assert eiger.tx_delay.right[:] == [0, 0] + assert eiger.tx_delay.frame[:] == [0, 0] + +@eigertest +def test_acitve(eiger): + eiger.file_write = False + eiger.reset_frames_caught() + eiger.active[1] = False + eiger.acq() + assert eiger._api.getFramesCaughtByReceiver(1) == 0 + assert eiger._api.getFramesCaughtByReceiver(0) == 1 + eiger.active = True + time.sleep(0.5) + eiger.acq() + assert eiger.frames_caught == 1 + +@eigertest +def test_set_default_settings(eiger): + eiger.default_settings() + assert eiger.n_frames == 1 + assert eiger.exposure_time == 1 + assert eiger.period == 0 + assert eiger.n_cycles == 1 + assert eiger.dynamic_range == 16 + +@eigertest +def test_flowcontrol10g(eiger): + eiger.flowcontrol_10g = True + assert eiger.flowcontrol_10g == True + eiger.flowcontrol_10g = False + assert eiger.flowcontrol_10g == False + +@eigertest +def test_read_vcmp(eiger): + eiger.vthreshold = 1500 + assert eiger.vcmp[:] == [1500]*4*eiger.n_modules + +@eigertest +def test_set_vcmp(eiger): + eiger.vcmp = [1000,1100,1200,1300,1400,1500,1600,1700] + assert eiger.vcmp[:] == [1000,1100,1200,1300,1400,1500,1600,1700] + eiger.vthreshold = 1500 + +#Disabled only works with receiver on the same pc +# @eigertest +# def test_setup500k(): +# from sls_detector import Eiger, free_shared_memory +# free_shared_memory() +# d = Eiger() +# d.setup500k(config_test.known_hostnames) +# d.acq() +# assert d.rx_tcpport == [1954,1955] +# assert d.frames_caught == 1 +# #could assert more setting but if the frame is caught it worked... \ No newline at end of file diff --git a/python/simple-integration-tests/eiger/test_firmware.py b/python/simple-integration-tests/eiger/test_firmware.py new file mode 100644 index 000000000..5c43fbc4f --- /dev/null +++ b/python/simple-integration-tests/eiger/test_firmware.py @@ -0,0 +1,128 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +Tests specific for the firmware. + +Check that register values are correct after starting an exposure + +0x4 exposure time +0x5 period +0x6 sub exposure time + +""" +import pytest +import config_test +from fixtures import detector, eiger, jungfrau, eigertest, jungfrautest +from sls_detector.errors import DetectorValueError +from sls_detector.utils import eiger_register_to_time + +# testdata_exptimes = [0.001, 0.002, 0.0236] + +@eigertest +def test_short_exposure_time(eiger): + t = 1.23 + eiger.exposure_time = t + eiger.file_write = False + eiger.start_detector() + eiger.stop_detector() + + #Register 0x4 holds exposure time + reg = eiger.register[0x4] + assert pytest.approx(t, 1e-9) == eiger_register_to_time(reg) + +@eigertest +def test_short_minimal_exposure_time(eiger): + t = 1e-8 + eiger.exposure_time = t + eiger.file_write = False + eiger.start_detector() + eiger.stop_detector() + + #Register 0x4 holds exposure time + reg = eiger.register[0x4] + assert pytest.approx(t, 1e-9) == eiger_register_to_time(reg) + + +@eigertest +def test_long_exposure_time(eiger): + t = 623 + eiger.exposure_time = t + eiger.file_write = False + eiger.start_detector() + eiger.stop_detector() + + # Register 0x4 holds exposure time + reg = eiger.register[0x4] + assert pytest.approx(t, 1e-9) == eiger_register_to_time(reg) + + +@eigertest +def test_short_period(eiger): + t = 0.1 + eiger.exposure_time = 0.001 + eiger.period = t + eiger.file_write = False + eiger.start_detector() + eiger.stop_detector() + + # Register 0x5 holds period + reg = eiger.register[0x5] + assert pytest.approx(t, 1e-9) == eiger_register_to_time(reg) + + +@eigertest +def test_long_period(eiger): + t = 8900 + eiger.exposure_time = 0.001 + eiger.period = t + eiger.file_write = False + eiger.start_detector() + eiger.stop_detector() + + # Register 0x5 holds period + reg = eiger.register[0x5] + assert pytest.approx(t, 1e-9) == eiger_register_to_time(reg) + +@eigertest +def test_zero_period_with_acq(eiger): + t = 0 + eiger.exposure_time = 0.001 + eiger.period = t + eiger.file_write = False + eiger.acq() + + # Register 0x5 holds period + reg = eiger.register[0x5] + assert pytest.approx(t, 1e-9) == eiger_register_to_time(reg) + + +testdata_times = [0.001, 0.002, 0.0236] +@eigertest +@pytest.mark.parametrize("t", testdata_times) +def test_subexptime(eiger,t): + eiger.sub_exposure_time = t + eiger.file_write = False + eiger.start_detector() + eiger.stop_detector() + + # Register 0x6 holds sub exposure time + # time is stored straight as n clocks + reg = eiger.register[0x6] + assert pytest.approx(t, 1e-9) == reg/100e6 + + +@eigertest +@pytest.mark.parametrize("t", testdata_times) +def test_subdeadtime(eiger, t): + eiger.sub_deadtime = t + eiger.sub_exposure_time = 1 + eiger.sub_exposure_time = 0.001 + eiger.file_write = False + eiger.start_detector() + eiger.stop_detector() + + # Register 0x7 holds sub period + # time is stored straight as n clocks + # exptime+deadtime + reg = eiger.register[0x7] + assert pytest.approx(t, 1e-7) == (reg/100e6-0.001) diff --git a/python/simple-integration-tests/eiger/test_general.py b/python/simple-integration-tests/eiger/test_general.py new file mode 100644 index 000000000..314f33e3b --- /dev/null +++ b/python/simple-integration-tests/eiger/test_general.py @@ -0,0 +1,187 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +General tests for the Detector class. Should not depend on the connected detector. Aim is to have tests working +for both Jungfrau and Eiger. + +NOTE! Uses hostnames from config_test +""" + +import pytest +import config_test +from fixtures import detector +from sls_detector.errors import DetectorValueError, DetectorError + + + +def test_error_handling(detector): + with pytest.raises(DetectorError): + detector._provoke_error() + +def test_not_busy(detector): + """Test that the detector is not busy from the start""" + assert detector.busy == False + +def test_reset_frames_caught(detector): + detector.file_write = False + detector.acq() + assert detector.frames_caught == 1 + detector.reset_frames_caught() + assert detector.frames_caught == 0 + +def test_set_busy_true_then_false(detector): + """Test both cases of assignment""" + detector.busy = True + assert detector.busy == True + detector.busy = False + assert detector.busy == False + +def test_set_readout_speed(detector): + for s in ['Full Speed', 'Half Speed', 'Quarter Speed', 'Super Slow Speed']: + detector.readout_clock = s + assert detector.readout_clock == s + +def test_wrong_speed_raises_error(detector): + with pytest.raises(KeyError): + detector.readout_clock = 'Something strange' + +def test_readout_clock_remains(detector): + s = detector.readout_clock + try: + detector.readout_clock = 'This does not exists' + except KeyError: + pass + assert detector.readout_clock == s + +def test_len_method(detector): + """to test this we need to know the length, this we get from the configuration of hostnames""" + assert len(detector) == len(config_test.known_hostnames) + +def test_setting_n_cycles_to_zero_gives_error(detector): + with pytest.raises(DetectorValueError): + detector.n_cycles = 0 + +def test_setting_n_cycles_to_negative_gives_error(detector): + with pytest.raises(DetectorValueError): + detector.n_cycles = -50 + +def test_set_cycles_frome_one_to_ten(detector): + for i in range(1,11): + detector.n_cycles = i + assert detector.n_cycles == i + detector.n_cycles = 1 + assert detector.n_cycles == 1 + +def test_get_detector_type(detector): + assert detector.detector_type == config_test.detector_type + + + +def test_set_file_index(detector): + detector.file_index = 5 + assert detector.file_index == 5 + +def test_negative_file_index_raises(detector): + with pytest.raises(ValueError): + detector.file_index = -8 + +def test_setting_file_name(detector): + fname = 'hej' + detector.file_name = fname + assert detector.file_name == fname + +def test_set_file_write(detector): + detector.file_write = True + assert detector.file_write == True + + detector.file_write = False + assert detector.file_write == False + + + +def test_set_high_voltage(detector): + detector.high_voltage = 55 + assert detector.high_voltage == 55 + +def test_negative_voltage_raises(detector): + with pytest.raises(DetectorValueError): + detector.high_voltage = -5 + +def test_high_voltage_raises_on_to_high(detector): + with pytest.raises(DetectorValueError): + detector.high_voltage = 500 + + + +def test_get_image_size(detector): + """Compares with the size in the config file""" + assert detector.image_size.rows == config_test.image_size[0] + assert detector.image_size.cols == config_test.image_size[1] + +def test_get_module_geometry(detector): + """Compares with the size in the config file""" + assert detector.module_geometry.horizontal == config_test.module_geometry[0] + assert detector.module_geometry.vertical == config_test.module_geometry[1] + +def test_set_nframes(detector): + detector.n_frames = 5 + assert detector.n_frames == 5 + detector.n_frames = 1 + assert detector.n_frames == 1 + +def test_set_n_measurements(detector): + detector.n_measurements = 7 + assert detector.n_measurements == 7 + detector.n_measurements = 1 + assert detector.n_measurements == 1 + +def test_negative_nframes_raises(detector): + with pytest.raises(DetectorValueError): + detector.n_frames = -2 + +def test_nmodules(detector): + """Assume that the number of modules should be the same as the number of hostnames""" + assert detector.n_modules == len(config_test.known_hostnames) + +def test_is_detector_online(detector): + assert detector.online == True + +def test_set_online(detector): + detector.online = False + assert detector.online == False + detector.online = True + assert detector.online == True + + + +def test_receiver_is_online(detector): + assert detector.receiver_online == True + +def test_set_receiver_online(detector): + detector.receiver_online = False + assert detector.receiver_online == False + detector.receiver_online = True + assert detector.receiver_online == True + +def test_set_receiver_online_raises_on_non_bool(detector): + with pytest.raises(TypeError): + detector.receiver_online = 'probably not this' + + + + +def test_set_period(detector): + detector.period = 5.123 + assert detector.period == 5.123 + detector.period = 0 + assert detector.period == 0 + + + +def test_set_timing_mode(detector): + detector.timing_mode = 'trigger' + assert detector.timing_mode == 'trigger' + detector.timing_mode = 'auto' + assert detector.timing_mode == 'auto' + + diff --git a/python/simple-integration-tests/eiger/test_load_config.py b/python/simple-integration-tests/eiger/test_load_config.py new file mode 100644 index 000000000..f9aa09de7 --- /dev/null +++ b/python/simple-integration-tests/eiger/test_load_config.py @@ -0,0 +1,38 @@ + +import pytest +import config_test +import os +dir_path = os.path.dirname(os.path.realpath(__file__)) +from sls_detector.detector import element_if_equal +from sls_detector.errors import DetectorValueError + + +from fixtures import eiger, eigertest + + +@eigertest +def test_load_config_file_eiger(eiger): + """Load a settings file and assert all settings""" + eiger.load_config(os.path.join(dir_path, 'test.config')) + + + assert eiger.rx_tcpport == [1954, 1955] + assert eiger.lock == False + assert eiger.rx_udpport == [50010, 50011, 50004, 50005] + assert eiger.rx_hostname == 'mpc2048' + assert eiger.flipped_data_x[:] == [False, True] + assert eiger.settings_path == config_test.settings_path + assert eiger.file_path == config_test.file_path + assert eiger.vthreshold == 1500 + assert element_if_equal(eiger.dacs.vtr[:]) == 4000 + assert eiger.dynamic_range == 32 + assert eiger.tengiga == False + assert eiger.high_voltage == 150 + assert element_if_equal(eiger.dacs.iodelay[:]) == 660 + +@eigertest +def test_load_parameters_file_eiger(eiger): + """Load a parametes file and assert the settings in the file""" + eiger.load_parameters(os.path.join(dir_path, 'test.par')) + assert element_if_equal(eiger.dacs.vrf[:]) == 3000 + assert eiger.vthreshold == 1800 \ No newline at end of file diff --git a/python/simple-integration-tests/eiger/test_network.py b/python/simple-integration-tests/eiger/test_network.py new file mode 100644 index 000000000..68e788e23 --- /dev/null +++ b/python/simple-integration-tests/eiger/test_network.py @@ -0,0 +1,81 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +Tests for network related functions of the detector +""" +import pytest +import config_test +from fixtures import eiger, eigertest, detector + + +# def test_last_client(detector): +# import socket +# # We probably should check for multiple ip's +# myip = socket.gethostbyname_ex(socket.gethostname())[-1][0] +# assert detector.last_client_ip == myip + +def test_get_hostname(detector): + for detector_host, config_host in zip(detector.hostname, config_test.known_hostnames): + assert detector_host == config_host + +def test_hostname_has_same_length_as_n_modules(detector): + assert len(detector.hostname) == detector.n_modules + + +# # def test_get_receiver_hostname(detector): +# # """Assume that the receiver are on the local computer""" +# # import socket +# # host = socket.gethostname().split('.')[0] +# # assert detector.rx_hostname == host + +# def test_set_receiver_hostname(detector): +# import socket +# host = socket.gethostname().split('.')[0] +# phony_host = 'madeup' +# detector.rx_hostname = phony_host +# assert detector.rx_hostname == phony_host +# detector.rx_hostname = host +# assert detector.rx_hostname == host + +@eigertest +def test_set_rx_zmqport_single_value(eiger): + eiger.rx_zmqport = 35000 + assert eiger.rx_zmqport == [35000, 35001, 35002, 35003] + +@eigertest +def test_set_rx_zmqport_list(eiger): + eiger.rx_zmqport = [37000, 38000] + assert eiger.rx_zmqport == [37000, 37001, 38000, 38001] + +@eigertest +def test_set_rx_updport(eiger): + ports = [60010,60011,60012,60013] + eiger.rx_udpport = ports + assert eiger.rx_udpport == ports + eiger.acq() + assert eiger.frames_caught == 1 + +@eigertest +def test_rx_tcpport(eiger): + ports = eiger.rx_tcpport + eiger.rx_tcpport = [2000,2001] + assert eiger.rx_tcpport == [2000,2001] + eiger.rx_tcpport = ports + assert eiger.rx_tcpport == ports + eiger.acq() + assert eiger.frames_caught == 1 + +@eigertest +@pytest.mark.new +def test_enable_disable_tengiga(eiger): + """ + This test does not check for dat on the 10Gbit link, only the set and get functions + """ + eiger.tengiga = True + assert eiger.tengiga == True + eiger.tengiga = False + assert eiger.tengiga == False + + + +#TODO! Add test for Jungfrau \ No newline at end of file diff --git a/python/simple-integration-tests/eiger/test_paths_and_files.py b/python/simple-integration-tests/eiger/test_paths_and_files.py new file mode 100644 index 000000000..7728c17ac --- /dev/null +++ b/python/simple-integration-tests/eiger/test_paths_and_files.py @@ -0,0 +1,54 @@ +import pytest +import config_test +from fixtures import detector, eiger, jungfrau, eigertest, jungfrautest +from sls_detector.errors import DetectorValueError + + + + +@eigertest +@pytest.mark.local +def test_set_path(eiger, tmpdir): + import os + path = os.path.join(tmpdir.dirname, tmpdir.basename) + eiger.file_path = path + assert eiger.file_path == path + +@eigertest +@pytest.mark.local +def test_set_path_and_write_files(eiger, tmpdir): + import os + prefix = 'testprefix' + path = os.path.join(tmpdir.dirname, tmpdir.basename) + eiger.file_path = path + eiger.file_write = True + eiger.exposure_time = 0.1 + eiger.n_frames = 1 + eiger.timing_mode = 'auto' + eiger.file_name = prefix + eiger.file_index = 0 + eiger.acq() + + files = [f.basename for f in tmpdir.listdir()] + + assert len(files) == 5 + assert (prefix+'_d0_0.raw' in files) == True + assert (prefix+'_d1_0.raw' in files) == True + assert (prefix+'_d2_0.raw' in files) == True + assert (prefix+'_d3_0.raw' in files) == True + +def test_set_discard_policy(detector): + detector.frame_discard_policy = 'nodiscard' + assert detector.frame_discard_policy == 'nodiscard' + detector.frame_discard_policy = 'discardpartial' + assert detector.frame_discard_policy == 'discardpartial' + detector.frame_discard_policy = 'discardempty' + assert detector.frame_discard_policy == 'discardempty' + +def test_set_discard_policy_raises(detector): + with pytest.raises(ValueError): + detector.frame_discard_policy = 'adjfvadksvsj' + +def test_set_frames_perfile(detector): + detector.frames_per_file = 5000 + assert detector.frames_per_file == 5000 \ No newline at end of file diff --git a/python/simple-integration-tests/eiger/test_threshold.py b/python/simple-integration-tests/eiger/test_threshold.py new file mode 100644 index 000000000..0eb3b3ed5 --- /dev/null +++ b/python/simple-integration-tests/eiger/test_threshold.py @@ -0,0 +1,47 @@ +import pytest +import config_test +import time +from sls_detector.errors import DetectorValueError +import os +from fixtures import eiger, eigertest + + +testdata_th = [0,333,500,1750,2000] + +@eigertest +@pytest.mark.parametrize("th", testdata_th) +def test_set_vthreshold(eiger, th): + eiger.vthreshold = th + assert eiger.vthreshold == th + +@eigertest +def test_vthreshold_with_different_vcmp(eiger): + #When vcmp is different for the chip vthreshold should return -1 + eiger.vthreshold = 1500 + eiger.dacs.vcmp_ll = 1400 + assert eiger.vthreshold == -1 + +@eigertest +def test_set_settingsdir(eiger): + path = os.path.dirname( os.path.realpath(__file__) ) + path = os.path.join(path, 'settingsdir') + eiger.settings_path = path + assert eiger.settings_path == path + +@eigertest +def test_set_trimmed_energies(eiger): + en = [5000,6000,7000] + eiger.trimmed_energies = en + assert eiger.trimmed_energies == en + + +#TODO! add checks for vcmp as well and improve naming +#TODO! remove dependency on beb number +testdata_en = [(5000, 500),(5500,750),(6000,1000),(6200,1100),(7000,1500)] +@eigertest +@pytest.mark.parametrize('val', testdata_en) +def test_set_energy_threshold(eiger, val): + eiger.settings = 'standard' + eiger.threshold = val[0] + assert eiger.threshold == val[0] + assert eiger.dacs.vrf[0] == val[1] \ No newline at end of file diff --git a/python/simple-integration-tests/eiger/test_time.py b/python/simple-integration-tests/eiger/test_time.py new file mode 100644 index 000000000..017eed3e4 --- /dev/null +++ b/python/simple-integration-tests/eiger/test_time.py @@ -0,0 +1,136 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +Tests regarding exposure time and period of the detector +Set and get test as well as test for duration and on detector +measurement of the time. +""" +import pytest +import config_test +from fixtures import detector, eiger, jungfrau, eigertest, jungfrautest +from sls_detector.errors import DetectorValueError, DetectorError +import time + + +testdata_times = [1e-8, 0.001, 0.5, 3.125, 5.0, 600, 784] +@pytest.mark.parametrize("t", testdata_times) +def test_set_and_get_exposure_time(eiger, t): + """ + Test that the exposure time we set in the detector + is the same as the one read back + """ + eiger.exposure_time = t + assert eiger.exposure_time == t + + +def test_negative_exposure_time_raises_error(eiger): + with pytest.raises(DetectorValueError): + eiger.exposure_time = -15 + + +testdata_times = [0.001, 0.0025, 0.005, 5] +@pytest.mark.parametrize("t", testdata_times) +def test_set_subexptime(eiger, t): + eiger.sub_exposure_time = t + assert eiger.sub_exposure_time == t + + +testdata_times = [-5,6,7,50] +@pytest.mark.parametrize("t", testdata_times) +def test_set_subextime_too_large_or_neg(eiger, t): + with pytest.raises((DetectorError, DetectorValueError)): + eiger.sub_exposure_time = t + + + +testdata_times = [0.2, 0.5, 1, 2, 5, 7] +@pytest.mark.slow +@pytest.mark.parametrize("t", testdata_times) +def test_measure_exposure_time_from_python(eiger, t): + """ + The main idea with this test is to make sure the overhead of a + single acq is less than tol[s]. This test also catches stupid bugs + that would for example not change the exposure time or make acquire + not blocking. + """ + tol = 0.5 + eiger.dynamic_range = 16 + eiger.file_write = False + eiger.n_frames = 1 + eiger.exposure_time = t + assert eiger.exposure_time == t + t0 = time.time() + eiger.acq() + duration = time.time()-t0 + assert duration < (t+tol) + + +testdata_times = [0.5, 1, 3, 5] + + +@pytest.mark.slow +@pytest.mark.parametrize("t", testdata_times) +def test_measure_period_from_python_and_detector(eiger, t): + tol = 0.5 + nframes = 5 + eiger.dynamic_range = 16 + eiger.file_write = False + eiger.n_frames = nframes + eiger.exposure_time = 0.001 + eiger.period = t + t0 = time.time() + eiger.acq() + duration = time.time()-t0 + assert duration < t*(nframes-1)+tol + for mp in eiger.measured_period: + assert pytest.approx(mp, 1e-5) == t + + +testdata_times = [0.001, 0.002, 0.003, 0.005, 0.01] +@pytest.mark.parametrize("t", testdata_times) +def test_measure_subperiod_nonparallel(eiger, t): + readout_time = 500e-6 + eiger.dynamic_range = 32 + eiger.file_write = False + eiger.flags = 'nonparallel' + eiger.n_frames = 1 + eiger.period = 0 + eiger.exposure_time = 0.5 + eiger.sub_exposure_time = t + eiger.sub_deadtime = 0 + eiger.acq() + for mp in eiger.measured_subperiod: + assert pytest.approx(mp, abs=1e-5) == t+readout_time + + +@pytest.mark.parametrize("t", testdata_times) +def test_measure_subperiod_parallel(eiger, t): + readout_time = 12e-6 + eiger.dynamic_range = 32 + eiger.file_write = False + eiger.flags = 'parallel' + eiger.n_frames = 1 + eiger.period = 0 + eiger.exposure_time = 0.5 + eiger.sub_exposure_time = t + eiger.sub_deadtime = 0 + eiger.acq() + for mp in eiger.measured_subperiod: + assert pytest.approx(mp, abs=1e-5) == t+readout_time + + +@pytest.mark.parametrize("t", testdata_times) +def test_measure_subperiod_parallel_when_changing_deadtime(eiger, t): + readout_time = 12e-6 + exposure_time = 0.001 + eiger.dynamic_range = 32 + eiger.file_write = False + eiger.flags = 'parallel' + eiger.n_frames = 1 + eiger.period = 0 + eiger.exposure_time = 0.5 + eiger.sub_exposure_time = exposure_time + eiger.sub_deadtime = t + eiger.acq() + for mp in eiger.measured_subperiod: + assert pytest.approx(mp, abs=1e-5) == t+exposure_time \ No newline at end of file diff --git a/python/simple-integration-tests/eiger/test_trimbits_and_dacs.py b/python/simple-integration-tests/eiger/test_trimbits_and_dacs.py new file mode 100644 index 000000000..3053ca75f --- /dev/null +++ b/python/simple-integration-tests/eiger/test_trimbits_and_dacs.py @@ -0,0 +1,34 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +Tests for trimbit and dac related functions +""" +import pytest +import config_test +from fixtures import detector, eiger, jungfrau, eigertest, jungfrautest +from sls_detector.errors import DetectorValueError + + +@eigertest +def test_set_trimbits(eiger): + """Limited values due to time""" + for i in [17, 32, 60]: + print(i) + eiger.trimbits = i + assert eiger.trimbits == i + +@eigertest +def test_set_trimbits_raises_on_too_big(eiger): + with pytest.raises(DetectorValueError): + eiger.trimbits = 75 + +@eigertest +def test_set_trimbits_raises_on_negative(eiger): + with pytest.raises(DetectorValueError): + eiger.trimbits = -5 + + +# @jungfrautest +# def test_jungfrau(jungfrau): +# """Example of a test that is not run with Eiger connected""" +# pass diff --git a/python/simple-integration-tests/eiger/test_version_numbers.py b/python/simple-integration-tests/eiger/test_version_numbers.py new file mode 100644 index 000000000..246712254 --- /dev/null +++ b/python/simple-integration-tests/eiger/test_version_numbers.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +Tests for hostname related functions of the detector +""" +import pytest +import config_test +from fixtures import detector, eiger, jungfrau, eigertest, jungfrautest +from sls_detector.errors import DetectorValueError + + + +def test_firmware_version(detector): + assert detector.firmware_version == config_test.fw_version + + diff --git a/python/simple-integration-tests/eiger/write_tb_files.py b/python/simple-integration-tests/eiger/write_tb_files.py new file mode 100644 index 000000000..b8ec9ba38 --- /dev/null +++ b/python/simple-integration-tests/eiger/write_tb_files.py @@ -0,0 +1,42 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +Created on Tue May 22 14:13:48 2018 + +@author: l_frojdh +""" +import os +from sls_detector_tools.io import write_trimbit_file +from sls_detector_tools import mask + +energy = [5000, 6000, 7000] +vrf = [500, 1000, 1500] + +for i,e in enumerate(energy): + dacs = np.array( [[ 0., 0.], #vsvp + [4000., 4000.], #vtr + [vrf[i], vrf[i]], #vrf + [1400., 1400.], #vrs + [4000., 4000.], #vsvn + [2556., 2556.], #vtgstv + [1400., 1400.], #vcmp_ll + [1500., 1500.], #vcmp_lr + [4000., 4000.], #vcall + [1500., 1500.], #vcmp_rl + [1100., 1100.], #rxb_rb + [1100., 1100.], #rxb_lb + [1500., 1500.], #vcmp_rr + [1500., 1500.], #vcp + [2000., 2000.], #vcn + [1550., 1550.], #vis + [ 660., 660.], #iodelay + [ 0., 0.], #tau + ]) + + tb = np.zeros((256,1024)) + + for beb in [83,98]: + write_trimbit_file(f'settingsdir/standard/{e}eV/noise.sn{beb:03d}', tb, dacs[:,0]) +#print(os.getcwd()) + +#print( os.path.realpath(__file__)) \ No newline at end of file diff --git a/python/simple-integration-tests/jungfrau_0_6/config_test.py b/python/simple-integration-tests/jungfrau_0_6/config_test.py new file mode 100644 index 000000000..045d04d8e --- /dev/null +++ b/python/simple-integration-tests/jungfrau_0_6/config_test.py @@ -0,0 +1,17 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +Created on Tue Nov 14 16:49:07 2017 + +@author: l_frojdh +""" + +fw_version = 0x180220 +detector_type = 'Jungfrau' +known_hostnames = ['bchip038'] +image_size = (512,1024) #rows, cols +module_geometry = (1,1) #horizontal, vertical + +#Remember to change these in the settings file as well! +settings_path = '/home/l_lopez/projects/slsDetectorPackage/settingsdir/jungfrau' +file_path = '/home/l_lopez/out' \ No newline at end of file diff --git a/python/simple-integration-tests/jungfrau_0_6/fixtures.py b/python/simple-integration-tests/jungfrau_0_6/fixtures.py new file mode 100644 index 000000000..d99c9e08f --- /dev/null +++ b/python/simple-integration-tests/jungfrau_0_6/fixtures.py @@ -0,0 +1,23 @@ +import pytest + +from sls_detector import Detector + +@pytest.fixture +def detector(): + from sls_detector import Detector + return Detector() + +@pytest.fixture +def eiger(): + from sls_detector import Eiger + return Eiger() + + +@pytest.fixture +def jungfrau(): + from sls_detector import Jungfrau + return Jungfrau() + +detector_type = Detector().detector_type +eigertest = pytest.mark.skipif(detector_type != 'Eiger', reason = 'Only valid for Eiger') +jungfrautest = pytest.mark.skipif(detector_type != 'Jungfrau', reason = 'Only valid for Jungfrau') \ No newline at end of file diff --git a/python/simple-integration-tests/jungfrau_0_6/test_main.py b/python/simple-integration-tests/jungfrau_0_6/test_main.py new file mode 100644 index 000000000..948e0b172 --- /dev/null +++ b/python/simple-integration-tests/jungfrau_0_6/test_main.py @@ -0,0 +1,17 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +General tests for the Jungfrau detector. + +NOTE! Uses hostnames from config_test +""" + +import pytest +import config_test +import tests + +import os +dir_path = os.path.dirname(os.path.realpath(__file__)) + +pytest.main(['-x', '-s', os.path.join(dir_path, 'tests/test_load_config.py')]) #Test 1 +pytest.main(['-x', '-s', os.path.join(dir_path, 'tests/test_overtemperature.py')]) #Test 2 diff --git a/python/simple-integration-tests/jungfrau_0_6/tests/test.config b/python/simple-integration-tests/jungfrau_0_6/tests/test.config new file mode 100644 index 000000000..8ac8d98d8 --- /dev/null +++ b/python/simple-integration-tests/jungfrau_0_6/tests/test.config @@ -0,0 +1,21 @@ +detsizechan 1024 512 + +settingsdir /home/l_lopez/projects/slsDetectorPackage/settingsdir/jungfrau +caldir /home/l_lopez/projects/slsDetectorPackage/settingsdir/jungfrau +lock 0 + +hostname bchip094+ + +rx_udpport 1754 +rx_udpip 10.1.1.107 +rx_udpmac 90:E2:BA:9A:4F:D4 +detectorip 10.1.1.9 +detectormac 00:aa:bb:cc:dd:ee +configuremac 0 + +powerchip 1 +timing auto + +outdir /home/l_lopez/out +threaded 1 +high diff --git a/python/simple-integration-tests/jungfrau_0_6/tests/test.par b/python/simple-integration-tests/jungfrau_0_6/tests/test.par new file mode 100644 index 000000000..b04533952 --- /dev/null +++ b/python/simple-integration-tests/jungfrau_0_6/tests/test.par @@ -0,0 +1 @@ +vhighvoltage 200 \ No newline at end of file diff --git a/python/simple-integration-tests/jungfrau_0_6/tests/test_load_config.py b/python/simple-integration-tests/jungfrau_0_6/tests/test_load_config.py new file mode 100644 index 000000000..de29b41b8 --- /dev/null +++ b/python/simple-integration-tests/jungfrau_0_6/tests/test_load_config.py @@ -0,0 +1,43 @@ + +import pytest +import config_test +import os +dir_path = os.path.dirname(os.path.realpath(__file__)) + +from fixtures import jungfrau, jungfrautest + + +def load_config_file_jungfrau_test(jungfrau): + """Load a settings file and assert all settings""" + + print('\tStarting load_config_file_jungfrau_test test case') + + jungfrau.free_shared_memory + jungfrau.load_config(os.path.join(dir_path, 'test.config')) + + assert jungfrau.lock == False + assert jungfrau.rx_udpport == ['1754'] + assert jungfrau.hostname == ['bchip094'] + assert jungfrau.firmware_version == config_test.fw_version + + print('\tFinished load_config_file_jungfrau_test test case') + +def load_parameters_file_jungfrau_test(jungfrau): + """Load a parametes file and assert the settings in the file""" + + print('\tStarting load_parameters_file_jungfrau_test test case') + + jungfrau.load_parameters(os.path.join(dir_path, 'test.par')) + assert jungfrau.high_voltage == 200 + + print('\tFinished load_parameters_file_jungfrau_test test case') + +@jungfrautest +def test_main(jungfrau): + print('\nTesting configuration file loading') + + load_config_file_jungfrau_test(jungfrau) + load_parameters_file_jungfrau_test(jungfrau) + + print('Tested configuration file loading') + diff --git a/python/simple-integration-tests/jungfrau_0_6/tests/test_overtemperature.py b/python/simple-integration-tests/jungfrau_0_6/tests/test_overtemperature.py new file mode 100644 index 000000000..0bcbbfc24 --- /dev/null +++ b/python/simple-integration-tests/jungfrau_0_6/tests/test_overtemperature.py @@ -0,0 +1,68 @@ + +import pytest +import config_test +import time +from fixtures import jungfrau, jungfrautest + +def powerchip_test(jungfrau, control): + """ + + Test the main overtemperature protection control + + """ + #Set test initial conditions + print('\tStarting powerchip_test test case') + + jungfrau.power_chip = False + jungfrau.temperature_control = control + assert jungfrau.power_chip == False + jungfrau.temperature_threshold = 35 + jungfrau.power_chip = True + + + if jungfrau.temperature_control is True: + if jungfrau.temperature_event is True: + assert jungfrau.power_chip == False + jungfrau.power_chip = True + assert jungfrau.power_chip == False + jungfrau.temperature_control = False + assert jungfrau.power_chip == True + jungfrau.temperature_control = True + jungfrau.temperature_threshold = 50 + assert jungfrau.power_chip == False + + print('\t\tWaiting to cool down the board. This may take a while...') + while jungfrau.temperature_threshold < jungfrau.temp.fpga[0]: + time.sleep(5) + print('\t\tJungfrau MCB temperature: {0:.2f} °C'.format(jungfrau.temp.fpga[0])) + + #Leave enough time to let the board cool down a bit more + time.sleep(30) + jungfrau.reset_temperature_event() + + assert jungfrau.temperature_event == False + assert jungfrau.power_chip == True + + else: + assert jungfrau.power_chip == True + else: + print('\t\tWaiting to warm up the board. This may take a while...') + while jungfrau.temperature_threshold > jungfrau.temp.fpga[0]: + time.sleep(5) + print('\t\tJungfrau MCB temperature: {0:.2f} °C'.format(jungfrau.temp.fpga[0])) + + assert jungfrau.temperature_event == False + assert jungfrau.power_chip == True + + print('\tFinished powerchip_test test case') + + +#@jungfrautest +def test_main(jungfrau): + + print('\nTesting overtemperature protection control') + + powerchip_test(jungfrau, False) + powerchip_test(jungfrau, True) + + print('Tested overtemperature protection control') diff --git a/python/sls_detector/__init__.py b/python/sls_detector/__init__.py new file mode 100644 index 000000000..bcd37efb0 --- /dev/null +++ b/python/sls_detector/__init__.py @@ -0,0 +1,5 @@ +from .detector import Detector, DetectorError, free_shared_memory +from .eiger import Eiger +from .jungfrau import Jungfrau +from .jungfrau_ctb import JungfrauCTB +from _sls_detector import DetectorApi diff --git a/python/sls_detector/__pycache__/__init__.cpython-37.pyc b/python/sls_detector/__pycache__/__init__.cpython-37.pyc new file mode 100644 index 000000000..296a4db29 Binary files /dev/null and b/python/sls_detector/__pycache__/__init__.cpython-37.pyc differ diff --git a/python/sls_detector/__pycache__/adcs.cpython-37.pyc b/python/sls_detector/__pycache__/adcs.cpython-37.pyc new file mode 100644 index 000000000..88e6212dd Binary files /dev/null and b/python/sls_detector/__pycache__/adcs.cpython-37.pyc differ diff --git a/python/sls_detector/__pycache__/dacs.cpython-37.pyc b/python/sls_detector/__pycache__/dacs.cpython-37.pyc new file mode 100644 index 000000000..dcfcd9af5 Binary files /dev/null and b/python/sls_detector/__pycache__/dacs.cpython-37.pyc differ diff --git a/python/sls_detector/__pycache__/decorators.cpython-37.pyc b/python/sls_detector/__pycache__/decorators.cpython-37.pyc new file mode 100644 index 000000000..36a15f29d Binary files /dev/null and b/python/sls_detector/__pycache__/decorators.cpython-37.pyc differ diff --git a/python/sls_detector/__pycache__/detector.cpython-37.pyc b/python/sls_detector/__pycache__/detector.cpython-37.pyc new file mode 100644 index 000000000..ec20c2e95 Binary files /dev/null and b/python/sls_detector/__pycache__/detector.cpython-37.pyc differ diff --git a/python/sls_detector/__pycache__/detector_property.cpython-37.pyc b/python/sls_detector/__pycache__/detector_property.cpython-37.pyc new file mode 100644 index 000000000..fd03b52cd Binary files /dev/null and b/python/sls_detector/__pycache__/detector_property.cpython-37.pyc differ diff --git a/python/sls_detector/__pycache__/eiger.cpython-37.pyc b/python/sls_detector/__pycache__/eiger.cpython-37.pyc new file mode 100644 index 000000000..ec868bcd8 Binary files /dev/null and b/python/sls_detector/__pycache__/eiger.cpython-37.pyc differ diff --git a/python/sls_detector/__pycache__/errors.cpython-37.pyc b/python/sls_detector/__pycache__/errors.cpython-37.pyc new file mode 100644 index 000000000..984ff4fdb Binary files /dev/null and b/python/sls_detector/__pycache__/errors.cpython-37.pyc differ diff --git a/python/sls_detector/__pycache__/jungfrau.cpython-37.pyc b/python/sls_detector/__pycache__/jungfrau.cpython-37.pyc new file mode 100644 index 000000000..0549d1379 Binary files /dev/null and b/python/sls_detector/__pycache__/jungfrau.cpython-37.pyc differ diff --git a/python/sls_detector/__pycache__/jungfrau_ctb.cpython-37.pyc b/python/sls_detector/__pycache__/jungfrau_ctb.cpython-37.pyc new file mode 100644 index 000000000..2f25b63cb Binary files /dev/null and b/python/sls_detector/__pycache__/jungfrau_ctb.cpython-37.pyc differ diff --git a/python/sls_detector/__pycache__/registers.cpython-37.pyc b/python/sls_detector/__pycache__/registers.cpython-37.pyc new file mode 100644 index 000000000..478aa9fb1 Binary files /dev/null and b/python/sls_detector/__pycache__/registers.cpython-37.pyc differ diff --git a/python/sls_detector/__pycache__/utils.cpython-37.pyc b/python/sls_detector/__pycache__/utils.cpython-37.pyc new file mode 100644 index 000000000..1b30cd7d9 Binary files /dev/null and b/python/sls_detector/__pycache__/utils.cpython-37.pyc differ diff --git a/python/sls_detector/adcs.py b/python/sls_detector/adcs.py new file mode 100644 index 000000000..f8df51b37 --- /dev/null +++ b/python/sls_detector/adcs.py @@ -0,0 +1,40 @@ +from functools import partial +class Adc: + def __init__(self, name, detector): + self.name = name + self._detector = detector + self.get_nmod = self._detector._api.getNumberOfDetectors + # Bind functions to get and set the dac + self.get = partial(self._detector._api.getAdc, self.name) + + + def __getitem__(self, key): + """ + Get dacs either by slice, key or list + """ + if key == slice(None, None, None): + return [self.get(i) / 1000 for i in range(self.get_nmod())] + elif isinstance(key, Iterable): + return [self.get(k) / 1000 for k in key] + else: + return self.get(key) / 1000 + + def __repr__(self): + """String representation for a single adc in all modules""" + degree_sign = u'\N{DEGREE SIGN}' + r_str = ['{:14s}: '.format(self.name)] + r_str += ['{:6.2f}{:s}C, '.format(self.get(i)/1000, degree_sign) for i in range(self.get_nmod())] + return ''.join(r_str).strip(', ') + + + +class DetectorAdcs: + """ + Interface to the ADCs on the readout board + """ + def __iter__(self): + for attr, value in self.__dict__.items(): + yield value + + def __repr__(self): + return '\n'.join([str(t) for t in self]) \ No newline at end of file diff --git a/python/sls_detector/dacs.py b/python/sls_detector/dacs.py new file mode 100644 index 000000000..50ea8a1a7 --- /dev/null +++ b/python/sls_detector/dacs.py @@ -0,0 +1,125 @@ +from .detector_property import DetectorProperty +from functools import partial +import numpy as np + +class Dac(DetectorProperty): + """ + This class represents a dac on the detector. One instance handles all + dacs with the same name for a multi detector instance. + + .. note :: + + This class is used to build up DetectorDacs and is in general + not directly accessed to the user. + + + """ + def __init__(self, name, low, high, default, detector): + + super().__init__(partial(detector._api.getDac, name), + partial(detector._api.setDac, name), + detector._api.getNumberOfDetectors, + name) + + self.min_value = low + self.max_value = high + self.default = default + + + + def __repr__(self): + """String representation for a single dac in all modules""" + r_str = ['{:10s}: '.format(self.__name__)] + r_str += ['{:5d}, '.format(self.get(i)) for i in range(self.get_nmod())] + return ''.join(r_str).strip(', ') + + +class DetectorDacs: + _dacs = [('vsvp', 0, 4000, 0), + ('vtr', 0, 4000, 2500), + ('vrf', 0, 4000, 3300), + ('vrs', 0, 4000, 1400), + ('vsvn', 0, 4000, 4000), + ('vtgstv', 0, 4000, 2556), + ('vcmp_ll', 0, 4000, 1500), + ('vcmp_lr', 0, 4000, 1500), + ('vcall', 0, 4000, 4000), + ('vcmp_rl', 0, 4000, 1500), + ('rxb_rb', 0, 4000, 1100), + ('rxb_lb', 0, 4000, 1100), + ('vcmp_rr', 0, 4000, 1500), + ('vcp', 0, 4000, 200), + ('vcn', 0, 4000, 2000), + ('vis', 0, 4000, 1550), + ('iodelay', 0, 4000, 660)] + _dacnames = [_d[0] for _d in _dacs] + + def __init__(self, detector): + # We need to at least initially know which detector we are connected to + self._detector = detector + + # Index to support iteration + self._current = 0 + + # Populate the dacs + for _d in self._dacs: + setattr(self, '_'+_d[0], Dac(*_d, detector)) + + def __getattr__(self, name): + return self.__getattribute__('_' + name) + + + def __setattr__(self, name, value): + if name in self._dacnames: + return self.__getattribute__('_' + name).__setitem__(slice(None, None, None), value) + else: + super().__setattr__(name, value) + + def __next__(self): + if self._current >= len(self._dacs): + self._current = 0 + raise StopIteration + else: + self._current += 1 + return self.__getattr__(self._dacnames[self._current-1]) + + def __iter__(self): + return self + + def __repr__(self): + r_str = ['========== DACS ========='] + r_str += [repr(dac) for dac in self] + return '\n'.join(r_str) + + def get_asarray(self): + """ + Read the dacs into a numpy array with dimensions [ndacs, nmodules] + """ + dac_array = np.zeros((len(self._dacs), self._detector.n_modules)) + for i, _d in enumerate(self): + dac_array[i,:] = _d[:] + return dac_array + + def set_from_array(self, dac_array): + """ + Set the dacs from an numpy array with dac values. [ndacs, nmodules] + """ + dac_array = dac_array.astype(np.int) + for i, _d in enumerate(self): + _d[:] = dac_array[i] + + def set_default(self): + """ + Set all dacs to their default values + """ + for _d in self: + _d[:] = _d.default + + def update_nmod(self): + """ + Update the cached value of nmod, needs to be run after adding or + removing detectors + """ + for _d in self: + _d._n_modules = self._detector.n_modules + diff --git a/python/sls_detector/decorators.py b/python/sls_detector/decorators.py new file mode 100644 index 000000000..d1ec4482a --- /dev/null +++ b/python/sls_detector/decorators.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +Function decorators for the sls_detector. +""" +from .errors import DetectorError +import functools + + +def error_handling(func): + """ + Check for errors registered by the slsDetectorSoftware + """ + @functools.wraps(func) + def wrapper(self, *args, **kwargs): + + # remove any previous errors + self._api.clearErrorMask() + + # call function + result = func(self, *args, **kwargs) + + # check for new errors + m = self.error_mask + if m != 0: + msg = self.error_message + self._api.clearErrorMask() + raise DetectorError(msg) + return result + + return wrapper + + +def property_error_handling(func): + """ + Check for errors registered by the slsDetectorSoftware + """ + + @functools.wraps(func) + def wrapper(self, *args, **kwargs): + # remove any previous errors + self._detector._api.clearErrorMask() + + # call function + result = func(self, *args, **kwargs) + + # check for new errors + m = self._detector.error_mask + if m != 0: + msg = self._detector.error_message + self._detector._api.clearErrorMask() + raise DetectorError(msg) + return result + + return wrapper \ No newline at end of file diff --git a/python/sls_detector/detector.py b/python/sls_detector/detector.py new file mode 100644 index 000000000..51154f779 --- /dev/null +++ b/python/sls_detector/detector.py @@ -0,0 +1,1450 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +Python - sls +============= + +""" +import os +from collections.abc import Iterable +from collections import namedtuple + +from _sls_detector import DetectorApi +from .decorators import error_handling +from .detector_property import DetectorProperty +from .errors import DetectorError, DetectorValueError +from .registers import Register +from .utils import element_if_equal + + +class Detector: + """ + Base class used as interface with the slsDetectorSoftware. To control a specific detector use the + derived classes such as Eiger and Jungfrau. Functions as an interface to the C++ API and provides a + more Pythonic interface + """ + + _speed_names = {0: 'Full Speed', 1: 'Half Speed', 2: 'Quarter Speed', 3: 'Super Slow Speed'} + _speed_int = {'Full Speed': 0, 'Half Speed': 1, 'Quarter Speed': 2, 'Super Slow Speed': 3} + _settings = [] + + def __init__(self, multi_id=0): + self._api = DetectorApi(multi_id) + self._register = Register(self) + + self._flippeddatax = DetectorProperty(self._api.getFlippedDataX, + self._api.setFlippedDataX, + self._api.getNumberOfDetectors, + 'flippeddatax') + self._flippeddatay = DetectorProperty(self._api.getFlippedDataY, + self._api.setFlippedDataY, + self._api.getNumberOfDetectors, + 'flippeddatay') + try: + self.online = True + self.receiver_online = True + except DetectorError: + print('WARNING: Cannot connect to detector') + + + def __len__(self): + return self._api.getNumberOfDetectors() + + def __repr__(self): + return '{}(id = {})'.format(self.__class__.__name__, + self._api.getMultiDetectorId()) + + + def acq(self): + """ + Blocking command to launch the programmed measurement. Number of frames specified by frames, cycles etc. + """ + self._api.acq() + + + @property + @error_handling + def busy(self): + """ + Checks the detector is acquiring. Can also be set but should only be used if the acquire fails and + leaves the detector with busy == True + + .. note :: + + Only works when the measurement is launched using acquire, not with status start! + + Returns + -------- + bool + :py:obj:`True` if the detector is acquiring otherwise :py:obj:`False` + + Examples + ---------- + + :: + + d.busy + >> True + + #If the detector is stuck reset by: + d.busy = False + + + """ + return self._api.getAcquiringFlag() + + @busy.setter + @error_handling + def busy(self, value): + self._api.setAcquiringFlag(value) + + def clear_errors(self): + """Clear the error mask for the detector. Used to reset after checking.""" + self._api.clearErrorMask() + + @property + @error_handling + def client_version(self): + """ + :py:obj:`str` The date of commit for the client API version + + Examples + ---------- + + :: + + d.client_version + >> '20180327' + + """ + v = hex(self._api.getClientVersion()) + return v[2:] + + @property + @error_handling + def detector_number(self): + """ + Get all detector numbers as a list. For Eiger the detector numbers + correspond to the beb numbers. + + Examples + --------- + + :: + + #for beb083 and beb098 + detector.detector_number + >> [83, 98] + + """ + return [self._api.getDetectorNumber(i) for i in range(self.n_modules)] + + @property + def detector_type(self): + """ + Return either a string or list of strings with the detector type. + + * Eiger + * Jungfrau + * etc. + + Examples + ---------- + + :: + + detector.detector_type + >> 'Eiger' + + detector.detector_type + >> ['Eiger', 'Jungfrau'] + + """ + return element_if_equal(self._api.getDetectorType()) + + @property + @error_handling + def dynamic_range(self): + """ + :obj:`int`: Dynamic range of the detector. + + +----+-------------+------------------------------+ + | dr | max counts | comments | + +====+=============+==============================+ + | 4 | 15 | | + +----+-------------+------------------------------+ + | 8 | 255 | | + +----+-------------+------------------------------+ + |16 | 4095 | 12 bit internally | + +----+-------------+------------------------------+ + |32 | 4294967295 | Autosumming of 12 bit frames | + +----+-------------+------------------------------+ + + Raises + ------- + ValueError + If the dynamic range is not available in the detector + + + """ + return self._api.getDynamicRange() + + @dynamic_range.setter + @error_handling + def dynamic_range(self, dr): + if dr in self._detector_dynamic_range: + self._api.setDynamicRange(dr) + return + else: + raise DetectorValueError('Cannot set dynamic range to: {:d} availble options: '.format(dr), + self._detector_dynamic_range) + + @property + def error_mask(self): + """Read the error mask from the slsDetectorSoftware""" + return self._api.getErrorMask() + + @property + def error_message(self): + """Read the error message from the slsDetectorSoftware""" + return self._api.getErrorMessage() + + @property + @error_handling + def exposure_time(self): + """ + :obj:`double` Exposure time in [s] of a single frame. + """ + return self._api.getExposureTime() / 1e9 + + @exposure_time.setter + @error_handling + def exposure_time(self, t): + ns_time = int(t * 1e9) + if ns_time <= 0: + raise DetectorValueError('Exposure time must be larger than 0') + self._api.setExposureTime(ns_time) + + @property + @error_handling + def file_index(self): + """ + :obj:`int` Index for frames and file names + + Raises + ------- + ValueError + If the user tries to set an index less than zero + + Examples + --------- + + :: + + detector.file_index + >> 0 + + detector.file_index = 10 + detector.file_index + >> 10 + + """ + return self._api.getFileIndex() + + @file_index.setter + @error_handling + def file_index(self, i): + if i < 0: + raise ValueError('Index needs to be positive') + self._api.setFileIndex(i) + + @property + @error_handling + def file_name(self): + """ + :obj:`str`: Base file name for writing images + + Examples + --------- + + :: + + detector.file_name + >> 'run' + + detector.file_name = 'myrun' + + #For a single acquisition the detector now writes + # myrun_master_0.raw + # myrun_d0_0.raw + # myrun_d1_0.raw + # myrun_d2_0.raw + # myrun_d3_0.raw + + """ + return self._api.getFileName() + + @file_name.setter + @error_handling + def file_name(self, fname): + self._api.setFileName(fname) + + @property + @error_handling + def file_path(self): + """ + :obj:`str`: Path where images are written + + Raises + ------- + FileNotFoundError + If path does not exists + + Examples + --------- + + :: + + detector.file_path + >> '/path/to/files' + + detector.file_path = '/new/path/to/other/files' + + """ + fp = self._api.getFilePath() + if fp == '': + return [self._api.getFilePath(i) for i in range(len(self))] + else: + return fp + + @file_path.setter + @error_handling + def file_path(self, path): + if os.path.exists(path) is True: + self._api.setFilePath(path) + else: + raise FileNotFoundError('File path does not exists') + + @property + @error_handling + def file_write(self): + """ + :obj:`bool` If True write files to disk + """ + return self._api.getFileWrite() + + @file_write.setter + @error_handling + def file_write(self, fwrite): + self._api.setFileWrite(fwrite) + + + @property + @error_handling + def file_overwrite(self): + """ + :obj:`bool` If true overwrite files on disk + """ + return self._api.getFileOverWrite() + + @file_overwrite.setter + @error_handling + def file_overwrite(self, value): + self._api.setFileOverWrite(value) + + @property + @error_handling + def file_padding(self): + """ + Pad files in the receiver + :obj:`bool` If true pads partial frames + """ + return self._api.getReceiverPartialFramesPadding() + + @file_padding.setter + @error_handling + def file_padding(self, value): + self._api.getReceiverPartialFramesPadding(value) + + @property + @error_handling + def firmware_version(self): + """ + :py:obj:`int` Firmware version of the detector + """ + return self._api.getFirmwareVersion() + + @property + def flags(self): + """Read and set flags. Accepts both single flag as + string or list of flags. + + Raises + -------- + RuntimeError + If flag not recognized + + + Examples + ---------- + + :: + + #Eiger + detector.flags + >> ['storeinram', 'parallel'] + + detector.flags = 'nonparallel' + detector.flags + >> ['storeinram', 'nonparallel'] + + detector.flags = ['continous', 'parallel'] + + + """ + return self._api.getReadoutFlags() + + @flags.setter + def flags(self, flags): + if isinstance(flags, str): + self._api.setReadoutFlag(flags) + elif isinstance(flags, Iterable): + for f in flags: + self._api.setReadoutFlag(f) + + @property + def frames_caught(self): + """ + Number of frames caught by the receiver. Can be used to check for + package loss. + """ + return self._api.getFramesCaughtByReceiver() + + + @property + @error_handling + def frame_discard_policy(self): + """ + Decides what the receiver does when packet loss occurs. + nodiscard - keep all frames + discardempty - discard only empty frames + discardpartial - discard partial and empty frames + """ + return self._api.getReceiverFrameDiscardPolicy() + + @frame_discard_policy.setter + @error_handling + def frame_discard_policy(self, policy): + self._api.setReceiverFramesDiscardPolicy(policy) + + + @property + def api_compatibility(self): + Compatibility = namedtuple('Compatibility', ['client_detector', 'client_receiver']) + c = Compatibility(self._api.isClientAndDetecorCompatible(), self._api.isClientAndReceiverCompatible()) + return c + + @property + def frame_padding(self): + """ + Padd partial frames in the receiver + """ + return self._api.getReceiverPartialFramesPadding() + + @frame_padding.setter + def frame_padding(self, padding): + self._api.setReceiverPartialFramesPadding(padding) + + def free_shared_memory(self): + """ + Free the shared memory that contains the detector settings + and reinitialized with 0 detectors so that you can keep + using the same object. + + """ + self._api.freeSharedMemory() + self.__init__(self._api.getMultiDetectorId()) + + @property + def flipped_data_x(self): + """Flips data on x axis. Set for eiger bottom modules""" + return self._flippeddatax + + @property + def flipped_data_y(self): + """Flips data on y axis.""" + return self._flippeddatax + + @property + @error_handling + def high_voltage(self): + """ + High voltage applied to the sensor + """ + return self._api.getDac('vhighvoltage', -1) + + @high_voltage.setter + @error_handling + def high_voltage(self, voltage): + voltage = int(voltage) + if voltage < 0 or voltage > 200: + raise DetectorValueError('High voltage {:d}V is out of range. Should be between 0-200V'.format(voltage)) + self._api.setDac('vhighvoltage', -1, voltage) + + + @property + @error_handling + def hostname(self): + """ + :obj:`list` of :obj:`str`: hostnames of all connected detectors + + Examples + --------- + + :: + + detector.hostname + >> ['beb059', 'beb058'] + + """ + _hm = self._api.getHostname() + if _hm == '': + return [] + return _hm.strip('+').split('+') + + + @hostname.setter + @error_handling + def hostname(self, hn): + if isinstance(hn, str): + self._api.setHostname(hn) + else: + name = ''.join([''.join((h, '+')) for h in hn]) + self._api.setHostname(name) + + @property + def image_size(self): + """ + :py:obj:`collections.namedtuple` with the image size of the detector + Also works setting using a normal tuple + + .. note :: + + Follows the normal convention in Python of (rows, cols) + + Examples + ---------- + + :: + + d.image_size = (512, 1024) + + d.image_size + >> ImageSize(rows=512, cols=1024) + + d.image_size.rows + >> 512 + + d.image_size.cols + >> 1024 + + """ + size = namedtuple('ImageSize', ['rows', 'cols']) + return size(*self._api.getImageSize()) + + @image_size.setter + @error_handling + def image_size(self, size): + self._api.setImageSize(*size) + + @error_handling + def load_config(self, fname): + """ + Load detector configuration from a configuration file + + Raises + -------- + FileNotFoundError + If the file does not exists + + """ + if os.path.isfile(fname): + self._api.readConfigurationFile(fname) + else: + raise FileNotFoundError('Cannot find configuration file') + + @error_handling + def load_parameters(self, fname): + """ + Setup detector by executing commands in a parameters file + + + .. note :: + + If you are relying mainly on the Python API it is probably + better to track the settings from Python. This function uses + parameters stored in a text file and the command line commands. + + Raises + -------- + FileNotFoundError + If the file does not exists + + """ + if os.path.isfile(fname): + self._api.readParametersFile(fname) + else: + raise FileNotFoundError('Cannot find parameters file') + + @error_handling + def load_trimbits(self, fname, idet=-1): + """ + Load trimbit file or files. Either called with detector number or -1 + to try to load detector specific trimbit files + + Parameters + ----------- + fname: + :py:obj:`str` Filename (including path) to the trimbit files + + idet + :py:obj:`int` Detector to load trimbits to, -1 for all + + + :: + + #Assuming 500k consisting of beb049 and beb048 + # 0 is beb049 + # 1 is beb048 + + #Load name.sn049 to beb049 and name.sn048 to beb048 + detector.load_trimbits('/path/to/dir/name') + + #Load one file to a specific detector + detector.load_trimbits('/path/to/dir/name.sn049', 0) + + """ + self._api.loadTrimbitFile(fname, idet) + + + @property + @error_handling + def lock(self): + """Lock the detector to this client + + :: + + detector.lock = True + + """ + return self._api.getServerLock() + + @lock.setter + def lock(self, value): + self._api.setServerLock(value) + + @property + @error_handling + def lock_receiver(self): + """Lock the receivers to this client + + :: + + detector.lock_receiver = True + + """ + + return self._api.getReceiverLock() + + @lock_receiver.setter + def lock_receiver(self, value): + self._api.setReceiverLock(value) + + @property + @error_handling + def module_geometry(self): + """ + :obj:`namedtuple` Geometry(horizontal=nx, vertical=ny) + of the detector modules. + + Examples + --------- + + :: + + detector.module_geometry + >> Geometry(horizontal=1, vertical=2) + + detector.module_geometry.vertical + >> 2 + + detector.module_geometry[0] + >> 1 + + """ + _t = self._api.getDetectorGeometry() + Geometry = namedtuple('Geometry', ['horizontal', 'vertical']) + return Geometry(horizontal=_t[0], vertical=_t[1]) + + @property + @error_handling + def n_frames(self): + """ + :obj:`int` Number of frames per acquisition + """ + return self._api.getNumberOfFrames() + + @n_frames.setter + @error_handling + def n_frames(self, n): + if n >= 1: + self._api.setNumberOfFrames(n) + else: + raise DetectorValueError('Invalid value for n_frames: {:d}. Number of'\ + ' frames should be an integer greater than 0'.format(n)) + + @property + @error_handling + def frames_per_file(self): + return self._api.getReceiverFramesPerFile() + + @frames_per_file.setter + @error_handling + def frames_per_file(self, n): + self._api.setReceiverFramesPerFile(n) + + @property + @error_handling + def n_cycles(self): + """Number of cycles for the measurement (exp*n_frames)*n_cycles""" + return self._api.getCycles() + + @n_cycles.setter + @error_handling + def n_cycles(self, n_cycles): + if n_cycles > 0: + self._api.setCycles(n_cycles) + else: + raise DetectorValueError('Number of cycles must be positive') + + @property + @error_handling + def n_measurements(self): + """ + Number of times to repeat the programmed measurement. + This is the outer most part. Real time operation is not + guaranteed since this is software controlled. + + Examples + ---------- + + :: + + detector.n_frames = 1 + detector.n_cycles = 1 + detector.n_measurements = 3 + + detector.acq() # 1 frame 3 times + + detector.n_frames = 5 + detector.n_cycles = 3 + detector.n_measurements = 2 + + detector.acq() # 5x3 frames 2 times total 30 frames + + """ + return self._api.getNumberOfMeasurements() + + @n_measurements.setter + @error_handling + def n_measurements(self, value): + if value > 0: + self._api.setNumberOfMeasurements(value) + else: + raise DetectorValueError('Number of measurements must be positive') + + @property + @error_handling + def n_modules(self): + """ + :obj:`int` Number of (half)modules in the detector + + Examples + --------- + + :: + + detector.n_modules + >> 2 + + """ + return self._api.getNumberOfDetectors() + + @property + @error_handling + def online(self): + """Online flag for the detector + + Examples + ---------- + + :: + + d.online + >> False + + d.online = True + + """ + return self._api.getOnline() + + @online.setter + @error_handling + def online(self, value): + self._api.setOnline(value) + + + @property + @error_handling + def last_client_ip(self): + """Returns the ip address of the last client + that accessed the detector + + Returns + ------- + + :obj:`str` last client ip + + Examples + ---------- + + :: + + detector.last_client_ip + >> '129.129.202.117' + + """ + return self._api.getLastClientIP() + + @property + def receiver_last_client_ip(self): + """Returns the ip of the client last talking to the receiver""" + return self._api.getReceiverLastClientIP() + + @property + @error_handling + def receiver_online(self): + """ + Online flag for the receiver. Is set together with detector.online when creating the detector object + + Examples + --------- + + :: + + d.receiver_online + >> True + + d.receiver_online = False + + """ + return self._api.getReceiverOnline() + + @receiver_online.setter + @error_handling + def receiver_online(self, value): + self._api.setReceiverOnline(value) + + @property + @error_handling + def receiver_version(self): + """ + :py:obj:`str` Receiver version as a string. [yearmonthday] + + Examples + ---------- + + :: + + d.receiver_version + >> '20180327' + + """ + v = hex(self._api.getReceiverVersion()) + return v[2:] + + #When returning instance error hadling needs to be done in the + #class that is returned + @property + def register(self): + """Directly manipulate registers on the readout board + + Examples + --------- + + :: + + d.register[0x5d] = 0xf00 + + """ + return self._register + + def reset_frames_caught(self): + """ + Reset the number of frames caught by the receiver. + + .. note :: + + Automatically done when using d.acq() + + """ + self._api.resetFramesCaught() + + @property + @error_handling + def period(self): + """ + :obj:`double` Period between start of frames. Set to 0 for the detector + to choose the shortest possible + """ + _t = self._api.getPeriod() + return _t / 1e9 + + @period.setter + @error_handling + def period(self, t): + ns_time = int(t * 1e9) + if ns_time < 0: + raise ValueError('Period must be 0 or larger') + self._api.setPeriod(ns_time) + + @property + @error_handling + def rate_correction(self): + """ + :obj:`list` of :obj:`double` Rate correction for all modules. + Set to 0 for **disabled** + + .. todo :: + + Should support individual assignments + + Raises + ------- + ValueError + If the passed list is not of the same length as the number of + detectors + + Examples + --------- + + :: + + detector.rate_correction + >> [125.0, 155.0] + + detector.rate_correction = [125, 155] + + + """ + return self._api.getRateCorrection() + + @rate_correction.setter + @error_handling + def rate_correction(self, tau_list): + if len(tau_list) != self.n_modules: + raise ValueError('List of tau needs the same length') + self._api.setRateCorrection(tau_list) + + + @property + @error_handling + def readout_clock(self): + """ + Speed of the readout clock relative to the full speed + + * Full Speed + * Half Speed + * Quarter Speed + * Super Slow Speed + + Examples + --------- + + :: + + d.readout_clock + >> 'Half Speed' + + d.readout_clock = 'Full Speed' + + + """ + speed = self._api.getReadoutClockSpeed() + return self._speed_names[speed] + + @readout_clock.setter + @error_handling + def readout_clock(self, value): + speed = self._speed_int[value] + self._api.setReadoutClockSpeed(speed) + + @property + def receiver_frame_index(self): + return self._api.getReceiverCurrentFrameIndex() + + @property + @error_handling + def rx_datastream(self): + """ + Zmq datastream from receiver. :py:obj:`True` if enabled and :py:obj:`False` + otherwise + + :: + + #Enable data streaming from receiver + detector.rx_datastream = True + + #Check data streaming + detector.rx_datastream + >> True + + """ + return self._api.getRxDataStreamStatus() + + @rx_datastream.setter + def rx_datastream(self, status): + self._api.setRxDataStreamStatus(status) + + + @property + @error_handling + def rx_hostname(self): + """ + Receiver hostname + """ + s = self._api.getNetworkParameter('rx_hostname') + return element_if_equal(s) + + @rx_hostname.setter + @error_handling + def rx_hostname(self, names): + # if we pass a list join the list + if isinstance(names, list): + names = '+'.join(n for n in names)+'+' + + self._api.setNetworkParameter('rx_hostname', names, -1) + + + @property + @error_handling + def rx_udpip(self): + """ + Receiver UDP ip + """ + s = self._api.getNetworkParameter('rx_udpip') + return element_if_equal(s) + + @rx_udpip.setter + def rx_udpip(self, ip): + if isinstance(ip, list): + for i, addr in enumerate(ip): + self._api.setNetworkParameter('rx_udpip', addr, i) + else: + self._api.setNetworkParameter('rx_udpip', ip, -1) + + + @property + def rx_udpmac(self): + return element_if_equal(self._api.getNetworkParameter('rx_udpmac')) + + @rx_udpmac.setter + def rx_udpmac(self, mac): + if isinstance(mac, list): + for i, m in enumerate(mac): + self._api.setNetworkParameter('rx_udpmac', m, i) + else: + self._api.setNetworkParameter('rx_udpmac', mac, -1) + + @property + @error_handling + def rx_tcpport(self): + return [self._api.getRxTcpport(i) for i in range(self.n_modules)] + + @rx_tcpport.setter + @error_handling + def rx_tcpport(self, ports): + if len(ports) != len(self): + raise ValueError('Number of ports: {} not equal to number of ' + 'detectors: {}'.format(len(ports), len(self))) + else: + for i, p in enumerate(ports): + self._api.setRxTcpport(i, p) + + @property + @error_handling + def rx_zmqip(self): + """ + ip where the receiver streams data + """ + ip = self._api.getNetworkParameter('rx_zmqip') + return element_if_equal(ip) + + @rx_zmqip.setter + def rx_zmqip(self, ip): + self._api.setNetworkParameter('rx_zmqip', ip, -1) + + + @property + @error_handling + def detector_mac(self): + """ + Read detector mac address + """ + mac = self._api.getNetworkParameter('detectormac') + return element_if_equal(mac) + + @property + @error_handling + def detector_ip(self): + """ + Read detector ip address + """ + ip = self._api.getNetworkParameter('detectorip') + return element_if_equal(ip) + + @property + @error_handling + def client_zmqip(self): + """ + Ip address where the client listens to zmq stream + """ + ip = self._api.getNetworkParameter('client_zmqip') + return element_if_equal(ip) + + @client_zmqip.setter + @error_handling + def client_zmqip(self, ip): + self._api.setNetworkParameter('client_zmqip', ip, -1) + + + + @property + @error_handling + def rx_fifodepth(self): + """ + Fifo depth of receiver in number of frames + """ + return self._api.getReceiverFifoDepth() + + @rx_fifodepth.setter + @error_handling + def rx_fifodepth(self, n_frames): + self._api.setReceiverFifoDepth(n_frames) + + + @property + @error_handling + def rx_udpsocksize(self): + """ + UDP buffer size + """ + buffer_size = [int(s) for s in self._api.getNetworkParameter('rx_udpsocksize')] + return element_if_equal(buffer_size) + + @property + @error_handling + def rx_jsonaddheader(self): + """ + UDP buffer size + """ + header = self._api.getNetworkParameter('rx_jsonaddheader') + return element_if_equal(header) + + @rx_jsonaddheader.setter + @error_handling + def rx_jsonaddheader(self, header): + self._api.setNetworkParameter('rx_jsonaddheader', header, -1) + + + + @rx_udpsocksize.setter + @error_handling + def rx_udpsocksize(self, buffer_size): + self._api.setNetworkParameter('rx_udpsocksize', str(buffer_size), -1) + + + @property + @error_handling + def rx_realudpsocksize(self): + """ + UDP buffer size + """ + buffer_size = [int(s) for s in self._api.getNetworkParameter('rx_realudpsocksize')] + return element_if_equal(buffer_size) + + + @property + @error_handling + def rx_zmqport(self): + """ + Return the receiver zmq ports. + + :: + + detector.rx_zmqport + >> [30001, 30002] + + """ + _s = self._api.getNetworkParameter('rx_zmqport') + if _s == '': + return [] + else: + return [int(_p) for _p in _s] + + @rx_zmqport.setter + @error_handling + def rx_zmqport(self, port): + if isinstance(port, Iterable): + for i, p in enumerate(port): + self._api.setNetworkParameter('rx_zmqport', str(p), i) + else: + self._api.setNetworkParameter('rx_zmqport', str(port), -1) + +# Add back when versioning is defined +# @property +# def software_version(self): +# return self._api.getSoftwareVersion(); + + + @property + def user(self): + return self._api.getUserDetails() + + @property + @error_handling + def server_version(self): + """ + :py:obj:`int` On-board server version of the detector + """ + return hex(self._api.getServerVersion()) + + @property + @error_handling + def settings(self): + """ + Detector settings used to control for example calibration or gain + switching. For EIGER almost always standard standard. + + .. warning :: + + For Eiger setting settings should be followed by setting the threshold + otherwise reading of the settings will overwrite the set value + + + """ + return self._api.getSettings() + + @settings.setter + @error_handling + def settings(self, s): + if s in self._settings: + self._api.setSettings(s) + else: + raise DetectorValueError('Settings: {:s}, not defined for {:s}. ' + 'Valid options are: [{:s}]'.format(s, self.detector_type, ', '.join(self._settings))) + + + @property + @error_handling + def settings_path(self): + """ + The path where the slsDetectorSoftware looks for settings/trimbit files + """ + return self._api.getSettingsDir() + + @settings_path.setter + @error_handling + def settings_path(self, path): + if os.path.isdir(path): + self._api.setSettingsDir(path) + else: + raise FileNotFoundError('Settings path does not exist') + + @property + @error_handling + def status(self): + """ + :py:obj:`str` Status of the detector: idle, running, + + .. todo :: + + Check possible values + + """ + return self._api.getRunStatus() + + def start_detector(self): + """ + Non blocking command to star acquisition. Needs to be used in combination + with receiver start. + """ + self._api.startAcquisition() + + def stop_detector(self): + """ + Stop acquisition early or if the detector hangs + """ + self._api.stopAcquisition() + + + def start_receiver(self): + self._api.startReceiver() + + def stop_receiver(self): + self._api.stopReceiver() + + @property + def threaded(self): + """ + Enable parallel execution of commands to the different detector modules + + Examples + ---------- + + :: + + d.threaded + >> True + + d.threaded = False + + """ + return self._api.getThreadedProcessing() + + @threaded.setter + def threaded(self, value): + self._api.setThreadedProcessing(value) + + @property + @error_handling + def threshold(self): + """ + Detector threshold in eV + """ + return self._api.getThresholdEnergy() + + @threshold.setter + @error_handling + def threshold(self, eV): + self._api.setThresholdEnergy(eV) + + @property + def timing_mode(self): + """ + :py:obj:`str` Timing mode of the detector + + * **auto** Something + * **trigger** Something else + + + """ + return self._api.getTimingMode() + + @timing_mode.setter + def timing_mode(self, mode): + self._api.setTimingMode(mode) + + + @property + def trimmed_energies(self): + """ + EIGER: the energies at which the detector was trimmed. This also sets + the range for which the calibration of the detector is valid. + + + :: + + detector.trimmed_energies = [5400, 6400, 8000] + + detector.trimmed_energies + >> [5400, 6400, 8000] + + """ + + return self._api.getTrimEnergies() + + @trimmed_energies.setter + def trimmed_energies(self, energy_list): + self._api.setTrimEnergies(energy_list) + + @property + def vthreshold(self): + """ + Threshold in DAC units for the detector. Sets the individual vcmp of + all chips in the detector. + """ + return self._api.getDac('vthreshold', -1) + + @vthreshold.setter + def vthreshold(self, th): + self._api.setDac('vthreshold', -1, th) + + @property + def trimbits(self): + """ + Set or read trimbits of the detector. + + Examples + --------- + + :: + + #Set all to 32 + d.trimbits = 32 + + d.trimbits + >> 32 + + #if undefined or different + d.trimbits + >> -1 + + """ + return self._api.getAllTrimbits() + + @trimbits.setter + def trimbits(self, value): + if self._trimbit_limits.min <= value <= self._trimbit_limits.max: + self._api.setAllTrimbits(value) + else: + raise DetectorValueError('Trimbit setting {:d} is outside of range:'\ + '{:d}-{:d}'.format(value, self._trimbit_limits.min, self._trimbit_limits.max)) + + @property + def client_zmqport(self): + """zmq port of the client""" + _s = self._api.getNetworkParameter('client_zmqport') + if _s == '': + return [] + return [int(_p)+i for _p in _s for i in range(2)] + + @error_handling + def _provoke_error(self): + self._api.setErrorMask(1) + + + def config_network(self): + """ + Configures the detector source and destination MAC addresses, IP addresses + and UDP ports, and computes the IP header checksum for such parameters + """ + self._api.configureNetworkParameters() + +def free_shared_memory(multi_id=0): + """ + Function to free the shared memory but do not initialize with new + 0 size detector + """ + api = DetectorApi(multi_id) + api.freeSharedMemory() diff --git a/python/sls_detector/detector_property.py b/python/sls_detector/detector_property.py new file mode 100644 index 000000000..76f379db1 --- /dev/null +++ b/python/sls_detector/detector_property.py @@ -0,0 +1,50 @@ +from collections.abc import Iterable +import numpy as np + +class DetectorProperty: + """ + Base class for a detector property that should be accessed by name and index + """ + def __init__(self, get_func, set_func, nmod_func, name): + self.get = get_func + self.set = set_func + self.get_nmod = nmod_func + self.__name__ = name + + def __getitem__(self, key): + if key == slice(None, None, None): + return [self.get(i) for i in range(self.get_nmod())] + elif isinstance(key, Iterable): + return [self.get(k) for k in key] + else: + return self.get(key) + + def __setitem__(self, key, value): + #operate on all values + if key == slice(None, None, None): + if isinstance(value, (np.integer, int)): + for i in range(self.get_nmod()): + self.set(i, value) + elif isinstance(value, Iterable): + for i in range(self.get_nmod()): + self.set(i, value[i]) + else: + raise ValueError('Value should be int or np.integer not', type(value)) + + #Iterate over some + elif isinstance(key, Iterable): + if isinstance(value, Iterable): + for k,v in zip(key, value): + self.set(k,v) + + elif isinstance(value, int): + for k in key: + self.set(k, value) + + #Set single value + elif isinstance(key, int): + self.set(key, value) + + def __repr__(self): + s = ', '.join(str(v) for v in self[:]) + return '{}: [{}]'.format(self.__name__, s) \ No newline at end of file diff --git a/python/sls_detector/eiger.py b/python/sls_detector/eiger.py new file mode 100644 index 000000000..f87652cbc --- /dev/null +++ b/python/sls_detector/eiger.py @@ -0,0 +1,618 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +Created on Wed Dec 6 11:51:18 2017 + +@author: l_frojdh +""" + +import socket +from collections.abc import Iterable +from collections import namedtuple +from functools import partial + +from .adcs import Adc, DetectorAdcs +from .dacs import DetectorDacs +from .decorators import error_handling +from .detector import Detector +from .detector_property import DetectorProperty +from .utils import element_if_equal +from sls_detector.errors import DetectorValueError, DetectorError + +class EigerVcmp: + """ + Convenience class to be able to loop over vcmp for Eiger + + + .. todo:: + + Support single assignment and perhaps unify with Dac class + + """ + + def __init__(self, detector): + _names = ['vcmp_ll', + 'vcmp_lr', + 'vcmp_rl', + 'vcmp_rr'] + self.set = [] + self.get = [] + for i in range(detector.n_modules): + if i % 2 == 0: + name = _names + else: + name = _names[::-1] + for n in name: + self.set.append(partial(detector._api.setDac, n, i)) + self.get.append(partial(detector._api.getDac, n, i)) + + def __getitem__(self, key): + if key == slice(None, None, None): + return [_d() for _d in self.get] + return self.get[key]() + + def __setitem__(self, i, value): + self.set[i](value) + + def __repr__(self): + return 'vcmp: '+ str(self[:]) + + +class EigerDacs(DetectorDacs): + _dacs = [('vsvp', 0, 4000, 0), + ('vtr', 0, 4000, 2500), + ('vrf', 0, 4000, 3300), + ('vrs', 0, 4000, 1400), + ('vsvn', 0, 4000, 4000), + ('vtgstv', 0, 4000, 2556), + ('vcmp_ll', 0, 4000, 1500), + ('vcmp_lr', 0, 4000, 1500), + ('vcall', 0, 4000, 4000), + ('vcmp_rl', 0, 4000, 1500), + ('rxb_rb', 0, 4000, 1100), + ('rxb_lb', 0, 4000, 1100), + ('vcmp_rr', 0, 4000, 1500), + ('vcp', 0, 4000, 200), + ('vcn', 0, 4000, 2000), + ('vis', 0, 4000, 1550), + ('iodelay', 0, 4000, 660)] + _dacnames = [_d[0] for _d in _dacs] + + +# noinspection PyProtectedMember +class DetectorDelays: + _delaynames = ['frame', 'left', 'right'] + + def __init__(self, detector): + # We need to at least initially know which detector we are connected to + self._detector = detector + + setattr(self, '_frame', DetectorProperty(detector._api.getDelayFrame, + detector._api.setDelayFrame, + detector._api.getNumberOfDetectors, + 'frame')) + + setattr(self, '_left', DetectorProperty(detector._api.getDelayLeft, + detector._api.setDelayLeft, + detector._api.getNumberOfDetectors, + 'left')) + + setattr(self, '_right', DetectorProperty(detector._api.getDelayRight, + detector._api.setDelayRight, + detector._api.getNumberOfDetectors, + 'right')) + # Index to support iteration + self._current = 0 + + def __getattr__(self, name): + return self.__getattribute__('_' + name) + + def __setattr__(self, name, value): + if name in self._delaynames: + return self.__getattribute__('_' + name).__setitem__(slice(None, None, None), value) + else: + super().__setattr__(name, value) + + def __next__(self): + if self._current >= len(self._delaynames): + self._current = 0 + raise StopIteration + else: + self._current += 1 + return self.__getattr__(self._delaynames[self._current-1]) + + def __iter__(self): + return self + + def __repr__(self): + hn = self._detector.hostname + r_str = ['Transmission delay [ns]\n' + '{:11s}{:>8s}{:>8s}{:>8s}'.format('', 'left', 'right', 'frame')] + for i in range(self._detector.n_modules): + r_str.append('{:2d}:{:8s}{:>8d}{:>8d}{:>8d}'.format(i, hn[i], self.left[i], self.right[i], self.frame[i])) + return '\n'.join(r_str) + + +class Eiger(Detector): + """ + Subclassing Detector to set up correct dacs and detector specific + functions. + """ + _detector_dynamic_range = [4, 8, 16, 32] + + + _settings = ['standard', 'highgain', 'lowgain', 'veryhighgain', 'verylowgain'] + """available settings for Eiger, note almost always standard""" + + def __init__(self, id=0): + super().__init__(id) + + self._active = DetectorProperty(self._api.getActive, + self._api.setActive, + self._api.getNumberOfDetectors, + 'active') + + self._vcmp = EigerVcmp(self) + self._dacs = EigerDacs(self) + self._trimbit_limits = namedtuple('trimbit_limits', ['min', 'max'])(0, 63) + self._delay = DetectorDelays(self) + + # Eiger specific adcs + self._temp = DetectorAdcs() + self._temp.fpga = Adc('temp_fpga', self) + self._temp.fpgaext = Adc('temp_fpgaext', self) + self._temp.t10ge = Adc('temp_10ge', self) + self._temp.dcdc = Adc('temp_dcdc', self) + self._temp.sodl = Adc('temp_sodl', self) + self._temp.sodr = Adc('temp_sodr', self) + self._temp.fpgafl = Adc('temp_fpgafl', self) + self._temp.fpgafr = Adc('temp_fpgafr', self) + + @property + @error_handling + def active(self): + """ + Is the detector active? Can be used to enable or disable a detector + module + + Examples + ---------- + + :: + + d.active + >> active: [True, True] + + d.active[1] = False + >> active: [True, False] + """ + return self._active + + @active.setter + @error_handling + def active(self, value): + self._active[:] = value + + @property + def measured_period(self): + return self._api.getMeasuredPeriod() + + @property + def measured_subperiod(self): + return self._api.getMeasuredSubPeriod() + + @property + @error_handling + def add_gappixels(self): + """Enable or disable the (virual) pixels between ASICs + + Examples + ---------- + + :: + + d.add_gappixels = True + + d.add_gappixels + >> True + + """ + return self._api.getGapPixels() + + @add_gappixels.setter + @error_handling + def add_gappixels(self, value): + self._api.setGapPixels(value) + + @property + def dacs(self): + """ + + An instance of DetectorDacs used for accessing the dacs of a single + or multi detector. + + Examples + --------- + + :: + + d = Eiger() + + #Set all vrf to 1500 + d.dacs.vrf = 1500 + + #Check vrf + d.dacs.vrf + >> vrf : 1500, 1500 + + #Set a single vtr + d.dacs.vtr[0] = 1800 + + #Set vrf with multiple values + d.dacs.vrf = [3500,3700] + d.dacs.vrf + >> vrf : 3500, 3700 + + #read into a variable + var = d.dacs.vrf[:] + + #set multiple with multiple values, mostly used for large systems + d.dacs.vcall[0,1] = [3500,3600] + d.dacs.vcall + >> vcall : 3500, 3600 + + d.dacs + >> + ========== DACS ========= + vsvp : 0, 0 + vtr : 4000, 4000 + vrf : 1900, 1900 + vrs : 1400, 1400 + vsvn : 4000, 4000 + vtgstv : 2556, 2556 + vcmp_ll : 1500, 1500 + vcmp_lr : 1500, 1500 + vcall : 4000, 4000 + vcmp_rl : 1500, 1500 + rxb_rb : 1100, 1100 + rxb_lb : 1100, 1100 + vcmp_rr : 1500, 1500 + vcp : 1500, 1500 + vcn : 2000, 2000 + vis : 1550, 1550 + iodelay : 660, 660 + + """ + return self._dacs + + @property + @error_handling + def tx_delay(self): + """ + Transmission delay of the modules to allow running the detector + in a network not supporting the full speed of the detector. + + + :: + + d.tx_delay + >> + Transmission delay [ns] + left right frame + 0:beb048 0 15000 0 + 1:beb049 100 190000 100 + + d.tx_delay.left = [2000,5000] + """ + return self._delay + + def default_settings(self): + """ + reset the detector to some type of standard settings + mostly used when testing + """ + self.n_frames = 1 + self.exposure_time = 1 + self.period = 0 + self.n_cycles = 1 + self.n_measurements = 1 + self.dynamic_range = 16 + + @property + @error_handling + def eiger_matrix_reset(self): + """ + Matrix reset bit for Eiger. + + :py:obj:`True` : Normal operation, the matrix is reset before each acq. + :py:obj:`False` : Matrix reset disabled. Used to not reset before + reading out analog test pulses. + """ + return self._api.getCounterBit() + + @eiger_matrix_reset.setter + @error_handling + def eiger_matrix_reset(self, value): + self._api.setCounterBit(value) + + @property + @error_handling + def flowcontrol_10g(self): + """ + :py:obj:`True` - Flow control enabled :py:obj:`False` flow control disabled. + Sets for all moduels, if for some reason access to a single module is needed + this can be done trough the C++ API. + + """ + fc = self._api.getNetworkParameter('flow_control_10g') + return element_if_equal([bool(int(e)) for e in fc]) + + @flowcontrol_10g.setter + @error_handling + def flowcontrol_10g(self, value): + if value is True: + v = '1' + else: + v = '0' + self._api.setNetworkParameter('flow_control_10g', v, -1) + + @error_handling + def pulse_all_pixels(self, n): + """ + Pulse each pixel of the chip **n** times using the analog test pulses. + The pulse height is set using d.dacs.vcall with 4000 being 0 and 0 being + the highest pulse. + + :: + + #Pulse all pixels ten times + d.pulse_all_pixels(10) + + #Avoid resetting before acq + d.eiger_matrix_reset = False + + d.acq() #take frame + + #Restore normal behaviour + d.eiger_matrix_reset = True + + + """ + self._api.pulseAllPixels(n) + + @error_handling + def pulse_diagonal(self, n): + """ + Pulse pixels in super colums in a diagonal fashion. Used for calibration + of vcall. Saves time compared to pulsing all pixels. + """ + self._api.pulseDiagonal(n) + + @error_handling + def pulse_chip(self, n): + """ + Advance the counter by toggling enable. Gives 2*n+2 int the counter + + """ + n = int(n) + if n >= -1: + self._api.pulseChip(n) + else: + raise ValueError('n must be equal or larger than -1') + + @property + def vcmp(self): + """ + Convenience function to get and set the individual vcmp of chips + Used mainly in the calibration code. + + Examples + --------- + + :: + + #Reading + d.vcmp[:] + >> [500, 500, 500, 500, 500, 500, 500, 500] + + #Setting + d.vcmp = [500, 500, 500, 500, 500, 500, 500, 500] + + + """ + + return self._vcmp + + @vcmp.setter + @error_handling + def vcmp(self, values): + if len(values) == len(self._vcmp.set): + for i, v in enumerate(values): + self._vcmp.set[i](v) + else: + raise ValueError('vcmp only compatible with setting all') + + @property + @error_handling + def rx_udpport(self): + """ + UDP port for the receiver. Each module has two ports referred to + as rx_udpport and rx_udpport2 in the command line interface + here they are grouped for each detector + + :: + + [0:rx_udpport, 0:rx_udpport2, 1:rx_udpport ...] + + Examples + ----------- + + :: + + d.rx_udpport + >> [50010, 50011, 50004, 50005] + + d.rx_udpport = [50010, 50011, 50012, 50013] + + """ + p0 = self._api.getNetworkParameter('rx_udpport') + p1 = self._api.getNetworkParameter('rx_udpport2') + return [int(val) for pair in zip(p0, p1) for val in pair] + + @rx_udpport.setter + @error_handling + def rx_udpport(self, ports): + """Requires iterating over elements two and two for setting ports""" + a = iter(ports) + for i, p in enumerate(zip(a, a)): + self._api.setNetworkParameter('rx_udpport', str(p[0]), i) + self._api.setNetworkParameter('rx_udpport2', str(p[1]), i) + + @property + @error_handling + def rx_zmqport(self): + """ + Return the receiver zmq ports. Note that Eiger has two ports per receiver! + + :: + + detector.rx_zmqport + >> [30001, 30002, 30003, 30004] + + + """ + _s = self._api.getNetworkParameter('rx_zmqport') + if _s == '': + return [] + else: + return [int(_p) + i for _p in _s for i in range(2)] + + @rx_zmqport.setter + @error_handling + def rx_zmqport(self, port): + if isinstance(port, Iterable): + for i, p in enumerate(port): + self._api.setNetworkParameter('rx_zmqport', str(p), i) + else: + self._api.setNetworkParameter('rx_zmqport', str(port), -1) + + + @property + @error_handling + def sub_exposure_time(self): + """ + Sub frame exposure time in *seconds* for Eiger in 32bit autosumming mode + + :: + + d.sub_exposure_time + >> 0.0023 + + d.sub_exposure_time = 0.002 + + """ + return self._api.getSubExposureTime() / 1e9 + + + @sub_exposure_time.setter + @error_handling + def sub_exposure_time(self, t): + #TODO! checking here or in the detector? + ns_time = int(t * 1e9) + if ns_time > 0: + self._api.setSubExposureTime(ns_time) + else: + raise DetectorValueError('Sub exposure time must be larger than 0') + + @property + @error_handling + def sub_deadtime(self): + """ + Deadtime between subexposures. Used to mimize noise by delaying the start of the next + subexposure. + """ + return self._api.getSubExposureDeadTime() / 1e9 + + + @sub_deadtime.setter + @error_handling + def sub_deadtime(self, t): + ns_time = int(t * 1e9) + if ns_time >= 0: + self._api.setSubExposureDeadTime(ns_time) + else: + raise ValueError('Sub deadtime time must be larger or equal to 0') + + @property + def temp(self): + """ + An instance of DetectorAdcs used to read the temperature + of different components + + Examples + ----------- + + :: + + detector.temp + >> + temp_fpga : 36.90°C, 45.60°C + temp_fpgaext : 31.50°C, 32.50°C + temp_10ge : 0.00°C, 0.00°C + temp_dcdc : 36.00°C, 36.00°C + temp_sodl : 33.00°C, 34.50°C + temp_sodr : 33.50°C, 34.00°C + temp_fpgafl : 33.81°C, 30.93°C + temp_fpgafr : 27.88°C, 29.15°C + + a = detector.temp.fpga[:] + a + >> [36.568, 45.542] + + + """ + return self._temp + + @property + @error_handling + def tengiga(self): + """Enable 10Gbit/s data output + + Examples + ---------- + + :: + + d.tengiga + >> False + + d.tengiga = True + + """ + return self._api.getTenGigabitEthernet() + + @tengiga.setter + @error_handling + def tengiga(self, value): + self._api.setTenGigabitEthernet(value) + + def set_delays(self, delta): + self.tx_delay.left = [delta*(i*2) for i in range(self.n_modules)] + self.tx_delay.right = [delta*(i*2+1) for i in range(self.n_modules)] + + + def setup500k(self, hostnames): + """ + Setup the Eiger detector to run on the local machine + """ + + self.hostname = hostnames + self.file_write = False + self.image_size = (512, 1024) + self.rx_tcpport = [1954, 1955] + self.rx_udpport = [50010, 50011, 50004, 50005] + self.rx_hostname = socket.gethostname().split('.')[0] + self.rx_datastream = False + self.file_write = False + self.online = True + self.receiver_online = True diff --git a/python/sls_detector/errors.py b/python/sls_detector/errors.py new file mode 100644 index 000000000..f16ceeb9f --- /dev/null +++ b/python/sls_detector/errors.py @@ -0,0 +1,25 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +Created on Thu Dec 14 17:13:55 2017 + +@author: l_frojdh +""" + + +class DetectorError(Exception): + """ + This error should be used when something fails + on the detector side + """ + pass + + +class DetectorSettingDoesNotExist(Exception): + """This error should be used when the setting does not exist""" + pass + + +class DetectorValueError(Exception): + """This error should be used when the set value is outside the allowed range""" + pass diff --git a/python/sls_detector/jungfrau.py b/python/sls_detector/jungfrau.py new file mode 100644 index 000000000..52f226a39 --- /dev/null +++ b/python/sls_detector/jungfrau.py @@ -0,0 +1,275 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +Jungfrau detector class and support functions. +Inherits from Detector. +""" +from .adcs import Adc, DetectorAdcs +from .decorators import error_handling +from .detector import Detector +from .dacs import DetectorDacs +from .utils import element_if_equal + + +class JungfrauDacs(DetectorDacs): + _dacs = [('vb_comp', 0, 4000, 1220), + ('vdd_prot', 0, 4000, 3000), + ('vin_com', 0, 4000, 1053), + ('vref_prech', 0, 4000, 1450), + ('vb_pixbuff', 0, 4000, 750), + ('vb_ds', 0, 4000, 1000), + ('vref_ds', 0, 4000, 480), + ('vref_comp', 0, 4000, 420), + ] + _dacnames = [_d[0] for _d in _dacs] + +class Jungfrau(Detector): + """ + Class used to control a Jungfrau detector. Inherits from the Detector class but a specialized + class is needed to provide the correct dacs and unique functions. + + """ + _detector_dynamic_range = [4, 8, 16, 32] + + _settings = ['dynamichg0', + 'dynamicgain', + 'fixgain1', + 'fixgain2', + 'forceswitchg1', + 'forceswitchg2'] + """Available settings for Jungfrau""" + + def __init__(self, multi_id=0): + #Init on base calss + super().__init__(multi_id) + self._dacs = JungfrauDacs(self) + + #Jungfrau specific temps, this can be reduced to a single value? + self._temp = DetectorAdcs() + self._temp.fpga = Adc('temp_fpga', self) + # self._register = Register(self) + + + @property + def dacs(self): + """ + + An instance of DetectorDacs used for accessing the dacs of a single + or multi detector. + + Examples + --------- + + :: + + #Jungfrau + + + """ + return self._dacs + + @property + @error_handling + def power_chip(self): + """Power on or off the ASICs, True for on False for off""" + return self._api.isChipPowered() + + @power_chip.setter + @error_handling + def power_chip(self, value): + self._api.powerChip(value) + + @property + @error_handling + def delay(self): + """Delay after trigger [s]""" + return self._api.getDelay()/1e9 + + @delay.setter + @error_handling + def delay(self, t): + ns_time = int(t * 1e9) + self._api.setDelay(ns_time) + + @property + @error_handling + def n_gates(self): + return self._api.getNumberOfGates() + + @n_gates.setter + @error_handling + def n_gates(self, n): + self._api.setNumberOfGates(n) + + @property + @error_handling + def n_probes(self): + return self._api.getNumberOfProbes() + + @n_probes.setter + @error_handling + def n_probes(self, n): + self._api.setNumberOfProbes(n) + + @property + @error_handling + def storagecell_start(self): + """ + First storage cell + """ + return self._api.getStoragecellStart() + + @storagecell_start.setter + @error_handling + def storagecell_start(self, value): + self._api.setStoragecellStart(value) + + + @property + @error_handling + def n_storagecells(self): + """ + number of storage cells used for the measurements + """ + return self._api.getNumberOfStorageCells() + + @n_storagecells.setter + @error_handling + def n_storagecells(self, value): + self._api.setNumberOfStorageCells(value) + + @property + def temp(self): + """ + An instance of DetectorAdcs used to read the temperature + of different components + + Examples + ----------- + + :: + + detector.temp + >> + temp_fpga : 36.90°C, 45.60°C + + a = detector.temp.fpga[:] + a + >> [36.568, 45.542] + + + """ + return self._temp + + @property + def temperature_threshold(self): + """Threshold for switching of chips""" + return self._api.getThresholdTemperature() + + @temperature_threshold.setter + def temperature_threshold(self, t): + self._api.setThresholdTemperature(t) + + @property + def temperature_control(self): + """ + Monitor the temperature of the detector and switch off chips if temperature_threshold is + crossed + + + Examples + --------- + + :: + + #activate + detector.temperature_control = True + + #deactivate + detector.temperature_control = False + + + """ + return self._api.getTemperatureControl() + + @temperature_control.setter + def temperature_control(self, v): + self._api.setTemperatureControl(v) + + @property + def temperature_event(self): + """Have the temperature threshold been crossed? + + Returns + --------- + + :py:obj:`True` if the threshold have been crossed and temperature_control is active + otherwise :py:obj:`False` + + """ + return self._api.getTemperatureEvent() + + def reset_temperature_event(self): + """Reset the temperature_event. After reset temperature_event is False""" + self._api.resetTemperatureEvent() + + @property + @error_handling + def rx_udpport(self): + """ + UDP port for the receiver. Each module have one port. + Note! Eiger has two ports + + :: + + [0:rx_udpport] + + Examples + ----------- + + :: + + d.rx_udpport + >> [50010] + + d.rx_udpport = [50010] + + """ + return self._api.getNetworkParameter('rx_udpport') + + + @rx_udpport.setter + @error_handling + def rx_udpport(self, ports): + """Requires iterating over elements two and two for setting ports""" + for i, p in enumerate(ports): + self._api.setNetworkParameter('rx_udpport', str(p), i) + + @property + def detector_mac(self): + s = self._api.getNetworkParameter('detectormac') + return element_if_equal(s) + + + @detector_mac.setter + def detector_mac(self, mac): + if isinstance(mac, list): + for i, m in enumerate(mac): + self._api.setNetworkParameter('detectormac', m, i) + else: + self._api.setNetworkParameter('detectormac', mac, -1) + + + @property + @error_handling + def detector_ip(self): + s = self._api.getNetworkParameter('detectorip') + return element_if_equal(s) + + @detector_ip.setter + def detector_ip(self, ip): + if isinstance(ip, list): + for i, addr in enumerate(ip): + self._api.setNetworkParameter('detectorip', addr, i) + else: + self._api.setNetworkParameter('detectorip', ip, -1) diff --git a/python/sls_detector/jungfrau_ctb.py b/python/sls_detector/jungfrau_ctb.py new file mode 100644 index 000000000..08233b376 --- /dev/null +++ b/python/sls_detector/jungfrau_ctb.py @@ -0,0 +1,181 @@ +from functools import partial +from collections.abc import Iterable +from collections import namedtuple +import socket + +from .detector import Detector +from .utils import element_if_equal +from .adcs import DetectorAdcs, Adc +from .dacs import DetectorDacs +from .detector_property import DetectorProperty +from .decorators import error_handling +from .registers import Register, Adc_register + +class JungfrauCTBDacs(DetectorDacs): + _dacs = [('dac0', 0, 4000, 1400), + ('dac1', 0, 4000, 1200), + ('dac2', 0, 4000, 900), + ('dac3', 0, 4000, 1050), + ('dac4', 0, 4000, 1400), + ('dac5', 0, 4000, 655), + ('dac6', 0, 4000, 2000), + ('dac7', 0, 4000, 1400), + ('dac8', 0, 4000, 850), + ('dac9', 0, 4000, 2000), + ('dac10', 0, 4000, 2294), + ('dac11', 0, 4000, 983), + ('dac12', 0, 4000, 1475), + ('dac13', 0, 4000, 1200), + ('dac14', 0, 4000, 1600), + ('dac15', 0, 4000, 1455), + ('dac16', 0, 4000, 0), + ('dac17', 0, 4000, 1000), + ] + _dacnames = [_d[0] for _d in _dacs] + + + +class JungfrauCTB(Detector): + def __init__(self, id = 0): + super().__init__(id) + self._dacs = JungfrauCTBDacs(self) + self._register = Register(self) + self._adc_register = Adc_register(self) + + @property + def v_a(self): + return self._api.getDac_mV('v_a', -1) + + @v_a.setter + def v_a(self, value): + self._api.setDac_mV('v_a', -1, value) + + @property + def v_b(self): + return self._api.getDac_mV('v_b', -1) + + @v_b.setter + def v_b(self, value): + self._api.setDac_mV('v_b', -1, value) + + + @property + def v_c(self): + return self._api.getDac_mV('v_c', -1) + + @v_c.setter + def v_c(self, value): + self._api.setDac_mV('v_c', -1, value) + + @property + def v_d(self): + return self._api.getDac_mV('v_d', -1) + + @v_d.setter + def v_d(self, value): + self._api.setDac_mV('v_d', -1, value) + + @property + def v_io(self): + return self._api.getDac_mV('v_io', -1) + + @v_io.setter + def v_io(self, value): + self._api.setDac_mV('v_io', -1, value) + + @property + def v_limit(self): + return self._api.getDac_mV('v_limit', -1) + + @v_limit.setter + def v_limit(self, value): + self._api.setDac_mV('v_limit', -1, value) + + @property + def adc_register(self): + return self._adc_register + + # @property + # def register(self): + # return self._register + + def adcOFF(self): + """Switch off the ADC""" + self.adc_register[0x8] = 1 + + + + @property + def dacs(self): + """ + + An instance of DetectorDacs used for accessing the dacs of a single + or multi detector. + + Examples + --------- + + :: + + #JungfrauCTB + + + """ + return self._dacs + + @property + def dbitpipeline(self): + return self._api.getDbitPipeline() + + @dbitpipeline.setter + def dbitpipeline(self, value): + self._api.setDbitPipeline(value) + + + @property + def dbitphase(self): + return self._api.getDbitPhase() + + @dbitphase.setter + def dbitphase(self, value): + self._api.setDbitPhase(value) + + @property + def dbitclock(self): + return self._api.getDbitClock() + + @dbitclock.setter + def dbitclock(self, value): + self._api.setDbitClock(value) + + @property + def samples(self): + return self._api.getJCTBSamples() + + @samples.setter + def samples(self, value): + self._api.setJCTBSamples(value) + + @property + @error_handling + def readout_clock(self): + """ + Speed of the readout clock relative to the full speed + + + Examples + --------- + + :: + + + + + """ + return self._api.getReadoutClockSpeed() + + + @readout_clock.setter + @error_handling + def readout_clock(self, value): + self._api.setReadoutClockSpeed(value) diff --git a/python/sls_detector/registers.py b/python/sls_detector/registers.py new file mode 100644 index 000000000..48e6f2306 --- /dev/null +++ b/python/sls_detector/registers.py @@ -0,0 +1,18 @@ +from .decorators import error_handling, property_error_handling +class Register: + def __init__(self, detector): + self._detector = detector + + @property_error_handling + def __getitem__(self, key): + return self._detector._api.readRegister(key) + + def __setitem__(self, key, value): + self._detector._api.writeRegister(key, value) + +class Adc_register: + def __init__(self, detector): + self._detector = detector + + def __setitem__(self, key, value): + self._detector._api.writeAdcRegister(key, value) \ No newline at end of file diff --git a/python/sls_detector/utils.py b/python/sls_detector/utils.py new file mode 100644 index 000000000..b4f93fb12 --- /dev/null +++ b/python/sls_detector/utils.py @@ -0,0 +1,32 @@ +""" +Utility functions that are useful for testing and troubleshooting +but not directly used in controlling the detector +""" + + +def all_equal(mylist): + """If all elements are equal return true otherwise false""" + return all(x == mylist[0] for x in mylist) + + +def element_if_equal(mylist): + """If all elements are equal return only one element""" + if all_equal(mylist): + if len(mylist) == 0: + return None + else: + return mylist[0] + else: + return mylist + + +def eiger_register_to_time(register): + """ + Decode register value and return time in s. Values are stored in + a 32bit register with bits 2->0 containing the exponent and bits + 31->3 containing the significand (int value) + + """ + clocks = register >> 3 + exponent = register & 0b111 + return clocks*10**exponent / 100e6 diff --git a/python/sphinx/Makefile b/python/sphinx/Makefile new file mode 100644 index 000000000..7b6f690fb --- /dev/null +++ b/python/sphinx/Makefile @@ -0,0 +1,20 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = python -msphinx +SPHINXPROJ = sls_detector_tools +SOURCEDIR = . +BUILDDIR = _build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) \ No newline at end of file diff --git a/python/sphinx/_static/tp_scurve.png b/python/sphinx/_static/tp_scurve.png new file mode 100644 index 000000000..0ca71c2d9 Binary files /dev/null and b/python/sphinx/_static/tp_scurve.png differ diff --git a/python/sphinx/_static/untrimmed.png b/python/sphinx/_static/untrimmed.png new file mode 100644 index 000000000..86f620800 Binary files /dev/null and b/python/sphinx/_static/untrimmed.png differ diff --git a/python/sphinx/code_quality.rst b/python/sphinx/code_quality.rst new file mode 100644 index 000000000..9fe47c1fe --- /dev/null +++ b/python/sphinx/code_quality.rst @@ -0,0 +1,134 @@ +Code quality +============================= + +For usability and reliability of the software the code needs to be high quality. For this +project it means meeting the four criteria described below. Any addition should pass all of +them. + + +-------------------------------- +Look, read and feel like Python +-------------------------------- + +When using classes and functions from the +package it should feel like you are using Python tools and be forces +to write C++ style code with Python syntax. + +:: + + with xray_box.shutter_open(): + for th in threshold: + d.vthreshold = th + d.acq() + +should be preferred over + +:: + + N = len(threshold) + xray_box.open_shutter() + for i in range(N): + d.dacs.set_dac('vthreshold', threshold[i]) + d.acq() + xray_box.close_shutter() + +even if the difference might seem small. + +-------------------- +Have documentation +-------------------- + +Classes and functions should be documented with doc-strings +in the source code. Preferably with examples. The syntax to be used +is numpy-sphinx. + +:: + + def function(arg): + """ + This is a function that does something + + Parameters + ---------- + arg: int + An argument + + Returns + -------- + value: double + Returns a value + + """ + return np.sin(arg+np.pi) + +--------------------------------- +Pass static analysis with pylint +--------------------------------- + +Yes, anything less than 9/10 just means that you are lazy. If +there is a good reason why to diverge, then we can always +add an exception. + +Currently the following additions are made: + + * good-names: x, y, ax, im etc. + * function arguments 10 + * Whitelist: numpy, _sls + + + +----------------------- +Tested code +----------------------- + +Last but not least... *actually last just because of the long list included.* +All code that goes in should have adequate tests. If a new function does not +have a minimum of one test it does not get added. + +**Unit-tests with pytest and mocker** + +:: + + ----------- coverage: platform linux, python 3.6.4-final-0 ----------- + Name Stmts Miss Cover + ------------------------------------------------ + sls_detector/__init__.py 4 0 100% + sls_detector/decorators.py 14 3 79% + sls_detector/detector.py 461 115 75% + sls_detector/eiger.py 150 64 57% + sls_detector/errors.py 7 0 100% + sls_detector/jungfrau.py 59 26 56% + ------------------------------------------------ + TOTAL 695 208 70% + + + ========= 78 passed in 0.60 seconds ========= + + + +**Simple integration tests** + +These tests require a detector connected. Performs simple tasks like setting +exposure time and reading back to double check the value + +:: + + ----------- coverage: platform linux, python 3.6.4-final-0 ----------- + Name Stmts Miss Cover + ------------------------------------------------ + sls_detector/__init__.py 4 0 100% + sls_detector/decorators.py 14 0 100% + sls_detector/detector.py 461 103 78% + sls_detector/eiger.py 150 20 87% + sls_detector/errors.py 7 0 100% + sls_detector/jungfrau.py 59 26 56% + ------------------------------------------------ + TOTAL 695 149 79% + + + ========= 67 passed, 1 skipped in 16.66 seconds ========= + +**Complex integration test** + +Typical measurements. Might require X-rays. Tests are usually evaluated from +plots \ No newline at end of file diff --git a/python/sphinx/command_line.rst b/python/sphinx/command_line.rst new file mode 100644 index 000000000..a57d68696 --- /dev/null +++ b/python/sphinx/command_line.rst @@ -0,0 +1,370 @@ +Command line to Python +========================= + +If you are already familiar with the command line interface to the +slsDetectorSoftware here is a quick reference translating to Python commands + + + .. note :: + + Commands labeled Mythen only or Gotthard only are currently not implemented in the + Python class. If you need this functionallity please contact the SLS Detector Group + +.. py:currentmodule:: sls_detector + +.. |ro| replace:: *(read only)* +.. |free| replace:: :py:func:`Detector.free_shared_memory` +.. |sub| replace:: :py:attr:`Detector.sub_exposure_time` +.. |mg| replace:: Mythen and Gotthard only +.. |g| replace:: Gotthard only +.. |m| replace:: Mythen only +.. |msp| replace:: :py:attr:`Detector.measured_subperiod` +.. |new_chiptest| replace:: New chip test board only +.. |chiptest| replace:: Chip test board only +.. |dr| replace:: :py:attr:`Detector.dynamic_range` +.. |j| replace:: Jungfrau only +.. |te| replace:: :py:attr:`Detector.trimmed_energies` +.. |temp_fpgaext| replace:: :py:attr:`Detector.temp`.fpgaext +.. |epa| replace:: :py:func:`Eiger.pulse_all_pixels` +.. |rfc| replace:: :py:func:`Detector.reset_frames_caught` +.. |rfi| replace:: :py:attr:`Detector.receiver_frame_index` +.. |ron| replace:: :py:attr:`Detector.receiver_online` +.. |flipy| replace:: :py:attr:`Detector.flipped_data_y` +.. |flipx| replace:: :py:attr:`Detector.flipped_data_x` +.. |adcr| replace:: :py:func:`DetectorApi.writeAdcRegister` +.. |sb| replace:: :py:func:`DetectorApi.setBitInRegister` +.. |cb| replace:: :py:func:`DetectorApi.clearBitInRegister` +.. |tempth| replace:: :py:attr:`Jungfrau.temperature_threshold` +.. |tempev| replace:: :py:attr:`Jungfrau.temperature_event` +.. |tempco| replace:: :py:attr:`Jungfrau.temperature_control` +.. |depr| replace:: *Deprecated/Internal* +.. |nimp| replace:: *Not implemented* +.. |rudp| replace:: :py:attr:`Detector.rx_realudpsocksize` +.. |lci| replace:: :py:attr:`Detector.last_client_ip` +.. |rlci| replace:: :py:attr:`Detector.receiver_last_client_ip` +.. |fdp| replace:: :py:attr:`Detector.frame_discard_policy` +.. |apic| replace:: :py:attr:`Detector.api_compatibility` + + +------------------------ +Commands +------------------------ + +===================== ===================================== ================== ========= +Command Python Implementation Tests +===================== ===================================== ================== ========= +sls_detector_acquire :py:func:`Detector.acq` OK OK +test |depr| \- \- +help help(Detector.acq) \- \- +exitserver |depr| \- \- +exitreceiver |depr| \- \- +flippeddatay |flipy| OK \- +digitest |depr| \- \- +bustest |depr| \- \- +digibittest Which detector? \- \- +reg :py:attr:`Detector.register` OK \- +adcreg |adcr| OK \- +setbit |sb| OK \- +clearbit |cb| OK \- +getbit |nimp| \- \- +r_compression Not implemented in receiver \- \- +acquire :py:func:`Detector.acq` OK \- +busy :py:attr:`Detector.busy` OK Partial +status :py:attr:`Detector.status` OK |ro| \- +status start :py:func:`Detector.start_detector` OK \- +status stop :py:func:`Detector.stop_detector` OK \- +data |depr| \- \- +frame |depr| \- \- +readctr |g| \- \- +resetctr |g| \- \- +resmat :py:attr:`Eiger.eiger_matrix_reset` OK OK +free |free| OK \- +hostname :py:attr:`Detector.hostname` OK OK +add |nimp| \- \- +replace |nimp| \- \- +user |nimp| \- \- +master |nimp| \- \- +sync Which detector? \- \- +online :py:attr:`Detector.online` OK \- +checkonline |nimp| \- \- +activate :py:attr:`Eiger.active` \- \- +nmod :py:attr:`Detector.n_modules` OK \- +maxmod |depr| \- \- +dr |dr| OK OK +roi |g| \- \- +detsizechan :py:attr:`Detector.image_size` OK \- +roimask |nimp| \- \- +flippeddatax |flipx| OK \- +tengiga :py:attr:`Eiger.tengiga` OK \- +gappixels :py:attr:`Eiger.add_gappixels` OK \- +flags :py:attr:`Detector.flags` OK \- +extsig |mg| \- \- +programfpga |j| \- \- +resetfpga |j| \- \- +powerchip :py:attr:`Jungfrau.powerchip` \- \- +led |nimp| \- \- +auto_comp_disable |j| \- \- +pulse Used in |epa| OK \- +pulsenmove Used in |epa| OK \- +pulsechip :py:func:`Eiger.pulse_chip` OK \- +checkdetversion |apic| \- \- +checkrecversion |apic| \- \- +moduleversion |m| \- \- +detectornumber :py:attr:`Detector.detector_number` OK \- +modulenumber |m| \- \- +detectorversion :py:attr:`Detector.firmware_version` OK OK +softwareversion :py:attr:`Detector.server_version` \- \- +thisversion :py:attr:`Detector.client_version` Reads date \- +receiverversion :py:attr:`Detector.receiver_version` Reads date \- +timing :py:attr:`Detector.timing_mode` OK \- +exptime :py:attr:`Detector.exposure_time` OK OK +subexptime |sub| OK OK +period :py:attr:`Detector.period` OK OK +subdeadtime :py:attr:`Eiger.sub_deadtime` OK OK +delay :py:attr:`Jungfrau.delay` OK \- +gates :py:attr:`Jungfrau.n_gates` OK \- +frames :py:attr:`Detector.n_frames` OK OK +cycles :py:attr:`Detector.n_cycles` OK \- +probes :py:attr:`Jungfrau.n_probes` OK \- +measurements :py:attr:`Detector.n_measurements` OK \- +samples Chip test board only (new?) \- \- +storagecells :py:attr:`Jungfrau.n_storagecells` OK \- +storagecell_start :py:attr:`Jungfrau.storagecell_start` OK \- +exptimel |mg| \- \- +periodl |mg| \- \- +delayl |mg| \- \- +gatesl |mg| \- \- +framesl |mg| \- \- +cyclesl |mg| \- \- +probesl |mg| \- \- +now |nimp| \- \- +timestamp |m| \- \- +nframes |nimp| \- \- +measuredperiod :py:attr:`Detector.measured_period` OK \- +measuredsubperiod |msp| \- \- +clkdivider :py:attr:`Detector.readout_clock` OK OK +setlength |m| \- \- +waitstates |m| \- \- +totdivider |m| \- \- +totdutycycle |m| \- \- +phasestep |g| \- \- +oversampling |new_chiptest| \- \- +adcclk |new_chiptest| \- \- +adcphase |new_chiptest| \- \- +adcpipeline |new_chiptest| \- \- +dbitclk |new_chiptest| \- \- +dbitphase |new_chiptest| \- \- +dbitpipeline |new_chiptest| \- \- +config :py:func:`Detector.load_config` OK \- +rx_printconfig |nimp| \- \- +parameters :py:func:`Detector.load_parameters` OK \- +setup |nimp| \- \- +flatfield |nimp| \- \- +ffdir |nimp| \- \- +ratecorr :py:attr:`Detector.rate_correction` OK \- +badchannels |nimp| \- \- +angconv |m| \- \- +globaloff |nimp| \- \- +fineoff |nimp| \- \- +binsize |nimp| \- \- +angdir |nimp| \- \- +moveflag |nimp| \- \- +samplex |nimp| \- \- +sampley |nimp| \- \- +threaded :py:attr:`Detector.threaded` OK \- +darkimage |nimp| \- \- +gainimage |nimp| \- \- +settingsdir :py:attr:`Detector.settings_path` OK \- +trimdir |nimp| \- \- +caldir |nimp| \- \- +trimen :py:attr:`Detector.trimmed_energies` OK \- +settings :py:attr:`Detector.settings` OK \- +threshold :py:attr:`Detector.threshold` OK \- +thresholdnotb |nimp| \- \- +trimbits :py:func:`Detector.load_trimbits` OK \- +trim |nimp| \- \- +trimval :py:attr:`Detector.trimbits` OK OK +pedestal |nimp| \- \- +vthreshold :py:attr:`Detector.vthreshold` OK \- +vcalibration |nimp| \- \- +vtrimbit |nimp| \- \- +vpreamp |nimp| \- \- +vshaper1 |nimp| \- \- +vshaper2 |nimp| \- \- +vhighvoltage :py:attr:`Detector.high_voltage` OK \- +vapower |nimp| \- \- +vddpower |nimp| \- \- +vshpower |nimp| \- \- +viopower |nimp| \- \- +vref_ds :py:attr:`Jungfrau.dacs.vref_ds` OK \- +vcascn_pb |nimp| \- \- +vcascp_pb |nimp| \- \- +vout_cm |nimp| \- \- +vcasc_out |nimp| \- \- +vin_cm |nimp| \- \- +vref_comp |nimp| \- \- +ib_test_c |nimp| \- \- +dac0 |nimp| \- \- +dac1 |nimp| \- \- +dac2 |nimp| \- \- +dac3 |nimp| \- \- +dac4 |nimp| \- \- +dac5 |nimp| \- \- +dac6 |nimp| \- \- +dac7 |nimp| \- \- +vsvp :py:attr:`Eiger.dacs.vsvp` OK \- +vsvn :py:attr:`Eiger.dacs.vsvn` OK \- +vtr :py:attr:`Eiger.dacs.vtr` OK \- +vrf :py:attr:`Eiger.dacs.vrf` OK \- +vrs :py:attr:`Eiger.dacs.vrs` OK \- +vtgstv :py:attr:`Eiger.dacs.vtgstv` OK \- +vcmp_ll :py:attr:`Eiger.dacs.vcmp_ll` OK \- +vcmp_ll :py:attr:`Eiger.dacs.vcmp_ll` OK \- +vcall :py:attr:`Eiger.dacs.vcall` OK \- +vcmp_rl :py:attr:`Eiger.dacs.vcmp_rl` OK \- +vcmp_rr :py:attr:`Eiger.dacs.vcmp_rr` OK \- +rxb_rb :py:attr:`Eiger.dacs.rxb_rb` OK \- +rxb_lb :py:attr:`Eiger.dacs.rxb_lb` OK \- +vcp :py:attr:`Eiger.dacs.vcp` OK \- +vcn :py:attr:`Eiger.dacs.vcn` OK \- +vis :py:attr:`Eiger.dacs.vis` OK \- +iodelay :py:attr:`Eiger.dacs.iodelay` OK \- +dac |nimp| \- \- +adcvpp |nimp| \- \- +v_a |nimp| \- \- +v_b |nimp| \- \- +v_c |nimp| \- \- +v_d |nimp| \- \- +v_io |nimp| \- \- +v_chip |nimp| \- \- +v_limit |nimp| \- \- +vIpre |nimp| \- \- +VcdSh |nimp| \- \- +Vth1 |nimp| \- \- +Vth2 |nimp| \- \- +Vth3 |nimp| \- \- +VPL |nimp| \- \- +Vtrim |nimp| \- \- +vIbias |nimp| \- \- +vIinSh |nimp| \- \- +cas |nimp| \- \- +casSh |nimp| \- \- +vIbiasSh |nimp| \- \- +vIcin |nimp| \- \- +vIpreOut |nimp| \- \- +temp_adc |nimp| \- \- +temp_fpga :py:attr:`Detector.temp`.fpga OK \- +temp_fpgaext |temp_fpgaext| OK \- +temp_10ge :py:attr:`Detector.temp`.t10ge OK \- +temp_dcdc :py:attr:`Detector.temp`.dcdc OK \- +temp_sodl :py:attr:`Detector.temp`.sodl OK \- +temp_sodr :py:attr:`Detector.temp`.sodr OK \- +adc |nimp| \- \- +temp_fpgafl :py:attr:`Detector.temp`.fpgafl OK \- +temp_fpgafr :py:attr:`Detector.temp`.fpgafr OK \- +i_a |nimp| \- \- +i_b |nimp| \- \- +i_c |nimp| \- \- +i_d |nimp| \- \- +i_io |nimp| \- \- +vm_a |nimp| \- \- +vm_b |nimp| \- \- +vm_c |nimp| \- \- +vm_d |nimp| \- \- +vm_io |nimp| \- \- +temp_threshold |tempth| \- \- +temp_control |tempco| \- \- +temp_event |tempev| \- \- +outdir :py:attr:`Detector.file_path` OK OK +fname :py:attr:`Detector.file_name` OK OK +index :py:attr:`Detector.file_index` OK OK +enablefwrite :py:attr:`Detector.file_write` OK OK +overwrite :py:attr:`Detector.file_overwrite` OK \- +currentfname |nimp| \- \- +fileformat :py:attr:`Detector.file_format` OK \- +positions |depr| \- \- +startscript |depr| \- \- +startscriptpar |depr| \- \- +stopscript |depr| \- \- +stopscriptpar |depr| \- \- +scriptbefore |depr| \- \- +scriptbeforepar |depr| \- \- +scriptafter |depr| \- \- +scriptafterpar |depr| \- \- +headerafter |depr| \- \- +headerbefore |depr| \- \- +headerbeforepar |depr| \- \- +headerafterpar |depr| \- \- +encallog |depr| \- \- +angcallog |depr| \- \- +scan0script |depr| \- \- +scan0par |depr| \- \- +scan0prec |depr| \- \- +scan0steps |depr| \- \- +scan0range |depr| \- \- +scan1script |depr| \- \- +scan1par |depr| \- \- +scan1prec |depr| \- \- +scan1steps |depr| \- \- +scan1range |depr| \- \- +rx_hostname :py:attr:`Detector.rx_hostname` OK \- +rx_udpip :py:attr:`Detector.rx_udpip` OK \- +rx_udpmac :py:attr:`Detector.rx_udpmac` OK \- +rx_udpport :py:attr:`Detector.rx_udpport` OK \- +rx_udpport2 :py:attr:`Detector.rx_udpport` OK \- +rx_udpsocksize :py:attr:`Detector.rx_udpsocksize` OK \- +rx_realudpsocksize |rudp| OK +detectormac :py:attr:`Detector.detector_mac` OK \- +detectorip :py:attr:`Detector.detector_ip` OK \- +txndelay_left :py:attr:`Eiger.delay`.left OK \- +txndelay_right :py:attr:`Eiger.delay`.right OK \- +txndelay_frame :py:attr:`Eiger.delay`.frame OK \- +flowcontrol_10g :py:attr:`Eiger.flowcontrol_10g` OK \- +zmqport :py:attr:`Detector.client_zmqport` Read \- +rx_zmqport :py:attr:`Detector.rx_zmqport` Read \- +rx_datastream :py:attr:`Detector.rx_datastream` OK \- +zmqip :py:attr:`Detector.client_zmqip` OK \- +rx_zmqip :py:attr:`Detector.rx_zmqip` Read \- +rx_jsonaddheader :py:attr:`Detector.rx_jsonaddheader` OK \- +configuremac :py:attr:`Detector.config_network` OK \- +rx_tcpport :py:attr:`Detector.rx_tcpport` +port |nimp| \- \- +stopport |nimp| \- \- +lock :py:attr:`Detector.lock` OK \- +lastclient :py:attr:`Detector.last_client_ip` OK \- +receiver start :py:func:`Detector.start_receiver` OK \- +receiver stop :py:func:`Detector.stop_receiver` \- \- +r_online |ron| OK \- +r_checkonline |nimp| \- \- +framescaught :py:attr:`Detector.frames_caught` OK \- +resetframescaught |rfc| OK \- +frameindex |rfi| OK \- +r_lock :py:attr:`Detector.lock_receiver` OK \- +r_lastclient |rlci| OK \- +r_readfreq |nimp| \- \- +rx_fifodepth :py:attr:`Detector.rx_fifodepth` OK \- +r_silent |nimp| \- \- +r_framesperfile :py:attr:`Detector.n_frames_per_file` OK \- +r_discardpolicy |fdp| OK \- +r_padding :py:attr:`Detector.file_padding` OK \- +adcinvert |chiptest| \- \- +adcdisable |chiptest| \- \- +pattern |chiptest| \- \- +patword |chiptest| \- \- +patioctrl |chiptest| \- \- +patclkctrl |chiptest| \- \- +patlimits |chiptest| \- \- +patloop0 |chiptest| \- \- +patnloop0 |chiptest| \- \- +patwait0 |chiptest| \- \- +patwaittime0 |chiptest| \- \- +patloop1 |chiptest| \- \- +patnloop1 |chiptest| \- \- +patwait1 |chiptest| \- \- +patwaittime1 |chiptest| \- \- +patloop2 |chiptest| \- \- +patnloop2 |chiptest| \- \- +patwait2 |chiptest| \- \- +patwaittime2 |chiptest| \- \- +dut_clk |chiptest| \- \- +===================== ===================================== ================== ========= \ No newline at end of file diff --git a/python/sphinx/conf.py b/python/sphinx/conf.py new file mode 100644 index 000000000..bb228ce4c --- /dev/null +++ b/python/sphinx/conf.py @@ -0,0 +1,178 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# sls_detector_tools documentation build configuration file, created by +# sphinx-quickstart on Wed Nov 1 10:17:29 2017. +# +# This file is execfile()d with the current directory set to its +# containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +# +import os +import sys +sys.path.insert(0, os.path.abspath('..')) + + +# -- General configuration ------------------------------------------------ + +# If your documentation needs a minimal Sphinx version, state it here. +# +# needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = ['sphinx.ext.autodoc', + 'sphinx.ext.doctest', + 'sphinx.ext.coverage', + 'sphinx.ext.mathjax', + 'sphinx.ext.viewcode', + 'sphinx.ext.napoleon', + 'sphinx.ext.todo', + 'sphinx.ext.autosummary'] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix(es) of source filenames. +# You can specify multiple suffix as a list of string: +# +# source_suffix = ['.rst', '.md'] +source_suffix = '.rst' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = 'sls_detector' +copyright = '2019, Sls Detector Group' +author = 'Erik Frojdh' + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short X.Y version. +version = '4.0.1' +# The full version, including alpha/beta/rc tags. +release = '4.0.1' + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +# +# This is also used if you do content translation via gettext catalogs. +# Usually you set "language" from the command line for these cases. +language = None + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This patterns also effect to html_static_path and html_extra_path +exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# If true, `todo` and `todoList` produce output, else they produce nothing. +todo_include_todos = True + + +# -- Options for HTML output ---------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +# +html_theme = "sphinx_rtd_theme" + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +# +# html_theme_options = {} + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + +# Custom sidebar templates, must be a dictionary that maps document names +# to template names. +# +# This is required for the alabaster theme +# refs: http://alabaster.readthedocs.io/en/latest/installation.html#sidebars +html_sidebars = { + '**': [ + 'about.html', + 'navigation.html', + 'relations.html', # needs 'show_related': True theme option to display + 'searchbox.html', + 'donate.html', + ] +} + + +# -- Options for HTMLHelp output ------------------------------------------ + +# Output file base name for HTML help builder. +htmlhelp_basename = 'sls_detector_doc' +napoleon_use_ivar = True + +# -- Options for LaTeX output --------------------------------------------- + +latex_elements = { + # The paper size ('letterpaper' or 'a4paper'). + # + # 'papersize': 'letterpaper', + + # The font size ('10pt', '11pt' or '12pt'). + # + # 'pointsize': '10pt', + + # Additional stuff for the LaTeX preamble. + # + # 'preamble': '', + + # Latex figure (float) alignment + # + # 'figure_align': 'htbp', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, +# author, documentclass [howto, manual, or own class]). +latex_documents = [ + (master_doc, 'sls_detector.tex', 'sls_detector Documentation', + 'Erik Frojdh', 'manual'), +] + + +# -- Options for manual page output --------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + (master_doc, 'sls_detector_tools', 'sls_detector_tools Documentation', + [author], 1) +] + + +# -- Options for Texinfo output ------------------------------------------- + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + (master_doc, 'py_sls', 'py_sls Documentation', + author, 'py_sls', 'One line description of project.', + 'Miscellaneous'), +] + + diff --git a/python/sphinx/cpp_api.rst b/python/sphinx/cpp_api.rst new file mode 100644 index 000000000..839dbac3f --- /dev/null +++ b/python/sphinx/cpp_api.rst @@ -0,0 +1,12 @@ +C++ API +===================================================== + + +.. py:currentmodule:: _sls_detector + +.. autoclass:: DetectorApi + :members: + :undoc-members: + + + diff --git a/python/sphinx/error-handling.rst b/python/sphinx/error-handling.rst new file mode 100644 index 000000000..56b3ddd46 --- /dev/null +++ b/python/sphinx/error-handling.rst @@ -0,0 +1,67 @@ +Error handling +========================= + + +Check input in Python +---------------------- + +As far as possible we try to check the input on the Python side +before calling the slsDeteectorsSoftware. Errors should not pass +silently but raise an exception + +:: + + #Trimbit range for Eiger is 0-63 + detector.trimbits = 98 + (...) + ValueError: Trimbit setting 98 is outside of range:0-63 + +Errors in slsDetectorsSoftware +------------------------------- + +The slsDetectorsSoftware uses a mask to record errors from the different +detectors. If an error is found we raise a RuntimeError at the end of the +call using the error message from slsDetectorsSoftware + +.. todo :: + + Implement this for all functions + +:: + + detector.settings = 'bananas' + (...) + RuntimeError: Detector 0: + Could not set settings. + Detector 1: + Could not set settings. + Detector 2: + Could not set settings. + + +Using decorators +------------------- + +Using decorators we can reset the error mask before the command and then +check it after the command + +.. code-block:: python + + #add decorator to check the error mask + @error_handling + def some_function(): + a = 1+1 + return a + +Communication with the detector is usually the biggest overhead so +this does not impact performance. + +:: + + %timeit d.exposure_time + >> 1.52 ms ± 5.42 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each) + + %timeit d.decorated_exposure_time + >> 1.53 ms ± 3.18 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each) + + diff --git a/python/sphinx/examples.rst b/python/sphinx/examples.rst new file mode 100644 index 000000000..f3bc7d478 --- /dev/null +++ b/python/sphinx/examples.rst @@ -0,0 +1,143 @@ +Examples +================ + +Some short hints on how to use the detector + +------------------------ +Simple threshold scan +------------------------ + +Assuming you have set up your detector with exposure time, period, enabled +file writing etc. + +.. code-block:: python + + from sls_detector import Eiger + + d = Eiger() + threshold = range(0, 2000, 200) + for th in threshold: + d.vthreshold = th + d.acq() + + +If we want to control the shutter of for example, the big X-ray box we can add +this line in our code. It then opens the shutter just before the measurement +and closes is afterwards. + +:: + + with xrf_shutter_open(box, 'Fe'): + for th in threshold: + d.vthreshold = th + d.acq() + + +----------------------- +Reading temperatures +----------------------- + +:: + + d.temp + >> + temp_fpga : 43.19°C, 51.83°C + temp_fpgaext : 38.50°C, 38.50°C + temp_10ge : 39.50°C, 39.50°C + temp_dcdc : 42.50°C, 42.50°C + temp_sodl : 39.50°C, 40.50°C + temp_sodr : 39.50°C, 40.50°C + temp_fpgafl : 40.87°C, 37.61°C + temp_fpgafr : 34.51°C, 35.63°C + + d.temp.fpga + >> temp_fpga : 40.84°C, 39.31°C + + t = d.temp.fpga[0] + t + >> 40.551 + + t = d.temp.fpga[:] + t + >> [40.566, 39.128] + + +----------------------- +Non blocking acquire +----------------------- + +There are mainly two ways to achieve a non blocking acquire when calling from the Python API. One is to manually start +the detector and the second one is to launch the normal acquire from a different process. Depending on your measurement +it might also be better to run the other task in a seperate process and use acq in the main thread. +But lets start looking at the at the manual way: + +:: + + import time + from sls_detector import Eiger + d = Eiger() + + n = 10 + t = 1 + + d.exposure_time = t + d.n_frames = n + d.reset_frames_caught() + + #Start the measurement + t0 = time.time() + d.start_receiver() + d.start_detector() + + #Wait for the detector to be ready or do other important stuff + time.sleep(t*n) + + #check if the detector is ready otherwise wait a bit longer + while d.status != 'idle': + time.sleep(0.1) + + #Stop the receiver after we got the frames + #Detector is already idle so we don't need to stop it + d.stop_receiver() + + lost = d.frames_caught - n + print(f'{n} frames of {t}s took {time.time()-t0:{.3}}s with {lost} frames lost ') + + #Reset to not interfere with a potential next measurement + d.reset_frames_caught() + +Instead launching d.acq() from a different process is a bit easier since the control of receiver and detector +is handled in the acq call. However, you need to join the process used otherwise a lot of zombie processes would +hang around until the main process exits. + +:: + + import time + from multiprocessing import Process + from sls_detector import Eiger + + def acquire(): + """ + Create a new Eiger object that still referes to the same actual detector + and same shared memory. Then launch acq. + """ + detector = Eiger() + detector.acq() + + #This is the detector we use throughout the session + d = Eiger() + + #Process to run acquire + p = Process(target=acquire) + + #Start the thread and short sleep to allow the acq to start + p.start() + time.sleep(0.01) + + #Do some other work + while d.busy is True: + print(d.busy) + time.sleep(0.1) + + #Join the process + p.join() \ No newline at end of file diff --git a/python/sphinx/getting_started.rst b/python/sphinx/getting_started.rst new file mode 100644 index 000000000..b5b464474 --- /dev/null +++ b/python/sphinx/getting_started.rst @@ -0,0 +1,122 @@ +Getting started +================ + + +------------------------ +Setting up the detector +------------------------ + +All configuration of the detector can either be done from the Python +API (including loading config file) or externally. The detector setup is +discovered from the shared memory when launching a new script. Because the +detector usually should remain online longer than a specific script it is +recommended to run the receivers seperate. + +--------------------------------- +Setting and getting attributes +--------------------------------- + +Most of the detector and software setting are implemented as attributes +in the Detector class. When something is assigned it is also set +in the detector and when the attribute is called using dot notation it +it looked up from the detector. + +:: + + #Currently Eiger and Jungfrau but Detector should work for all + from sls_detector import Eiger() + d = Eiger() + + d.file_write = True + d.vthreshold = 1500 + + d.frame_index + >> 12 + + d.file_name + >> 'run' + +--------------------------------- +Working with DACs +--------------------------------- + +The following examples assumes an Eiger500k detector. But the same syntax +works for other detector sizes and models. + +:: + + d.dacs + >> + ========== DACS ========= + vsvp : 0, 0 + vtr : 4000, 4000 + vrf : 2000, 2300 + vrs : 1400, 1400 + vsvn : 4000, 4000 + vtgstv : 2556, 2556 + vcmp_ll : 1500, 1500 + vcmp_lr : 1500, 1500 + vcall : 3500, 3600 + vcmp_rl : 1500, 1500 + rxb_rb : 1100, 1100 + rxb_lb : 1100, 1100 + vcmp_rr : 1500, 1500 + vcp : 1500, 1500 + vcn : 2000, 2000 + vis : 1550, 1550 + iodelay : 660, 660 + + #Read dac values to a variable + vrf = d.dacs.vrf[:] + + #Set a dac in a module + d.dacs.vrf[0] = 1500 + d.dacs.vrf[0] + >> 1500 + + #Set vrf to the same value in all moduels + d.dacs.vrf = 1500 + + #Set a dac using an iterable + d.dacs.vrf = [1500, 1600] + d.dacs.vrf + >> vrf : 1500, 1600 + + #Set dacs iterating on index and values + d.dacs.vrf[0,1] = 1300,1400 + + +--------------------------------- +Operating multiple detectors +--------------------------------- + +Operating multiple detectors is supported by assigning an id when creating the object. If no id is +set it defaults to 0. + +:: + + d0 = Eiger() #id is now 0 + d1 = Jungfrau(1) + + #Or explicitly + d1 = Jungfrau(id = 0) + +The detectors now operate independently of each other but can be synchronized using a hardware trigger. + +:: + + from sls_detector import Eiger + + d0 = Eiger(0) + d1 = Eiger(1) + + d0.load_config('/some/path/T45.config') + d1.load_config('/some/path/T62.config') + + d0.n_frames = 1 + d0.exposure_time = 1 + d0.timing_mode = 'trigger' + + d1.n_frames = 5 + d1.exposure_time = 0.2 + d1.timing_mode = 'trigger' \ No newline at end of file diff --git a/python/sphinx/index.rst b/python/sphinx/index.rst new file mode 100644 index 000000000..ae114adac --- /dev/null +++ b/python/sphinx/index.rst @@ -0,0 +1,30 @@ +sls_detector - Python interface for the slsDetectorsPackage +============================================================== + +sls_detector provide Python bindings to the slsDetectorsPackage using mainly the +multiSlsDetector API. This module contains two parts, a compiled C module to +expose the API and a Python class to offer a more Pythonic interface. + + +.. toctree:: + :maxdepth: 3 + :caption: Contents: + + installation + getting_started + code_quality + command_line + examples + error-handling + + sls_detector + cpp_api + + + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` diff --git a/python/sphinx/installation.rst b/python/sphinx/installation.rst new file mode 100644 index 000000000..f17920876 --- /dev/null +++ b/python/sphinx/installation.rst @@ -0,0 +1,90 @@ +Installation +========================= + +The easiest way to install the Python API and the slsDetectorPackage is using conda. But other +methods are also available. + +--------------------- +Install using conda +--------------------- +If you don't have it installed get the latest version of `Miniconda`_ + +.. _Miniconda: https://conda.io/miniconda.html + +:: + + wget https://repo.continuum.io/miniconda/Miniconda3-latest-Linux-x86_64.sh + sh Miniconda3-latest-Linux-x86_64.sh + + +Install sls_detector and sls_detector_lib using: + +:: + + #Add conda channels + conda config --add channels conda-forge + conda config --add channels slsdetectorgroup + + #Install latest version + conda install sls_detector + + #Install specific version + conda install sls_detector=3.0.1 + +------------------------------ +Local build using conda-build +------------------------------ + +Needs the `sls_detector_lib`_ installed in order to automatically find headers +and shared libraries. Make sure that the branch of sls_detector matches the lib +version installed. + +.. _sls_detector_lib: https://github.com/slsdetectorgroup/sls_detector_lib + +:: + + #Clone source code + git clone https://github.com/slsdetectorgroup/sls_detector.git + + #Checkout the branch needed + git checkout 3.0.1 + + #Build and install the local version + conda-build sls_detector + conda install --use-local sls_detector + + +----------------------- +Developer build +----------------------- + +IF you if you are developing and are making constant changes to the code it's a bit cumbersome +to build with conda and install. Then an easier way is to build the C/C++ parts in the package +directory and temporary add this to the path + +:: + + #in path/to/sls_detector + python setup.py build_ext --inplace + +Then in your Python script + +:: + + import sys + sys.path.append('/path/to/sls_detector') + from sls_detector import Detector + + + +-------------- +Prerequisites +-------------- + +All dependencies are manged trough conda but for a stand alone build you would need + + * gcc 4.8+ + * Qwt 6 + * Qt 4.8 + * numpy + * slsDetectorPackage diff --git a/python/sphinx/makedocs.sh b/python/sphinx/makedocs.sh new file mode 100755 index 000000000..b3773381f --- /dev/null +++ b/python/sphinx/makedocs.sh @@ -0,0 +1,6 @@ +make clean +make html +rm -rf ../docs/ +mv _build/html/ ../docs/ +touch ../docs/.nojekyll +rm -rf _build diff --git a/python/sphinx/modules.rst b/python/sphinx/modules.rst new file mode 100644 index 000000000..731034e95 --- /dev/null +++ b/python/sphinx/modules.rst @@ -0,0 +1,8 @@ +sls_detector +================== + +.. toctree:: + :maxdepth: 4 + + sls_detector + diff --git a/python/sphinx/sls_detector.rst b/python/sphinx/sls_detector.rst new file mode 100644 index 000000000..15df97e89 --- /dev/null +++ b/python/sphinx/sls_detector.rst @@ -0,0 +1,33 @@ +Python classes +===================================================== + + +.. py:currentmodule:: sls_detector + +Detector +---------- + +.. autoclass:: Detector + :members: + :undoc-members: + :show-inheritance: + + + +Eiger +------- + +.. autoclass:: Eiger + :members: + :undoc-members: + :show-inheritance: + + + +Jungfrau +---------- + +.. autoclass:: Jungfrau + :members: + :undoc-members: + :show-inheritance: diff --git a/python/src/Detector.h b/python/src/Detector.h new file mode 100644 index 000000000..25775c357 --- /dev/null +++ b/python/src/Detector.h @@ -0,0 +1,1040 @@ +#ifndef DETECTOR_H +#define DETECTOR_H +#include +#include +#include +#include + +#include + +#include "error_defs.h" +#include "multiSlsDetector.h" +#include "slsDetector.h" +#include "slsDetectorUtils.h" +#include "sls_detector_defs.h" +#include "sls_receiver_defs.h" + +class Detector { + public: + Detector(int i) + : det(i), multi_detector_id(i) { + //Disable output from std::cout + std::cout.setstate(std::ios_base::failbit); + } + + int getMultiDetectorId() { return multi_detector_id; } + + //get image size as [nrow, ncols] return as a pair of ints + std::pair getImageSize() { + std::pair image_size{0, 0}; + image_size.first = det.getMaxNumberOfChannelsPerDetector(slsDetectorDefs::dimension::Y); + image_size.second = det.getMaxNumberOfChannelsPerDetector(slsDetectorDefs::dimension::X); + return image_size; + } + + void setImageSize(const int rows, const int cols) { + det.setMaxNumberOfChannelsPerDetector(slsDetectorDefs::dimension::Y, rows); + det.setMaxNumberOfChannelsPerDetector(slsDetectorDefs::dimension::X, cols); + } + + //blocking command, acquire set number of frames + void acquire() { det.acquire(); } + + //for Eiger check status of the module + //true active false deactivated + bool getActive(const int i) const { + return getSlsDetector(i)->activate(); + } + //activate or deactivate a module + void setActive(const int i, const bool value) { + getSlsDetector(i)->activate(value); + } + + int getFramesCaughtByReceiver() { + return det.getFramesCaughtByReceiver(); + // return det.getFramesCaughtByReceiver(); + } + int getFramesCaughtByReceiver(int i) const { + return getSlsDetector(i)->getFramesCaughtByReceiver(); + } + + void setReceiverFifoDepth(int n_frames){ + det.setReceiverFifoDepth(n_frames); + } + + + void setNumberOfStorageCells(const int64_t num) { + det.setTimer(slsReceiverDefs::timerIndex::STORAGE_CELL_NUMBER, num); + } + int getNumberOfStorageCells(){ + return det.setTimer(slsReceiverDefs::timerIndex::STORAGE_CELL_NUMBER, -1); + } + + void setStoragecellStart(int cell){ + det.setStoragecellStart(cell); + } + + int getStoragecellStart(){ + return det.setStoragecellStart(); + } + + int getReceiverFifoDepth(){ + return det.setReceiverFifoDepth(); + } + + + void resetFramesCaught() { + det.resetFramesCaught(); + } + + int getReceiverCurrentFrameIndex() { + return det.getReceiverCurrentFrameIndex(); + } + + bool getThreadedProcessing() { + return det.setThreadedProcessing(); + } + void setThreadedProcessing(const bool value) { + det.setThreadedProcessing(value); + } + + void startReceiver() { det.startReceiver(); } + void stopReceiver() { det.stopReceiver(); } + + bool getTenGigabitEthernet() { + return det.enableTenGigabitEthernet(); + } + void setTenGigabitEthernet(const bool value) { + det.enableTenGigabitEthernet(value); + } + + void setFileFormat(const std::string& format); + std::string getFileFormat(); + + std::string checkOnline() { + return det.checkOnline(); + } + + void clearErrorMask() { + det.clearAllErrorMask(); + } + + int64_t getErrorMask() { + return det.getErrorMask(); + } + void setErrorMask(const int64_t i) { + det.setErrorMask(i); + } + + std::string getErrorMessage() { + //tmp would hold the number of critical errors, is and should this be used? + int tmp = 0; + return det.getErrorMessage(tmp); + } + + bool getReceiverOnline() { + return det.setReceiverOnline(); + } + void setReceiverOnline(const bool status) { + det.setReceiverOnline(status); + } + + bool getOnline() { + return det.setOnline(); + } + void setOnline(const bool status) { + det.setOnline(status); + } + + bool isChipPowered() { + return det.powerChip(); + } + void powerChip(const bool value) { + det.powerChip(value); + } + + //read register from readout system, used for low level control + uint32_t readRegister(const uint32_t addr) { + return det.readRegister(addr); + } + + //directly write to register in readout system + void writeRegister(const uint32_t addr, const uint32_t value) { + det.writeRegister(addr, value); + } + + //directly write to the ADC register + //should this also be unsigned? Probably... + void writeAdcRegister(const int addr, const int value) { + det.writeAdcRegister(addr, value); + } + + void setBitInRegister(const uint32_t reg_addr, const int bit_number) { + det.setBit(reg_addr, bit_number); + } + void clearBitInRegister(const uint32_t reg_addr, const int bit_number) { + det.clearBit(reg_addr, bit_number); + } + + bool getAcquiringFlag() { + return det.getAcquiringFlag(); + } + + void setAcquiringFlag(const bool flag) { + det.setAcquiringFlag(flag); + } + + bool getCounterBit() { + return det.setCounterBit(); + } + void setCounterBit(bool b) { + det.setCounterBit(b); + } + + slsDetectorDefs::dacIndex dacNameToEnum(std::string dac_name); + + std::pair getDetectorGeometry() { + std::pair g; + det.getNumberOfDetectors(g.first, g.second); + return g; + } + + int getNumberOfDetectors() { + return det.getNumberOfDetectors(); + } + + std::string getRunStatus() { + auto s = det.getRunStatus(); + return det.runStatusType(s); + } + + void startAcquisition() { det.startAcquisition(); } + void stopAcquisition() { det.stopAcquisition(); } + + std::string getHostname() { + return det.getHostname(); + } + + void setHostname(std::string hostname) { + det.setHostname(hostname.c_str()); + } + + int getDynamicRange() { + return det.setDynamicRange(-1); + } + void setDynamicRange(const int dr) { + det.setDynamicRange(dr); + } + + void pulseChip(const int n) { det.pulseChip(n); } + void pulseAllPixels(const int n); + void pulseDiagonal(const int n); + + void readConfigurationFile(std::string fname) { det.readConfigurationFile(fname); } + void readParametersFile(std::string fname) { det.retrieveDetectorSetup(fname); } + + int64_t getFirmwareVersion() { return det.getId(slsDetectorDefs::DETECTOR_FIRMWARE_VERSION); } + int64_t getServerVersion() { return det.getId(slsDetectorDefs::DETECTOR_SOFTWARE_VERSION); } + int64_t getClientVersion() { return det.getId(slsDetectorDefs::THIS_SOFTWARE_VERSION); } + int64_t getReceiverVersion() { return det.getId(slsDetectorDefs::RECEIVER_VERSION); } + + int getDetectorNumber(int i) const { + return getSlsDetector(i)->getId(slsDetectorDefs::DETECTOR_SERIAL_NUMBER); + } + + int getReadoutClockSpeed() { + return det.setSpeed(slsDetectorDefs::CLOCK_DIVIDER, -1); + } + void setReadoutClockSpeed(const int speed) { + det.setSpeed(slsDetectorDefs::CLOCK_DIVIDER, speed); + } + + void setDbitPipeline(const int value) { + det.setSpeed(slsDetectorDefs::DBIT_PIPELINE, value); + } + int getDbitPipeline() { + return det.setSpeed(slsDetectorDefs::DBIT_PIPELINE, -1); + } + void setDbitPhase(const int value) { + det.setSpeed(slsDetectorDefs::DBIT_PHASE, value); + } + int getDbitPhase() { + return det.setSpeed(slsDetectorDefs::DBIT_PHASE, -1); + } + void setDbitClock(const int value) { + det.setSpeed(slsDetectorDefs::DBIT_CLOCK, value); + } + int getDbitClock() { + return det.setSpeed(slsDetectorDefs::DBIT_CLOCK, -1); + } + int getRxTcpport(int i) const { + return getSlsDetector(i)->setPort(slsDetectorDefs::portType::DATA_PORT); + } + + void setRxTcpport(const int i, const int value) { + getSlsDetector(i)->setPort(slsDetectorDefs::portType::DATA_PORT, value); + } + + void setRateCorrection(std::vector tau) { + for (int i = 0; i < det.getNumberOfDetectors(); ++i) + getSlsDetector(i)->setRateCorrection(tau[i]); + } + + std::vector getRateCorrection(); + + bool getFlippedDataX(int i) const { + return getSlsDetector(i)->getFlippedData(slsDetectorDefs::dimension::X); + } + + bool getFlippedDataY(int i) const { + return getSlsDetector(i)->getFlippedData(slsDetectorDefs::dimension::Y); + } + + void setFlippedDataX(int i, bool value) { + getSlsDetector(i)->setFlippedData(slsDetectorDefs::dimension::X, value); + } + + void setFlippedDataY(int i, bool value) { + getSlsDetector(i)->setFlippedData(slsDetectorDefs::dimension::Y, value); + } + + /*** Frame and file settings ***/ + void setFileName(std::string fname) { + det.setFileName(fname); + } + std::string getFileName() { + return det.getFileName(); + } + void setFilePath(std::string path) { + det.setFilePath(path); + } + void setFilePath(std::string path, const int i) { + getSlsDetector(i)->setFilePath(path); + } + std::string getFilePath() { + return det.getFilePath(); + } + std::string getFilePath(int i) const { + return getSlsDetector(i)->getFilePath(); + } + + std::string getUserDetails() { + return det.getUserDetails(); + } + + void setReceiverFramesPerFile(const int n_frames) { + det.setReceiverFramesPerFile(n_frames); + } + int getReceiverFramesPerFile() { + return det.setReceiverFramesPerFile(); + } + + std::string getReceiverFrameDiscardPolicy() { + return det.getReceiverFrameDiscardPolicy(det.setReceiverFramesDiscardPolicy()); + } + void setReceiverFramesDiscardPolicy(std::string f) { + auto fdp = det.getReceiverFrameDiscardPolicy(f); + if (fdp == slsReceiverDefs::GET_FRAME_DISCARD_POLICY) { + throw std::invalid_argument("Coult not decode policy: nodiscard, discardempty, discardpartial"); + } + det.setReceiverFramesDiscardPolicy(fdp); + } + + bool getReceiverPartialFramesPadding() { + return det.setReceiverPartialFramesPadding(); + } + + std::vector getMeasuredPeriod() { + std::vector mp; + for (int i = 0; i < det.getNumberOfDetectors(); ++i) { + auto t = det.getTimeLeft(slsReceiverDefs::MEASURED_PERIOD, i); + mp.push_back(static_cast(t) * 1E-9); + } + return mp; + } + std::vector getMeasuredSubPeriod() { + std::vector mp; + for (int i = 0; i < det.getNumberOfDetectors(); ++i) { + auto t = det.getTimeLeft(slsReceiverDefs::MEASURED_SUBPERIOD, i); + mp.push_back(static_cast(t) * 1E-9); + } + return mp; + } + + bool isClientAndDetecorCompatible() { + auto r = det.checkVersionCompatibility(slsDetectorDefs::CONTROL_PORT); + if (r == 0) + return true; + else + return false; + } + bool isClientAndReceiverCompatible() { + auto r = det.checkVersionCompatibility(slsDetectorDefs::DATA_PORT); + if (r == 0) + return true; + else + return false; + } + + void setReceiverPartialFramesPadding(bool padding) { + det.setReceiverPartialFramesPadding(padding); + } + + /*** END Frame and file settings ***/ + + void loadTrimbitFile(std::string fname, const int idet) { + det.loadSettingsFile(fname, idet); + } + + //Eiger: set the energies where the detector is trimmed + void setTrimEnergies(std::vector energy) { + det.setTrimEn(energy.size(), energy.data()); + } + + std::vector getTrimEnergies() { + //initial call to get legth, energies defaults to NULL + auto n_trimen = det.getTrimEn(); + std::vector trim_energies(n_trimen); + + //second call to get the energies + det.getTrimEn(trim_energies.data()); + return trim_energies; + } + + /*** Temperature control functions for Jungfrau ***/ + void setThresholdTemperature(float t) { + det.setThresholdTemperature(static_cast(t * 1000), -1); + } + + float getThresholdTemperature() { + return static_cast(det.setThresholdTemperature(-1, -1)) / 1000.0; + } + + void setTemperatureControl(bool v) { + det.setTemperatureControl(v); + } + bool getTemperatureControl() { + return det.setTemperatureControl(); + } + + bool getTemperatureEvent() { + return det.setTemperatureEvent(); + } + void resetTemperatureEvent() { + det.setTemperatureEvent(0); + } + /*** END Temperature control functions for Jungfrau ***/ + + void setThresholdEnergy(const int eV) { + det.setThresholdEnergy(eV); + } + + std::string getSettingsDir() { return det.getSettingsDir(); } + void setSettingsDir(std::string dir) { det.setSettingsDir(dir); } + + int getThresholdEnergy() { + return det.getThresholdEnergy(); + } + + std::string getSettings() { + return det.getDetectorSettings(det.getSettings()); + } + + void setSettings(std::string s) { + det.setSettings(det.getDetectorSettings(s)); + } + + //name to enum translation on the c++ side + //should we instead expose the enum to Python? + dacs_t getDac(std::string dac_name, const int mod_id) { + dacs_t val = -1; + auto dac = dacNameToEnum(dac_name); + return det.setDAC(val, dac, 0, mod_id); + } + + void setDac(std::string dac_name, const int mod_id, dacs_t val) { + auto dac = dacNameToEnum(dac_name); + det.setDAC(val, dac, 0, mod_id); + } + + dacs_t getDac_mV(std::string dac_name, const int mod_id) { + dacs_t val = -1; + auto dac = dacNameToEnum(dac_name); + return det.setDAC(val, dac, 1, mod_id); + } + + void setDac_mV(std::string dac_name, const int mod_id, dacs_t value) { + auto dac = dacNameToEnum(dac_name); + det.setDAC(value, dac, 1, mod_id); + } + + //Intended for the JungfrauCTB should we name dacs instead + dacs_t getDacFromIndex(const int index, const int mod_id) { + dacs_t val = -1; + auto dac = static_cast(0); + return det.setDAC(val, dac, 0, mod_id); + } + //Intended for the JungfrauCTB should we name dacs instead + dacs_t setDacFromIndex(const int index, const int mod_id, dacs_t value) { + auto dac = static_cast(0); + return det.setDAC(value, dac, 0, mod_id); + } + + //Calling multi do we have a need to lock/unlock a single det? + bool getServerLock() { return det.lockServer(-1); } + void setServerLock(const bool value) { det.lockServer(value); } + bool getReceiverLock() { return det.lockReceiver(-1); } + void setReceiverLock(const bool value) { det.lockReceiver(value); } + + dacs_t getAdc(std::string adc_name, int mod_id) { + auto adc = dacNameToEnum(adc_name); + return det.getADC(adc, mod_id); + } + + std::vector getReadoutFlags(); + + //note singular + void setReadoutFlag(const std::string flag_name); + + //name to enum transltion of dac + dacs_t getDacVthreshold() { + dacs_t val = -1; + auto dac = slsDetectorDefs::dacIndex::THRESHOLD; + return det.setDAC(val, dac, 0, -1); + } + + void setDacVthreshold(const dacs_t val) { + auto dac = slsDetectorDefs::dacIndex::THRESHOLD; + det.setDAC(val, dac, 0, -1); + } + + void setFileIndex(const int i) { + det.setFileIndex(i); + } + + int getFileIndex() { + return det.setFileIndex(-1); + } + + //time in ns + void setExposureTime(const int64_t t) { + det.setTimer(slsReceiverDefs::timerIndex::ACQUISITION_TIME, t); + } + + //time in ns + int64_t getExposureTime() { + return det.setTimer(slsReceiverDefs::timerIndex::ACQUISITION_TIME, -1); + } + + void setSubExposureTime(const int64_t t) { + det.setTimer(slsReceiverDefs::timerIndex::SUBFRAME_ACQUISITION_TIME, t); + } + + int64_t getSubExposureTime() { + //time in ns + return det.setTimer(slsReceiverDefs::timerIndex::SUBFRAME_ACQUISITION_TIME, -1); + } + + void setSubExposureDeadTime(const int64_t t) { + det.setTimer(slsReceiverDefs::timerIndex::SUBFRAME_DEADTIME, t); + } + + int64_t getSubExposureDeadTime() { + //time in ns + return det.setTimer(slsReceiverDefs::timerIndex::SUBFRAME_DEADTIME, -1); + } + + int64_t getCycles() { + return det.setTimer(slsReceiverDefs::timerIndex::CYCLES_NUMBER, -1); + } + + void setCycles(const int64_t n_cycles) { + det.setTimer(slsReceiverDefs::timerIndex::CYCLES_NUMBER, n_cycles); + } + int64_t getJCTBSamples() { + return det.setTimer(slsReceiverDefs::timerIndex::SAMPLES_JCTB, -1); + } + + void setJCTBSamples(const int64_t n_samples) { + det.setTimer(slsReceiverDefs::timerIndex::SAMPLES_JCTB, n_samples); + } + void setNumberOfMeasurements(const int n_measurements) { + det.setTimer(slsReceiverDefs::timerIndex::MEASUREMENTS_NUMBER, n_measurements); + } + int getNumberOfMeasurements() { + return det.setTimer(slsReceiverDefs::timerIndex::MEASUREMENTS_NUMBER, -1); + } + + int getNumberOfGates() { + return det.setTimer(slsReceiverDefs::timerIndex::GATES_NUMBER, -1); + } + void setNumberOfGates(const int t) { + det.setTimer(slsReceiverDefs::timerIndex::GATES_NUMBER, t); + } + int getNumberOfProbes() { + return det.setTimer(slsReceiverDefs::timerIndex::PROBES_NUMBER, -1); + } + void setNumberOfProbes(const int t) { + det.setTimer(slsReceiverDefs::timerIndex::PROBES_NUMBER, t); + } + //time in ns + int64_t getDelay() { + return det.setTimer(slsReceiverDefs::timerIndex::DELAY_AFTER_TRIGGER, -1); + } + //time in ns + void setDelay(const int64_t t) { + det.setTimer(slsReceiverDefs::timerIndex::DELAY_AFTER_TRIGGER, t); + } + //time in ns + int64_t getPeriod() { + return det.setTimer(slsReceiverDefs::timerIndex::FRAME_PERIOD, -1); + } + //time in ns + void setPeriod(const int64_t t) { + det.setTimer(slsReceiverDefs::timerIndex::FRAME_PERIOD, t); + } + + int64_t getNumberOfFrames() { + return det.setTimer(slsReceiverDefs::timerIndex::FRAME_NUMBER, -1); + } + + void setNumberOfFrames(const int64_t nframes) { + det.setTimer(slsReceiverDefs::timerIndex::FRAME_NUMBER, nframes); + } + + std::string getTimingMode() { + return det.externalCommunicationType(det.setExternalCommunicationMode()); + } + void setTimingMode(const std::string mode) { + det.setExternalCommunicationMode(det.externalCommunicationType(mode)); + } + + void freeSharedMemory() { + det.freeSharedMemory(); + } + + std::vector getDetectorType() { + std::vector detector_type; + for (int i = 0; i < det.getNumberOfDetectors(); ++i) { + detector_type.push_back(det.sgetDetectorsType(i)); + } + return detector_type; + } + + void setFileWrite(bool value) { + det.enableWriteToFile(value); + } + bool getFileWrite() { + return det.enableWriteToFile(-1); + } + + void setFileOverWrite(bool value){ + det.overwriteFile(value); + } + + bool getFileOverWrite(){ + return det.overwriteFile(-1); + } + + + + void setAllTrimbits(int tb) { + det.setAllTrimbits(tb); + } + int getAllTrimbits() { + return det.setAllTrimbits(-1); + } + bool getRxDataStreamStatus() { + return det.enableDataStreamingFromReceiver(); + } + + void setRxDataStreamStatus(bool state) { + det.enableDataStreamingFromReceiver(state); + } + + //Get a network parameter for all detectors, looping over individual detectors + //return a vector of strings + std::vector getNetworkParameter(std::string par_name) { + auto p = networkNameToEnum(par_name); + std::vector par; + for (int i = 0; i < det.getNumberOfDetectors(); ++i) { + par.push_back(getSlsDetector(i)->getNetworkParameter(p)); + } + return par; + } + + //Set network parameter for all modules if det_id == -1 otherwise the module + //specified with det_id. + void setNetworkParameter(std::string par_name, std::string par, const int det_id) { + auto p = networkNameToEnum(par_name); + if (det_id == -1) { + det.setNetworkParameter(p, par); + } else { + getSlsDetector(det_id)->setNetworkParameter(p, par); + } + } + + void configureNetworkParameters() { det.configureMAC(); } + + std::string getLastClientIP() { + return det.getLastClientIP(); + } + std::string getReceiverLastClientIP() { + return det.getReceiverLastClientIP(); + } + + //get frame delay of module (det_id) in ns + int getDelayFrame(const int det_id) { + auto r = getSlsDetector(det_id)->getNetworkParameter(slsDetectorDefs::networkParameter::DETECTOR_TXN_DELAY_FRAME); + return std::stoi(r); + } + //set frame delay of module (det_id) in ns + void setDelayFrame(const int det_id, const int delay) { + auto delay_str = std::to_string(delay); + getSlsDetector(det_id)->setNetworkParameter(slsDetectorDefs::networkParameter::DETECTOR_TXN_DELAY_FRAME, delay_str); + } + + //get delay left of module (det_id) in ns + int getDelayLeft(const int det_id) { + auto r = getSlsDetector(det_id)->getNetworkParameter(slsDetectorDefs::networkParameter::DETECTOR_TXN_DELAY_LEFT); + return std::stoi(r); + } + //set delay left of module (det_id) in ns + void setDelayLeft(const int det_id, const int delay) { + auto delay_str = std::to_string(delay); + getSlsDetector(det_id)->setNetworkParameter(slsDetectorDefs::networkParameter::DETECTOR_TXN_DELAY_LEFT, delay_str); + } + + //get delay right of module (det_id) in ns + int getDelayRight(const int det_id) { + auto r = getSlsDetector(det_id)->getNetworkParameter(slsDetectorDefs::networkParameter::DETECTOR_TXN_DELAY_RIGHT); + return std::stoi(r); + } + + //set delay right of module (det_id) in ns + void setDelayRight(const int det_id, const int delay) { + auto delay_str = std::to_string(delay); + getSlsDetector(det_id)->setNetworkParameter(slsDetectorDefs::networkParameter::DETECTOR_TXN_DELAY_RIGHT, delay_str); + } + + //Check if detector if filling in gap pixels in module + //return true if so, currently only in developer + bool getGapPixels() { + return det.enableGapPixels(-1); + } + + //Set to true to have the detector filling in gap pixels + //false to disable, currently only in developer + void setGapPixels(bool val) { + det.enableGapPixels(val); + } + + slsDetectorDefs::networkParameter networkNameToEnum(std::string par_name); + + private: + multiSlsDetector det; + slsDetector *getSlsDetector(int i) const; + int multi_detector_id = 0; +}; + +slsDetector *Detector::getSlsDetector(int i) const { + //Get a pointer to an slsDetector + //throw an exception to avoid accessing + //a null pointer + auto d = det(i); + if (d) + return d; + else + throw std::runtime_error("Could not get detector: " + std::to_string(i)); +} + + +void Detector::setFileFormat(const std::string& format){ + if (format == "binary"){ + det.setFileFormat(slsReceiverDefs::fileFormat::BINARY); + }else if(format == "ascii"){ + det.setFileFormat(slsReceiverDefs::fileFormat::ASCII); + }else if(format == "hdf5"){ + det.setFileFormat(slsReceiverDefs::fileFormat::HDF5); + } +} + +std::string Detector::getFileFormat(){ + auto format = det.setFileFormat(); + switch (format) + { + case slsDetectorDefs::fileFormat::BINARY: + return "binary"; + case slsDetectorDefs::fileFormat::ASCII: + return "ascii"; + case slsDetectorDefs::fileFormat::HDF5: + return "hdf5"; + default: + return "unknown"; + } +} + +slsDetectorDefs::networkParameter Detector::networkNameToEnum(std::string par_name) { + + if (par_name == "detectormac") { + return slsDetectorDefs::networkParameter::DETECTOR_MAC; + } else if (par_name == "detectorip") { + return slsDetectorDefs::networkParameter::DETECTOR_IP; + } else if (par_name == "rx_hostname") { + return slsDetectorDefs::networkParameter::RECEIVER_HOSTNAME; + } else if (par_name == "rx_udpip") { + return slsDetectorDefs::networkParameter::RECEIVER_UDP_IP; + } else if (par_name == "rx_udpport") { + return slsDetectorDefs::networkParameter::RECEIVER_UDP_PORT; + } else if (par_name == "rx_udpmac") { + return slsDetectorDefs::networkParameter::RECEIVER_UDP_MAC; + } else if (par_name == "rx_udpport2") { + return slsDetectorDefs::networkParameter::RECEIVER_UDP_PORT2; + } else if (par_name == "rx_udpsocksize") { + return slsDetectorDefs::networkParameter::RECEIVER_UDP_SCKT_BUF_SIZE; + } else if (par_name == "rx_realudpsocksize") { + return slsDetectorDefs::networkParameter::RECEIVER_REAL_UDP_SCKT_BUF_SIZE; + } else if (par_name == "rx_jsonaddheader") { + return slsDetectorDefs::networkParameter::ADDITIONAL_JSON_HEADER; + } else if (par_name == "delay_left") { + return slsDetectorDefs::networkParameter::DETECTOR_TXN_DELAY_LEFT; + } else if (par_name == "delay_right") { + return slsDetectorDefs::networkParameter::DETECTOR_TXN_DELAY_RIGHT; + } else if (par_name == "delay_frame") { + return slsDetectorDefs::networkParameter::DETECTOR_TXN_DELAY_FRAME; + } else if (par_name == "flow_control_10g") { + return slsDetectorDefs::networkParameter::FLOW_CONTROL_10G; + } else if (par_name == "client_zmqport") { + return slsDetectorDefs::networkParameter::CLIENT_STREAMING_PORT; + } else if (par_name == "rx_zmqport") { + return slsDetectorDefs::networkParameter::RECEIVER_STREAMING_PORT; + } else if (par_name == "rx_zmqip") { + return slsDetectorDefs::networkParameter::RECEIVER_STREAMING_SRC_IP; + } else if (par_name == "client_zmqip") { + return slsDetectorDefs::networkParameter::CLIENT_STREAMING_SRC_IP; + } + + throw std::runtime_error("Could not decode network parameter"); +}; + +// slsDetectorDefs::fileFormat Detector::file/// + +slsDetectorDefs::dacIndex Detector::dacNameToEnum(std::string dac_name) { + //to avoid uninitialised + slsDetectorDefs::dacIndex dac = slsDetectorDefs::dacIndex::E_SvP; + + if (dac_name == "vsvp") { + dac = slsDetectorDefs::dacIndex::E_SvP; + } else if (dac_name == "vtr") { + dac = slsDetectorDefs::dacIndex::E_Vtr; + } else if (dac_name == "vthreshold") { + dac = slsDetectorDefs::dacIndex::THRESHOLD; + } else if (dac_name == "vrf") { + dac = slsDetectorDefs::dacIndex::E_Vrf; + } else if (dac_name == "vrs") { + dac = slsDetectorDefs::dacIndex::E_Vrs; + } else if (dac_name == "vsvn") { + dac = slsDetectorDefs::dacIndex::E_SvN; + } else if (dac_name == "vtgstv") { + dac = slsDetectorDefs::dacIndex::E_Vtgstv; + } else if (dac_name == "vcmp_ll") { + dac = slsDetectorDefs::dacIndex::E_Vcmp_ll; + } else if (dac_name == "vcmp_lr") { + dac = slsDetectorDefs::dacIndex::E_Vcmp_lr; + } else if (dac_name == "vcall") { + dac = slsDetectorDefs::dacIndex::E_cal; + } else if (dac_name == "vcmp_rl") { + dac = slsDetectorDefs::dacIndex::E_Vcmp_rl; + } else if (dac_name == "rxb_rb") { + dac = slsDetectorDefs::dacIndex::E_rxb_rb; + } else if (dac_name == "rxb_lb") { + dac = slsDetectorDefs::dacIndex::E_rxb_lb; + } else if (dac_name == "vcmp_rr") { + dac = slsDetectorDefs::dacIndex::E_Vcmp_rr; + } else if (dac_name == "vcp") { + dac = slsDetectorDefs::dacIndex::E_Vcp; + } else if (dac_name == "vcn") { + dac = slsDetectorDefs::dacIndex::E_Vcn; + } else if (dac_name == "vis") { + dac = slsDetectorDefs::dacIndex::E_Vis; + } else if (dac_name == "iodelay") { + dac = slsDetectorDefs::dacIndex::IO_DELAY; + } else if (dac_name == "v_a") { + dac = slsDetectorDefs::dacIndex::V_POWER_A; + } else if (dac_name == "v_b") { + dac = slsDetectorDefs::dacIndex::V_POWER_B; + } else if (dac_name == "v_c") { + dac = slsDetectorDefs::dacIndex::V_POWER_C; + } else if (dac_name == "v_d") { + dac = slsDetectorDefs::dacIndex::V_POWER_D; + } else if (dac_name == "v_io") { + dac = slsDetectorDefs::dacIndex::V_POWER_IO; + } else if (dac_name == "v_chip") { + dac = slsDetectorDefs::dacIndex::V_POWER_CHIP; + } else if (dac_name == "v_limit") { + dac = slsDetectorDefs::dacIndex::V_LIMIT; + } else if (dac_name == "temp_fpga") { + dac = slsDetectorDefs::dacIndex::TEMPERATURE_FPGA; + } else if (dac_name == "temp_fpgaext") { + dac = slsDetectorDefs::dacIndex::TEMPERATURE_FPGAEXT; + } else if (dac_name == "temp_10ge") { + dac = slsDetectorDefs::dacIndex::TEMPERATURE_10GE; + } else if (dac_name == "temp_dcdc") { + dac = slsDetectorDefs::dacIndex::TEMPERATURE_DCDC; + } else if (dac_name == "temp_sodl") { + dac = slsDetectorDefs::dacIndex::TEMPERATURE_SODL; + } else if (dac_name == "temp_sodr") { + dac = slsDetectorDefs::dacIndex::TEMPERATURE_SODR; + } else if (dac_name == "temp_fpgafl") { + dac = slsDetectorDefs::dacIndex::TEMPERATURE_FPGA2; + } else if (dac_name == "temp_fpgafr") { + dac = slsDetectorDefs::dacIndex::TEMPERATURE_FPGA3; + } else if (dac_name == "vhighvoltage") { + dac = slsDetectorDefs::dacIndex::HV_NEW; + } else if (dac_name == "vb_comp") { + dac = static_cast(0); + } else if (dac_name == "vdd_prot") { + dac = static_cast(1); + } else if (dac_name == "vin_com") { + dac = static_cast(2); + } else if (dac_name == "vref_prech") { + dac = static_cast(3); + } else if (dac_name == "vb_pixbuff") { + dac = static_cast(4); + } else if (dac_name == "vb_ds") { + dac = static_cast(5); + } else if (dac_name == "vref_ds") { + dac = static_cast(6); + } else if (dac_name == "vref_comp") { + dac = static_cast(7); + } else if (dac_name == "dac0") { + dac = static_cast(0); + } else if (dac_name == "dac1") { + dac = static_cast(1); + } else if (dac_name == "dac2") { + dac = static_cast(2); + } else if (dac_name == "dac3") { + dac = static_cast(3); + } else if (dac_name == "dac4") { + dac = static_cast(4); + } else if (dac_name == "dac5") { + dac = static_cast(5); + } else if (dac_name == "dac6") { + dac = static_cast(6); + } else if (dac_name == "dac7") { + dac = static_cast(7); + } else if (dac_name == "dac8") { + dac = static_cast(8); + } else if (dac_name == "dac9") { + dac = static_cast(9); + } else if (dac_name == "dac10") { + dac = static_cast(10); + } else if (dac_name == "dac11") { + dac = static_cast(11); + } else if (dac_name == "dac12") { + dac = static_cast(12); + } else if (dac_name == "dac13") { + dac = static_cast(13); + } else if (dac_name == "dac14") { + dac = static_cast(14); + } else if (dac_name == "dac15") { + dac = static_cast(15); + } else if (dac_name == "dac16") { + dac = static_cast(16); + } else if (dac_name == "dac17") { + dac = static_cast(17); + } + + return dac; +} + +std::vector Detector::getReadoutFlags() { + std::vector flags; + auto r = det.setReadOutFlags(); + if (r & slsDetectorDefs::readOutFlags::STORE_IN_RAM) + flags.push_back("storeinram"); + if (r & slsDetectorDefs::readOutFlags::TOT_MODE) + flags.push_back("tot"); + if (r & slsDetectorDefs::readOutFlags::CONTINOUS_RO) + flags.push_back("continous"); + if (r & slsDetectorDefs::readOutFlags::PARALLEL) + flags.push_back("parallel"); + if (r & slsDetectorDefs::readOutFlags::NONPARALLEL) + flags.push_back("nonparallel"); + if (r & slsDetectorDefs::readOutFlags::SAFE) + flags.push_back("safe"); + if (r & slsDetectorDefs::readOutFlags::DIGITAL_ONLY) + flags.push_back("digital"); + if (r & slsDetectorDefs::readOutFlags::ANALOG_AND_DIGITAL) + flags.push_back("analog_digital"); + if (r & slsDetectorDefs::readOutFlags::NOOVERFLOW) + flags.push_back("nooverflow"); + if (r & slsDetectorDefs::readOutFlags::SHOW_OVERFLOW) + flags.push_back("overflow"); + return flags; +} + +//note singular +void Detector::setReadoutFlag(const std::string flag_name) { + if (flag_name == "none") + det.setReadOutFlags(slsDetectorDefs::readOutFlags::NORMAL_READOUT); + else if (flag_name == "storeinram") + det.setReadOutFlags(slsDetectorDefs::readOutFlags::STORE_IN_RAM); + else if (flag_name == "tot") + det.setReadOutFlags(slsDetectorDefs::readOutFlags::TOT_MODE); + else if (flag_name == "continous") + det.setReadOutFlags(slsDetectorDefs::readOutFlags::CONTINOUS_RO); + else if (flag_name == "parallel") + det.setReadOutFlags(slsDetectorDefs::readOutFlags::PARALLEL); + else if (flag_name == "nonparallel") + det.setReadOutFlags(slsDetectorDefs::readOutFlags::NONPARALLEL); + else if (flag_name == "safe") + det.setReadOutFlags(slsDetectorDefs::readOutFlags::SAFE); + else if (flag_name == "digital") + det.setReadOutFlags(slsDetectorDefs::readOutFlags::DIGITAL_ONLY); + else if (flag_name == "analog_digital") + det.setReadOutFlags(slsDetectorDefs::readOutFlags::ANALOG_AND_DIGITAL); + else if (flag_name == "nooverflow") + det.setReadOutFlags(slsDetectorDefs::readOutFlags::NOOVERFLOW); + else if (flag_name == "overflow") + det.setReadOutFlags(slsDetectorDefs::readOutFlags::SHOW_OVERFLOW); + else + throw std::runtime_error("Flag name not recognized"); +} + +std::vector Detector::getRateCorrection() { + std::vector rate_corr; + double tmp = 0; + for (int i = 0; i < det.getNumberOfDetectors(); ++i) { + getSlsDetector(i)->getRateCorrection(tmp); + rate_corr.push_back(tmp); + } + return rate_corr; +} + +void Detector::pulseAllPixels(int n) { + // int pulsePixelNMove(int n=0,int x=0,int y=0); + // int pulsePixel(int n=0,int x=0,int y=0); + + for (int j = 0; j < 8; ++j) { + det.pulsePixel(0, -255 + j, 0); + for (int i = 0; i < 256; ++i) { + det.pulsePixelNMove(n, 0, 1); + } + } + return; +} +void Detector::pulseDiagonal(int n) { + // int pulsePixelNMove(int n=0,int x=0,int y=0); + // int pulsePixel(int n=0,int x=0,int y=0); + + for (int j = 20; j < 232; j += 16) { + det.pulsePixel(0, -255, j); + for (int i = 0; i < 8; ++i) { + det.pulsePixelNMove(n, 1, 1); + } + } + return; +} + +#endif // DETECTOR_H diff --git a/python/src/main.cpp b/python/src/main.cpp new file mode 100644 index 000000000..b1bf61c3d --- /dev/null +++ b/python/src/main.cpp @@ -0,0 +1,270 @@ +#include +#include + +#include "Detector.h" + +namespace py = pybind11; + +PYBIND11_MODULE(_sls_detector, m) +{ + m.doc() = R"pbdoc( + C/C++ API + ----------------------- + .. warning :: + + This is the compiled c extension. You probably want to look at the + interface provided by sls instead. + + )pbdoc"; + + py::class_ DetectorApi(m, "DetectorApi", R"pbdoc( + Interface to the multiSlsDetector class through Detector.h These functions + are used by the python classes Eiger and Jungfrau and normally it is better + to use them than to directly access functions here. + + However it is possible to access these functions... + + :: + + #Using the python class + from sls_detector import Eiger + d = Eiger() + d._api.getThresholdEnergy() + + #creating a DetectorApi object (remember to set online flags) + from _sls_detector import DetectorApi + api = DetectorApi(0) + api.setOnline(True) + api.setReceiverOnline(True) + api.getNumberOfFrames() + + #But the Pythonic way is almost alway simpler + d = Eiger() + d.n_frames + >> 10 + + + + + )pbdoc"); + DetectorApi + .def(py::init()) + .def("freeSharedMemory", &Detector::freeSharedMemory) + .def("getMultiDetectorId", &Detector::getMultiDetectorId) + .def("acq", &Detector::acquire) + .def("getAcquiringFlag", &Detector::getAcquiringFlag) + .def("setAcquiringFlag", &Detector::setAcquiringFlag) + + .def("setAllTrimbits", &Detector::setAllTrimbits) + .def("getAllTrimbits", &Detector::getAllTrimbits) + .def("setCounterBit", &Detector::setCounterBit) + .def("getCounterBit", &Detector::getCounterBit) + + .def("getAdc", &Detector::getAdc) + .def("getDac", &Detector::getDac) + .def("getDac_mV", &Detector::getDac_mV) + .def("setDac", &Detector::setDac) + .def("setDac_mV", &Detector::setDac_mV) + .def("getDacFromIndex", &Detector::getDacFromIndex) + .def("setDacFromIndex", &Detector::setDacFromIndex) + + .def("getDbitPipeline", &Detector::getDbitPipeline) + .def("setDbitPipeline", &Detector::setDbitPipeline) + .def("getDbitPhase", &Detector::getDbitPhase) + .def("setDbitPhase", &Detector::setDbitPhase) + .def("getDbitClock", &Detector::getDbitClock) + .def("setDbitClock", &Detector::setDbitClock) + + .def("setThresholdEnergy", &Detector::setThresholdEnergy) + .def("getThresholdEnergy", &Detector::getThresholdEnergy) + + .def("getSettings", &Detector::getSettings) + .def("setSettings", &Detector::setSettings) + .def("getSettingsDir", &Detector::getSettingsDir) + .def("setSettingsDir", &Detector::setSettingsDir) + + .def("loadTrimbitFile", &Detector::loadTrimbitFile) + .def("setTrimEnergies", &Detector::setTrimEnergies) + .def("getTrimEnergies", &Detector::getTrimEnergies) + + .def("pulseChip", &Detector::pulseChip) + .def("pulseAllPixels", &Detector::pulseAllPixels) + .def("pulseDiagonal", &Detector::pulseDiagonal) + .def("getRunStatus", &Detector::getRunStatus) + .def("readConfigurationFile", &Detector::readConfigurationFile) + .def("readParametersFile", &Detector::readParametersFile) + .def("checkOnline", &Detector::checkOnline) + .def("setReadoutClockSpeed", &Detector::setReadoutClockSpeed) + .def("getReadoutClockSpeed", &Detector::getReadoutClockSpeed) + .def("getHostname", &Detector::getHostname) + .def("setHostname", &Detector::setHostname) + + .def("getOnline", &Detector::getOnline) + .def("setOnline", &Detector::setOnline) + .def("getReceiverOnline", &Detector::getReceiverOnline) + .def("setReceiverOnline", &Detector::setReceiverOnline) + + .def("getRxTcpport", &Detector::getRxTcpport) + .def("setRxTcpport", &Detector::setRxTcpport) + + .def("isChipPowered", &Detector::isChipPowered) + .def("powerChip", &Detector::powerChip) + + .def("readRegister", &Detector::readRegister) + .def("writeRegister", &Detector::writeRegister) + .def("writeAdcRegister", &Detector::writeAdcRegister) + .def("setBitInRegister", &Detector::setBitInRegister) + .def("clearBitInRegister", &Detector::clearBitInRegister) + + .def("setDynamicRange", &Detector::setDynamicRange) + .def("getDynamicRange", &Detector::getDynamicRange) + .def("getFirmwareVersion", &Detector::getFirmwareVersion) + .def("getServerVersion", &Detector::getServerVersion) + .def("getClientVersion", &Detector::getClientVersion) + .def("getReceiverVersion", &Detector::getReceiverVersion) + .def("getDetectorNumber", &Detector::getDetectorNumber) + .def("getRateCorrection", &Detector::getRateCorrection) + .def("setRateCorrection", &Detector::setRateCorrection) + + .def("startAcquisition", &Detector::startAcquisition) + .def("stopAcquisition", &Detector::stopAcquisition) + .def("startReceiver", &Detector::startReceiver) + .def("stopReceiver", &Detector::stopReceiver) + + .def("getFilePath", (std::string(Detector::*)()) & Detector::getFilePath, "Using multiSlsDetector") + .def("getFilePath", (std::string(Detector::*)(const int)const) & Detector::getFilePath, "File path for individual detector") + .def("setFilePath", (void (Detector::*)(std::string)) & Detector::setFilePath) + .def("setFilePath", (void (Detector::*)(std::string, const int)) & Detector::setFilePath) + + .def("setFileName", &Detector::setFileName) + .def("getFileName", &Detector::getFileName) + .def("setFileIndex", &Detector::setFileIndex) + .def("getFileIndex", &Detector::getFileIndex) + + .def("setExposureTime", &Detector::setExposureTime) + .def("getExposureTime", &Detector::getExposureTime) + .def("setSubExposureTime", &Detector::setSubExposureTime) + .def("getSubExposureTime", &Detector::getSubExposureTime) + .def("setPeriod", &Detector::setPeriod) + .def("getPeriod", &Detector::getPeriod) + .def("setSubExposureDeadTime", &Detector::setSubExposureDeadTime) + .def("getSubExposureDeadTime", &Detector::getSubExposureDeadTime) + + .def("getCycles", &Detector::getCycles) + .def("setCycles", &Detector::setCycles) + .def("setNumberOfMeasurements", &Detector::setNumberOfMeasurements) + .def("getNumberOfMeasurements", &Detector::getNumberOfMeasurements) + .def("getNumberOfGates", &Detector::getNumberOfGates) + .def("setNumberOfGates", &Detector::setNumberOfGates) + .def("getDelay", &Detector::getDelay) + .def("setDelay", &Detector::setDelay) + .def("getJCTBSamples", &Detector::getJCTBSamples) + .def("setJCTBSamples", &Detector::setJCTBSamples) + + .def("setStoragecellStart", &Detector::setStoragecellStart) + .def("getStoragecellStart", &Detector::getStoragecellStart) + .def("setNumberOfStorageCells", &Detector::setNumberOfStorageCells) + .def("getNumberOfStorageCells", &Detector::getNumberOfStorageCells) + + .def("getTimingMode", &Detector::getTimingMode) + .def("setTimingMode", &Detector::setTimingMode) + + .def("getDetectorType", &Detector::getDetectorType) + + .def("setThresholdTemperature", &Detector::setThresholdTemperature) + .def("getThresholdTemperature", &Detector::getThresholdTemperature) + .def("setTemperatureControl", &Detector::setTemperatureControl) + .def("getTemperatureControl", &Detector::getTemperatureControl) + .def("getTemperatureEvent", &Detector::getTemperatureEvent) + .def("resetTemperatureEvent", &Detector::resetTemperatureEvent) + + .def("getRxDataStreamStatus", &Detector::getRxDataStreamStatus) + .def("setRxDataStreamStatus", &Detector::setRxDataStreamStatus) + + .def("getNetworkParameter", &Detector::getNetworkParameter) + .def("setNetworkParameter", &Detector::setNetworkParameter) + .def("configureNetworkParameters", &Detector::configureNetworkParameters) + .def("getDelayFrame", &Detector::getDelayFrame) + .def("setDelayFrame", &Detector::setDelayFrame) + .def("getDelayLeft", &Detector::getDelayLeft) + .def("setDelayLeft", &Detector::setDelayLeft) + .def("getDelayRight", &Detector::getDelayRight) + .def("setDelayRight", &Detector::setDelayRight) + .def("getLastClientIP", &Detector::getLastClientIP) + .def("getReceiverLastClientIP", &Detector::getReceiverLastClientIP) + + .def("setReceiverFramesPerFile", &Detector::setReceiverFramesPerFile) + .def("getReceiverFramesPerFile", &Detector::getReceiverFramesPerFile) + .def("setReceiverFifoDepth", &Detector::setReceiverFifoDepth) + .def("getReceiverFifoDepth", &Detector::getReceiverFifoDepth) + + .def("getReceiverFrameDiscardPolicy", &Detector::getReceiverFrameDiscardPolicy) + .def("setReceiverFramesDiscardPolicy", &Detector::setReceiverFramesDiscardPolicy) + .def("setReceiverPartialFramesPadding", &Detector::setReceiverPartialFramesPadding) + .def("getReceiverPartialFramesPadding", &Detector::getReceiverPartialFramesPadding) + + .def("getUserDetails", &Detector::getUserDetails) + .def("isClientAndDetecorCompatible", &Detector::isClientAndDetecorCompatible) + .def("isClientAndReceiverCompatible", &Detector::isClientAndReceiverCompatible) + .def("getMeasuredPeriod", &Detector::getMeasuredPeriod) + .def("getMeasuredSubPeriod", &Detector::getMeasuredSubPeriod) + + + .def("setFileWrite", &Detector::setFileWrite) + .def("getFileWrite", &Detector::getFileWrite) + .def("setFileOverWrite", &Detector::setFileOverWrite) + .def("getFileOverWrite", &Detector::getFileOverWrite) + .def("getDacVthreshold", &Detector::getDacVthreshold) + .def("setDacVthreshold", &Detector::setDacVthreshold) + .def("setNumberOfFrames", &Detector::setNumberOfFrames) + .def("getNumberOfFrames", &Detector::getNumberOfFrames) + + //Overloaded calls + .def("getFramesCaughtByReceiver", (int (Detector::*)() ) & Detector::getFramesCaughtByReceiver) + .def("getFramesCaughtByReceiver", (int (Detector::*)(int) const) & Detector::getFramesCaughtByReceiver) + + .def("resetFramesCaught", &Detector::resetFramesCaught) + .def("getReceiverCurrentFrameIndex", &Detector::getReceiverCurrentFrameIndex) + .def("getGapPixels", &Detector::getGapPixels) + .def("setGapPixels", &Detector::setGapPixels) + + .def("clearErrorMask", &Detector::clearErrorMask) + .def("getErrorMask", &Detector::getErrorMask) + .def("setErrorMask", &Detector::setErrorMask) + .def("getErrorMessage", &Detector::getErrorMessage) + + .def("getFlippedDataX", &Detector::getFlippedDataX) + .def("getFlippedDataY", &Detector::getFlippedDataY) + .def("setFlippedDataX", &Detector::setFlippedDataX) + .def("setFlippedDataY", &Detector::setFlippedDataY) + + .def("getServerLock", &Detector::getServerLock) + .def("setServerLock", &Detector::setServerLock) + .def("getReceiverLock", &Detector::getReceiverLock) + .def("setReceiverLock", &Detector::setReceiverLock) + + .def("getReadoutFlags", &Detector::getReadoutFlags) + .def("setReadoutFlag", &Detector::setReadoutFlag) + + .def("setFileFormat", &Detector::setFileFormat) + .def("getFileFormat", &Detector::getFileFormat) + + .def("getActive", &Detector::getActive) + .def("setActive", &Detector::setActive) + .def("getThreadedProcessing", &Detector::getThreadedProcessing) + .def("setThreadedProcessing", &Detector::setThreadedProcessing) + + .def("getTenGigabitEthernet", &Detector::getTenGigabitEthernet) + .def("setTenGigabitEthernet", &Detector::setTenGigabitEthernet) + + .def("getImageSize", &Detector::getImageSize) + .def("setImageSize", &Detector::setImageSize) + .def("getNumberOfDetectors", &Detector::getNumberOfDetectors) + .def("getDetectorGeometry", &Detector::getDetectorGeometry); + +#ifdef VERSION_INFO + m.attr("__version__") = VERSION_INFO; +#else + m.attr("__version__") = "dev"; +#endif +} diff --git a/python/unit-tests/__pycache__/dac_test.cpython-37-PYTEST.pyc b/python/unit-tests/__pycache__/dac_test.cpython-37-PYTEST.pyc new file mode 100644 index 000000000..cb42d5d0f Binary files /dev/null and b/python/unit-tests/__pycache__/dac_test.cpython-37-PYTEST.pyc differ diff --git a/python/unit-tests/__pycache__/detector_test.cpython-37-PYTEST.pyc b/python/unit-tests/__pycache__/detector_test.cpython-37-PYTEST.pyc new file mode 100644 index 000000000..21d549630 Binary files /dev/null and b/python/unit-tests/__pycache__/detector_test.cpython-37-PYTEST.pyc differ diff --git a/python/unit-tests/__pycache__/test_detector_property.cpython-37-PYTEST.pyc b/python/unit-tests/__pycache__/test_detector_property.cpython-37-PYTEST.pyc new file mode 100644 index 000000000..c2f3580c0 Binary files /dev/null and b/python/unit-tests/__pycache__/test_detector_property.cpython-37-PYTEST.pyc differ diff --git a/python/unit-tests/__pycache__/test_utils.cpython-37-PYTEST.pyc b/python/unit-tests/__pycache__/test_utils.cpython-37-PYTEST.pyc new file mode 100644 index 000000000..845dc2c2b Binary files /dev/null and b/python/unit-tests/__pycache__/test_utils.cpython-37-PYTEST.pyc differ diff --git a/python/unit-tests/dac_test.py b/python/unit-tests/dac_test.py new file mode 100644 index 000000000..90341fcaa --- /dev/null +++ b/python/unit-tests/dac_test.py @@ -0,0 +1,173 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +Testing setting and getting dacs from the detector +""" +from unittest.mock import Mock, call +import pytest +from pytest_mock import mocker +import numpy as np + +from sls_detector import Eiger +from sls_detector import DetectorApi + + +def test_get_vrf_for_three_mod(mocker): + m2= mocker.patch.object(DetectorApi, 'getNumberOfDetectors', autospec=True) + m2.return_value = 3 + m = mocker.patch.object(DetectorApi, 'getDac', autospec=True) + m.return_value = 1560 + d = Eiger() + vrf = d.dacs.vrf[:] + assert vrf == [1560, 1560, 1560] + +def test_set_vrf_for_three_mod_same_value(mocker): + m2= mocker.patch.object(DetectorApi, 'getNumberOfDetectors', autospec=True) + m2.return_value = 3 + m = mocker.patch.object(DetectorApi, 'setDac', autospec=True) +# m.return_value = 1560 + d = Eiger() + d.dacs.vrf[:] = 1500 + calls = [call('vrf', 0, 1500), call('vrf', 1, 1500), call('vrf', 2, 1500)] + m.assert_has_calls(calls) + assert m.call_count == 3 + +def test_set_vrf_for_four_mod_different_value(mocker): + m2= mocker.patch.object(DetectorApi, 'getNumberOfDetectors', autospec=True) + m2.return_value = 4 + m = mocker.patch.object(DetectorApi, 'setDac', autospec=True) +# m.return_value = 1560 + d = Eiger() + d.dacs.vrf = [1500, 1600, 1800, 1502] + calls = [call('vrf', 0, 1500), + call('vrf', 1, 1600), + call('vrf', 2, 1800), + call('vrf', 3, 1502)] + m.assert_has_calls(calls) + assert m.call_count == 4 + +def test_set_vrf_for_four_mod_different_value_slice(mocker): + m2= mocker.patch.object(DetectorApi, 'getNumberOfDetectors', autospec=True) + m2.return_value = 4 + m = mocker.patch.object(DetectorApi, 'setDac', autospec=True) +# m.return_value = 1560 + d = Eiger() + d.dacs.vrf[:] = [1500, 1600, 1800, 1502] + calls = [call('vrf', 0, 1500), + call('vrf', 1, 1600), + call('vrf', 2, 1800), + call('vrf', 3, 1502)] + m.assert_has_calls(calls) + assert m.call_count == 4 + +def test_set_vcp_single_call(mocker): + m2= mocker.patch.object(DetectorApi, 'getNumberOfDetectors', autospec=True) + m2.return_value = 2 + m = mocker.patch.object(DetectorApi, 'setDac', autospec=True) +# m.return_value = 1560 + d = Eiger() + d.dacs.vcp[1] = 1637 + m.assert_called_once_with('vcp', 1, 1637) + +def test_iterate_on_index_call_vcn(mocker): + m2= mocker.patch.object(DetectorApi, 'getNumberOfDetectors', autospec=True) + m2.return_value = 10 + m = mocker.patch.object(DetectorApi, 'setDac', autospec=True) +# m.return_value = 1560 + d = Eiger() + d.dacs.vcn[0,3,8] = 1532 + calls = [call('vcn', 0, 1532), + call('vcn', 3, 1532), + call('vcn', 8, 1532)] + m.assert_has_calls(calls) + assert m.call_count == 3 + +def test_set_dac_from_element_in_numpy_array(mocker): + m2= mocker.patch.object(DetectorApi, 'getNumberOfDetectors', autospec=True) + m2.return_value = 2 + m = mocker.patch.object(DetectorApi, 'setDac', autospec=True) + d = Eiger() + + vrf = np.array((1600,1700,1800)) + d.dacs.vrf = vrf[0] + calls = [call('vrf', 0, 1600), + call('vrf', 1, 1600),] + m.assert_has_calls(calls) + assert m.call_count == 2 + +def test_set_dac_from_element_in_numpy_array_using_slice(mocker): + m2= mocker.patch.object(DetectorApi, 'getNumberOfDetectors', autospec=True) + m2.return_value = 2 + m = mocker.patch.object(DetectorApi, 'setDac', autospec=True) + d = Eiger() + + vrf = np.array((1600,1700,1800)) + d.dacs.vrf[:] = vrf[0] + calls = [call('vrf', 0, 1600), + call('vrf', 1, 1600),] + m.assert_has_calls(calls) + assert m.call_count == 2 + +def test_set_eiger_default(mocker): + m2= mocker.patch.object(DetectorApi, 'getNumberOfDetectors', autospec=True) + m2.return_value = 2 + m = mocker.patch.object(DetectorApi, 'setDac', autospec=True) +# m.return_value = 1560 + d = Eiger() + d.dacs.set_default() + calls = [call('vsvp', 0, 0), + call('vsvp', 1, 0), + call('vtr', 0, 2500), + call('vtr', 1, 2500), + call('vrf', 0, 3300), + call('vrf', 1, 3300), + call('vrs', 0, 1400), + call('vrs', 1, 1400), + call('vsvn', 0, 4000), + call('vsvn', 1, 4000), + call('vtgstv', 0, 2556), + call('vtgstv', 1, 2556), + call('vcmp_ll', 0, 1500), + call('vcmp_ll', 1, 1500), + call('vcmp_lr', 0, 1500), + call('vcmp_lr', 1, 1500), + call('vcall', 0, 4000), + call('vcall', 1, 4000), + call('vcmp_rl', 0, 1500), + call('vcmp_rl', 1, 1500), + call('rxb_rb', 0, 1100), + call('rxb_rb', 1, 1100), + call('rxb_lb', 0, 1100), + call('rxb_lb', 1, 1100), + call('vcmp_rr', 0, 1500), + call('vcmp_rr', 1, 1500), + call('vcp', 0, 200), + call('vcp', 1, 200), + call('vcn', 0, 2000), + call('vcn', 1, 2000), + call('vis', 0, 1550), + call('vis', 1, 1550), + call('iodelay', 0, 660), + call('iodelay', 1, 660)] + + m.assert_has_calls(calls) + assert m.call_count == 17*2 + +def test_set_eiger_set_from_array_call_count(mocker): + import numpy as np + m2= mocker.patch.object(DetectorApi, 'getNumberOfDetectors', autospec=True) + m2.return_value = 3 + m = mocker.patch.object(DetectorApi, 'setDac', autospec=True) +# m.return_value = 1560 + d = Eiger() + d.dacs.set_from_array( np.zeros((17,3))) + assert m.call_count == 17*3 + +def test_get_fpga_temp(mocker): + m2= mocker.patch.object(DetectorApi, 'getNumberOfDetectors', autospec=True) + m2.return_value = 2 + m = mocker.patch.object(DetectorApi, 'getAdc', autospec=True) + m.return_value = 34253 + d = Eiger() + t = d.temp.fpga[:] + assert t == [34.253, 34.253] \ No newline at end of file diff --git a/python/unit-tests/detector_eiger.py b/python/unit-tests/detector_eiger.py new file mode 100644 index 000000000..e17127636 --- /dev/null +++ b/python/unit-tests/detector_eiger.py @@ -0,0 +1,489 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +Testing parameters and methods of the Detector class using mocks +""" +from unittest.mock import Mock +import pytest +from pytest_mock import mocker + + + +@pytest.fixture +def d(): + from sls_detector import Eiger + return Eiger() + + +def test_acq_call(d, mocker): + m = mocker.patch('_sls_detector.DetectorApi.acq') + d.acq() + m.assert_called_once_with() + +def test_busy_call(d, mocker): + m = mocker.patch('_sls_detector.DetectorApi.getAcquiringFlag') + m.return_value = False + assert d.busy == False + + +def test_assign_to_detector_type(d): + with pytest.raises(AttributeError): + d.detector_type = 'Eiger' + +def test_det_type(d, mocker): + m = mocker.patch('_sls_detector.DetectorApi.getDetectorType') + m.return_value = 'Eiger' + assert d.detector_type == 'Eiger' + +def test_set_dynamic_range_4(d, mocker): + m = mocker.patch('_sls_detector.DetectorApi.setDynamicRange') + d.dynamic_range = 4 + m.assert_called_with(4) + +def test_set_dynamic_range_8(d, mocker): + m = mocker.patch('_sls_detector.DetectorApi.setDynamicRange') + d.dynamic_range = 8 + m.assert_called_with(8) + + +def test_set_dynamic_range_16(d, mocker): + m = mocker.patch('_sls_detector.DetectorApi.setDynamicRange') + d.dynamic_range = 16 + m.assert_called_with(16) + +def test_set_dynamic_range_32(d, mocker): + m = mocker.patch('_sls_detector.DetectorApi.setDynamicRange') + d.dynamic_range = 32 + m.assert_called_with(32) + +def test_set_dynamic_range_raises_exception(d, mocker): + mocker.patch('_sls_detector.DetectorApi.setDynamicRange') + with pytest.raises(ValueError): + d.dynamic_range = 17 + +def test_get_dynamic_range_32(d, mocker): + m = mocker.patch('_sls_detector.DetectorApi.getDynamicRange') + m.return_value = 32 + dr = d.dynamic_range + assert dr == 32 + +def test_eiger_matrix_reset(d, mocker): + m = mocker.patch('_sls_detector.DetectorApi.getCounterBit') + m.return_value = True + assert d.eiger_matrix_reset == True + +def test_set_eiger_matrix_reset(d, mocker): + m = mocker.patch('_sls_detector.DetectorApi.setCounterBit') + d.eiger_matrix_reset = True + m.assert_called_once_with(True) + + +def test_get_exposure_time(d, mocker): + m = mocker.patch('_sls_detector.DetectorApi.getExposureTime') + m.return_value = 100000000 + assert d.exposure_time == 0.1 + +def test_set_exposure_time(d, mocker): + m = mocker.patch('_sls_detector.DetectorApi.setExposureTime') + d.exposure_time = 1.5 + m.assert_called_once_with(1500000000) + +def test_set_exposure_time_less_than_zero(d, mocker): + mocker.patch('_sls_detector.DetectorApi.setExposureTime') + with pytest.raises(ValueError): + d.exposure_time = -7 + + +def test_get_file_index(d, mocker): + m = mocker.patch('_sls_detector.DetectorApi.getFileIndex') + m.return_value = 8 + assert d.file_index == 8 + +def test_set_file_index(d, mocker): + m = mocker.patch('_sls_detector.DetectorApi.setFileIndex') + d.file_index = 9 + m.assert_called_with(9) + + +def test_set_file_index_raises_on_neg(d, mocker): + mocker.patch('_sls_detector.DetectorApi.setFileIndex') + with pytest.raises(ValueError): + d.file_index = -9 + + +def test_get_file_name(d, mocker): + m = mocker.patch('_sls_detector.DetectorApi.getFileName') + d.file_name + m.assert_called_once_with() + +def test_set_file_name(d, mocker): + m = mocker.patch('_sls_detector.DetectorApi.setFileName') + d.file_name = 'hej' + m.assert_called_once_with('hej') + +def test_get_file_path(d, mocker): + m = mocker.patch('_sls_detector.DetectorApi.getFilePath') + d.file_path + m.assert_called_once_with() + +def test_set_file_path_when_path_exists(d, mocker): + m = mocker.patch('_sls_detector.DetectorApi.setFilePath') + #To avoid raising an exception because path is not there + mock_os = mocker.patch('os.path.exists') + mock_os.return_value = True + d.file_path = '/path/to/something/' + m.assert_called_once_with('/path/to/something/') + +def test_set_file_path_raises_when_not_exists(d, mocker): + mocker.patch('_sls_detector.DetectorApi.setFilePath') + mock_os = mocker.patch('os.path.exists') + mock_os.return_value = False + with pytest.raises(FileNotFoundError): + d.file_path = '/path/to/something/' + +def test_get_file_write(d, mocker): + m = mocker.patch('_sls_detector.DetectorApi.getFileWrite') + m.return_value = False + assert d.file_write == False + +def test_set_file_write(d, mocker): + m = mocker.patch('_sls_detector.DetectorApi.setFileWrite') + d.file_write = True + m.assert_called_once_with(True) + + +def test_get_firmware_version(d, mocker): + m = mocker.patch('_sls_detector.DetectorApi.getFirmwareVersion') + m.return_value = 20 + assert d.firmware_version == 20 + +def test_cannot_set_fw_version(d): + with pytest.raises(AttributeError): + d.firmware_version = 20 + +def test_get_high_voltage_call_signature(d, mocker): + m = mocker.patch('_sls_detector.DetectorApi.getDac') + d.high_voltage + m.assert_called_once_with('vhighvoltage', -1) + +def test_get_high_voltage(d, mocker): + m = mocker.patch('_sls_detector.DetectorApi.getDac') + m.return_value = 80 + assert d.high_voltage == 80 + +#self._api.setDac('vhighvoltage', -1, voltage) +def test_set_high_voltage(d, mocker): + m = mocker.patch('_sls_detector.DetectorApi.setDac') + d.high_voltage = 80 + m.assert_called_once_with('vhighvoltage', -1, 80) + +def test_decode_hostname_two_names(d, mocker): + m = mocker.patch('_sls_detector.DetectorApi.getHostname') + m.return_value = 'beb059+beb048+' + assert d.hostname == ['beb059', 'beb048'] + +def test_decode_hostname_four_names(d, mocker): + m = mocker.patch('_sls_detector.DetectorApi.getHostname') + m.return_value = 'beb059+beb048+beb120+beb153+' + assert d.hostname == ['beb059', 'beb048', 'beb120', 'beb153'] + +def test_decode_hostname_blank(d, mocker): + m = mocker.patch('_sls_detector.DetectorApi.getHostname') + m.return_value = '' + assert d.hostname == [] + +def test_get_image_size_gives_correct_size(d, mocker): + m = mocker.patch('_sls_detector.DetectorApi.getImageSize') + m.return_value = (512,1024) + im_size = d.image_size + assert im_size.rows == 512 + assert im_size.cols == 1024 + + + +def test_load_config(d, mocker): + m = mocker.patch('_sls_detector.DetectorApi.readConfigurationFile') + #To avoid raising an exception because path is not there + mock_os = mocker.patch('os.path.isfile') + mock_os.return_value = True + d.load_config('/path/to/my/file.config') + m.assert_called_once_with('/path/to/my/file.config') + +def test_load_config_raises_when_file_is_not_found(d, mocker): + mocker.patch('_sls_detector.DetectorApi.readConfigurationFile') + mock_os = mocker.patch('os.path.isfile') + mock_os.return_value = False + with pytest.raises(FileNotFoundError): + d.load_config('/path/to/my/file.config') + +def test_load_parameters(d, mocker): + m = mocker.patch('_sls_detector.DetectorApi.readParametersFile') + #To avoid raising an exception because path is not there + mock_os = mocker.patch('os.path.isfile') + mock_os.return_value = True + d.load_parameters('/path/to/my/file.par') + m.assert_called_once_with('/path/to/my/file.par') + +def test_load_parameters_raises_when_file_is_not_found(d, mocker): + mocker.patch('_sls_detector.DetectorApi.readParametersFile') + mock_os = mocker.patch('os.path.isfile') + mock_os.return_value = False + with pytest.raises(FileNotFoundError): + d.load_parameters('/path/to/my/file.par') + +#getDetectorGeometry +def test_get_module_geometry_gives_correct_size(d, mocker): + m = mocker.patch('_sls_detector.DetectorApi.getDetectorGeometry') + m.return_value = (13,7) + g = d.module_geometry + assert g.vertical == 7 + assert g.horizontal == 13 + +def test_get_module_geometry_access(d, mocker): + m = mocker.patch('_sls_detector.DetectorApi.getDetectorGeometry') + m.return_value = (12,3) + assert d.module_geometry[0] == 12 + assert d.module_geometry[1] == 3 + assert d.module_geometry.vertical == 3 + assert d.module_geometry.horizontal == 12 + +def test_get_n_frames(d, mocker): + m = mocker.patch('_sls_detector.DetectorApi.getNumberOfFrames') + m.return_value = 3 + assert d.n_frames == 3 + +def test_set_n_frames(d, mocker): + m = mocker.patch('_sls_detector.DetectorApi.setNumberOfFrames') + d.n_frames = 9 + m.assert_called_once_with(9) + +def test_set_n_frames_raises_on_neg(d, mocker): + mocker.patch('_sls_detector.DetectorApi.setNumberOfFrames') + with pytest.raises(ValueError): + d.n_frames = -1 + +def test_set_n_frames_raises_on_zero(d, mocker): + mocker.patch('_sls_detector.DetectorApi.setNumberOfFrames') + with pytest.raises(ValueError): + d.n_frames = 0 + +def test_get_n_modules(d, mocker): + m = mocker.patch('_sls_detector.DetectorApi.getNumberOfDetectors') + m.return_value = 12 + assert d.n_modules == 12 + +def test_get_period_time(d, mocker): + m = mocker.patch('_sls_detector.DetectorApi.getPeriod') + m.return_value = 130000000 + assert d.period == 0.13 + +def test_set_period_time(d, mocker): + m = mocker.patch('_sls_detector.DetectorApi.setPeriod') + d.period = 1.953 + m.assert_called_once_with(1953000000) + +def test_set_period_time_less_than_zero(d, mocker): + mocker.patch('_sls_detector.DetectorApi.setPeriod') + with pytest.raises(ValueError): + d.period = -7 + +def test_pulse_chip_call(d, mocker): + m = mocker.patch('_sls_detector.DetectorApi.pulseChip') + d.pulse_chip(15) + m.assert_called_once_with(15) + +def test_pulse_chip_call_minus_one(d, mocker): + m = mocker.patch('_sls_detector.DetectorApi.pulseChip') + d.pulse_chip(-1) + m.assert_called_once_with(-1) + +def test_pulse_chip_asserts_on_smaller_than_minus_one(d, mocker): + mocker.patch('_sls_detector.DetectorApi.pulseChip') + with pytest.raises(ValueError): + d.pulse_chip(-3) +#--------------------------------------------------------------------subexptime +def test_get_sub_exposure_time(d, mocker): + m = mocker.patch('_sls_detector.DetectorApi.getSubExposureTime') + m.return_value = 2370000 + assert d.sub_exposure_time == 0.00237 + + +def test_set_sub_exposure_time(d, mocker): + m = mocker.patch('_sls_detector.DetectorApi.setSubExposureTime') + d.sub_exposure_time = 0.002 + m.assert_called_once_with(2000000) + +def test_set_sub_exposure_time_raises_on_zero(d, mocker): + mocker.patch('_sls_detector.DetectorApi.setSubExposureTime') + with pytest.raises(ValueError): + d.sub_exposure_time = 0 + +#-------------------------------------------------------------Rate correction +def test_get_rate_correction(d, mocker): + m = mocker.patch('_sls_detector.DetectorApi.getRateCorrection') + m.return_value = [132,129] + assert d.rate_correction == [132,129] + +def test_set_rate_correction(d, mocker): + m = mocker.patch('_sls_detector.DetectorApi.setRateCorrection') + mock_n = mocker.patch('_sls_detector.DetectorApi.getNumberOfDetectors') + mock_n.return_value = 3 + d.rate_correction = [123,90,50] + m.assert_called_once_with([123,90,50]) + +def test_set_rate_correction_raises_on_wrong_number_of_values(d, mocker): + mocker.patch('_sls_detector.DetectorApi.setRateCorrection') + mock_n = mocker.patch('_sls_detector.DetectorApi.getNumberOfDetectors') + mock_n.return_value = 4 + with pytest.raises(ValueError): + d.rate_correction = [123,90,50] + +#----------------------------------------------------------------Readout clock +def test_get_readout_clock_0(d, mocker): + m = mocker.patch('_sls_detector.DetectorApi.getReadoutClockSpeed') + m.return_value = 0 + assert d.readout_clock == 'Full Speed' + +def test_get_readout_clock_1(d, mocker): + m = mocker.patch('_sls_detector.DetectorApi.getReadoutClockSpeed') + m.return_value = 1 + assert d.readout_clock == 'Half Speed' + +def test_get_readout_clock_2(d, mocker): + m = mocker.patch('_sls_detector.DetectorApi.getReadoutClockSpeed') + m.return_value = 2 + assert d.readout_clock == 'Quarter Speed' + +def test_get_readout_clock_3(d, mocker): + m = mocker.patch('_sls_detector.DetectorApi.getReadoutClockSpeed') + m.return_value = 3 + assert d.readout_clock == 'Super Slow Speed' + +def test_set_readout_clock_0(d, mocker): + m = mocker.patch('_sls_detector.DetectorApi.setReadoutClockSpeed') + d.readout_clock = 'Full Speed' + m.assert_called_once_with(0) + +def test_set_readout_clock_1(d, mocker): + m = mocker.patch('_sls_detector.DetectorApi.setReadoutClockSpeed') + d.readout_clock = 'Half Speed' + m.assert_called_once_with(1) + +def test_set_readout_clock_2(d, mocker): + m = mocker.patch('_sls_detector.DetectorApi.setReadoutClockSpeed') + d.readout_clock = 'Quarter Speed' + m.assert_called_once_with(2) + +def test_set_readout_clock_3(d, mocker): + m = mocker.patch('_sls_detector.DetectorApi.setReadoutClockSpeed') + d.readout_clock = 'Super Slow Speed' + m.assert_called_once_with(3) + +#----------------------------------------------------------------rx_datastream +def test_get_rx_datastream(d, mocker): + m = mocker.patch('_sls_detector.DetectorApi.getRxDataStreamStatus') + m.return_value = False + assert d.rx_datastream == False + +def test_set_rx_datastream(d, mocker): + m = mocker.patch('_sls_detector.DetectorApi.setRxDataStreamStatus') + d.rx_datastream = True + m.assert_called_once_with(True) + +def test_get_rx_zmqip(d, mocker): + m = mocker.patch('_sls_detector.DetectorApi.getNetworkParameter') + d.rx_zmqip + m.assert_called_once_with('rx_zmqip') + +def test_get_rx_zmqport_call(d, mocker): + m = mocker.patch('_sls_detector.DetectorApi.getNetworkParameter') + d.rx_zmqport + m.assert_called_once_with('rx_zmqport') + +def test_get_rx_zmqport_decode(d, mocker): + m = mocker.patch('_sls_detector.DetectorApi.getNetworkParameter') + m.return_value = '30001+30003+' + assert d.rx_zmqport == [30001, 30002, 30003, 30004] + +def test_get_rx_zmqport_empty(d, mocker): + m = mocker.patch('_sls_detector.DetectorApi.getNetworkParameter') + m.return_value = '' + assert d.rx_zmqport == [] + + +#--------------------------------------------------------------------status +def test_status_call(d, mocker): + m = mocker.patch('_sls_detector.DetectorApi.getRunStatus') + d.status + m.assert_called_once_with() + +def test_start_acq_call(d, mocker): + m = mocker.patch('_sls_detector.DetectorApi.startAcquisition') + d.start_acq() + m.assert_called_once_with() + +def test_stop_acq_call(d, mocker): + m = mocker.patch('_sls_detector.DetectorApi.stopAcquisition') + d.stop_acq() + m.assert_called_once_with() + +#--------------------------------------------------------------------subexptime +def test_get_sub_exposure_time(d, mocker): + m = mocker.patch('_sls_detector.DetectorApi.getSubExposureTime') + m.return_value = 2370000 + assert d.sub_exposure_time == 0.00237 + + +def test_set_sub_exposure_time(d, mocker): + m = mocker.patch('_sls_detector.DetectorApi.setSubExposureTime') + d.sub_exposure_time = 0.002 + m.assert_called_once_with(2000000) + +def test_set_sub_exposure_time_raises_on_zero(d, mocker): + mocker.patch('_sls_detector.DetectorApi.setSubExposureTime') + with pytest.raises(ValueError): + d.sub_exposure_time = 0 + +#------------------------------------------------------------------timing mode +def test_get_timing_mode(d, mocker): + m = mocker.patch('_sls_detector.DetectorApi.getTimingMode') + d.timing_mode + m.assert_called_once_with() + +def test_set_timing_mode(d, mocker): + m = mocker.patch('_sls_detector.DetectorApi.setTimingMode') + d.timing_mode = 'auto' + m.assert_called_once_with('auto') + +#----------------------------------------------------------------vthreshold +def test_get_vthreshold(d, mocker): + m = mocker.patch('_sls_detector.DetectorApi.getDac') + d.vthreshold + m.assert_called_once_with('vthreshold', -1) + +def test_set_vthreshold(d, mocker): + m = mocker.patch('_sls_detector.DetectorApi.setDac') + d.vthreshold = 1675 + m.assert_called_once_with('vthreshold', -1, 1675) + +#----------------------------------------------------------------trimbits +def test_get_trimbits(d, mocker): + m = mocker.patch('_sls_detector.DetectorApi.getAllTrimbits') + d.trimbits + m.assert_called_once_with() + +def test_set_trimbits(d, mocker): + m = mocker.patch('_sls_detector.DetectorApi.setAllTrimbits') + d.trimbits = 15 + m.assert_called_once_with(15) + +def test_set_trimbits_raises_outside_range(d, mocker): + mocker.patch('_sls_detector.DetectorApi.setAllTrimbits') + + with pytest.raises(ValueError): + d.trimbits = 69 + + with pytest.raises(ValueError): + d.trimbits = -5 + + diff --git a/python/unit-tests/detector_test.py b/python/unit-tests/detector_test.py new file mode 100644 index 000000000..55de8c801 --- /dev/null +++ b/python/unit-tests/detector_test.py @@ -0,0 +1,472 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +Testing parameters and methods of the Detector class using mocks +""" +from unittest.mock import Mock +import pytest +from pytest_mock import mocker + +import sys +sys.path.append('/home/l_frojdh/slsdetectorgrup/sls_detector') + +import _sls_detector +from sls_detector.errors import DetectorValueError, DetectorError +from sls_detector.utils import all_equal, element_if_equal +@pytest.fixture +def d(): + from sls_detector import Detector + return Detector() + + +def test_length_zero(d): + assert len(d) == 0 + +def test_acq_call(d, mocker): + m = mocker.patch('_sls_detector.DetectorApi.acq') + d.acq() + m.assert_called_once_with() + + + +def test_busy_call(d, mocker): + m = mocker.patch('_sls_detector.DetectorApi.getAcquiringFlag') + m.return_value = False + assert d.busy == False + +def test_set_busy(d): + d.busy = True + assert d.busy == True + assert d._api.getAcquiringFlag() == True + d.busy = False + assert d.busy == False + assert d._api.getAcquiringFlag() == False + +def test_error_mask(d): + d._api.setErrorMask(1) + assert d.error_mask == 1 + d.clear_errors() + +def test_error_handling(d): + with pytest.raises(DetectorError): + d._provoke_error() + +def test_assign_to_detector_type(d): + with pytest.raises(AttributeError): + d.detector_type = 'Eiger' + +def test_det_type(d, mocker): + m = mocker.patch('_sls_detector.DetectorApi.getDetectorType') + m.return_value = 'Eiger' + assert d.detector_type == 'Eiger' + + +def test_get_exposure_time(d, mocker): + m = mocker.patch('_sls_detector.DetectorApi.getExposureTime') + m.return_value = 100000000 + assert d.exposure_time == 0.1 + +def test_set_exposure_time(d, mocker): + m = mocker.patch('_sls_detector.DetectorApi.setExposureTime') + d.exposure_time = 1.5 + m.assert_called_once_with(1500000000) + +def test_set_exposure_time_less_than_zero(d, mocker): + mocker.patch('_sls_detector.DetectorApi.setExposureTime') + with pytest.raises(DetectorValueError): + d.exposure_time = -7 + + +def test_get_file_index(d, mocker): + m = mocker.patch('_sls_detector.DetectorApi.getFileIndex') + m.return_value = 8 + assert d.file_index == 8 + +def test_set_file_index(d, mocker): + m = mocker.patch('_sls_detector.DetectorApi.setFileIndex') + d.file_index = 9 + m.assert_called_with(9) + + +def file_index_with_no_detector(d): + assert d.file_index == -100 + +def dr_with_no_detector(d): + assert d.dynamic_range == -100 + +def test_set_file_index_raises_on_neg(d, mocker): + mocker.patch('_sls_detector.DetectorApi.setFileIndex') + with pytest.raises(ValueError): + d.file_index = -9 + + +def test_get_file_name(d, mocker): + m = mocker.patch('_sls_detector.DetectorApi.getFileName') + d.file_name + m.assert_called_once_with() + +def test_set_file_name(d, mocker): + m = mocker.patch('_sls_detector.DetectorApi.setFileName') + d.file_name = 'hej' + m.assert_called_once_with('hej') + +def test_get_file_path(d, mocker): + m = mocker.patch('_sls_detector.DetectorApi.getFilePath') + d.file_path + m.assert_called_once_with() + +def test_set_file_path_when_path_exists(d, mocker): + m = mocker.patch('_sls_detector.DetectorApi.setFilePath') + #To avoid raising an exception because path is not there + mock_os = mocker.patch('os.path.exists') + mock_os.return_value = True + d.file_path = '/path/to/something/' + m.assert_called_once_with('/path/to/something/') + +def test_set_file_path_raises_when_not_exists(d, mocker): + mocker.patch('_sls_detector.DetectorApi.setFilePath') + mock_os = mocker.patch('os.path.exists') + mock_os.return_value = False + with pytest.raises(FileNotFoundError): + d.file_path = '/path/to/something/' + +def test_get_file_write(d, mocker): + m = mocker.patch('_sls_detector.DetectorApi.getFileWrite') + m.return_value = False + assert d.file_write == False + +def test_set_file_write(d, mocker): + m = mocker.patch('_sls_detector.DetectorApi.setFileWrite') + d.file_write = True + m.assert_called_once_with(True) + + +def test_get_firmware_version(d, mocker): + m = mocker.patch('_sls_detector.DetectorApi.getFirmwareVersion') + m.return_value = 20 + assert d.firmware_version == 20 + +def test_cannot_set_fw_version(d): + with pytest.raises(AttributeError): + d.firmware_version = 20 + +def test_get_high_voltage_call_signature(d, mocker): + m = mocker.patch('_sls_detector.DetectorApi.getDac') + d.high_voltage + m.assert_called_once_with('vhighvoltage', -1) + +def test_get_high_voltage(d, mocker): + m = mocker.patch('_sls_detector.DetectorApi.getDac') + m.return_value = 80 + assert d.high_voltage == 80 + +#self._api.setDac('vhighvoltage', -1, voltage) +def test_set_high_voltage(d, mocker): + m = mocker.patch('_sls_detector.DetectorApi.setDac') + d.high_voltage = 80 + m.assert_called_once_with('vhighvoltage', -1, 80) + +def test_decode_hostname_two_names(d, mocker): + m = mocker.patch('_sls_detector.DetectorApi.getHostname') + m.return_value = 'beb059+beb048+' + assert d.hostname == ['beb059', 'beb048'] + +def test_decode_hostname_four_names(d, mocker): + m = mocker.patch('_sls_detector.DetectorApi.getHostname') + m.return_value = 'beb059+beb048+beb120+beb153+' + assert d.hostname == ['beb059', 'beb048', 'beb120', 'beb153'] + +def test_decode_hostname_blank(d, mocker): + m = mocker.patch('_sls_detector.DetectorApi.getHostname') + m.return_value = '' + assert d.hostname == [] + +def test_get_image_size_gives_correct_size(d, mocker): + m = mocker.patch('_sls_detector.DetectorApi.getImageSize') + m.return_value = (512,1024) + im_size = d.image_size + assert im_size.rows == 512 + assert im_size.cols == 1024 + + + +def test_load_config(d, mocker): + m = mocker.patch('_sls_detector.DetectorApi.readConfigurationFile') + #To avoid raising an exception because path is not there + mock_os = mocker.patch('os.path.isfile') + mock_os.return_value = True + d.load_config('/path/to/my/file.config') + m.assert_called_once_with('/path/to/my/file.config') + +def test_load_config_raises_when_file_is_not_found(d, mocker): + mocker.patch('_sls_detector.DetectorApi.readConfigurationFile') + mock_os = mocker.patch('os.path.isfile') + mock_os.return_value = False + with pytest.raises(FileNotFoundError): + d.load_config('/path/to/my/file.config') + +def test_load_parameters(d, mocker): + m = mocker.patch('_sls_detector.DetectorApi.readParametersFile') + #To avoid raising an exception because path is not there + mock_os = mocker.patch('os.path.isfile') + mock_os.return_value = True + d.load_parameters('/path/to/my/file.par') + m.assert_called_once_with('/path/to/my/file.par') + +def test_load_parameters_raises_when_file_is_not_found(d, mocker): + mocker.patch('_sls_detector.DetectorApi.readParametersFile') + mock_os = mocker.patch('os.path.isfile') + mock_os.return_value = False + with pytest.raises(FileNotFoundError): + d.load_parameters('/path/to/my/file.par') + +#getDetectorGeometry +def test_get_module_geometry_gives_correct_size(d, mocker): + m = mocker.patch('_sls_detector.DetectorApi.getDetectorGeometry') + m.return_value = (13,7) + g = d.module_geometry + assert g.vertical == 7 + assert g.horizontal == 13 + +def test_get_module_geometry_access(d, mocker): + m = mocker.patch('_sls_detector.DetectorApi.getDetectorGeometry') + m.return_value = (12,3) + assert d.module_geometry[0] == 12 + assert d.module_geometry[1] == 3 + assert d.module_geometry.vertical == 3 + assert d.module_geometry.horizontal == 12 + +def test_module_geometry_without_detectors(d): + t = d.module_geometry + assert t.horizontal == 0 + assert t.vertical == 0 + +def test_get_n_frames(d, mocker): + m = mocker.patch('_sls_detector.DetectorApi.getNumberOfFrames') + m.return_value = 3 + assert d.n_frames == 3 + +def test_set_n_frames(d, mocker): + m = mocker.patch('_sls_detector.DetectorApi.setNumberOfFrames') + d.n_frames = 9 + m.assert_called_once_with(9) + +def test_nframes_without_detector(d): + assert d.n_frames == -100 + +def test_set_n_frames_raises_on_neg(d, mocker): + mocker.patch('_sls_detector.DetectorApi.setNumberOfFrames') + with pytest.raises(DetectorValueError): + d.n_frames = -1 + +def test_set_n_frames_raises_on_zero(d, mocker): + mocker.patch('_sls_detector.DetectorApi.setNumberOfFrames') + with pytest.raises(DetectorValueError): + d.n_frames = 0 + +def test_n_cycles_without_detector(d): + assert d.n_cycles == -100 + +def test_set_n_cycles_raises_on_zero(d, mocker): + mocker.patch('_sls_detector.DetectorApi.setCycles') + with pytest.raises(DetectorValueError): + d.n_cycles = 0 + +def test_set_n_cycles(d, mocker): + m = mocker.patch('_sls_detector.DetectorApi.setCycles') + d.n_cycles = 56 + m.assert_called_once_with(56) + + + +def test_n_measurements_without_detector(d): + assert d.n_measurements == -100 + +def test_set_n_measurements_raises_on_zero(d, mocker): + mocker.patch('_sls_detector.DetectorApi.setNumberOfMeasurements') + with pytest.raises(DetectorValueError): + d.n_measurements = 0 + +def test_set_n_measurements(d, mocker): + m = mocker.patch('_sls_detector.DetectorApi.setNumberOfMeasurements') + d.n_measurements = 560 + m.assert_called_once_with(560) + +def test_get_n_modules_no_detector(d): + assert d.n_modules == 0 + +def test_get_n_modules(d, mocker): + m = mocker.patch('_sls_detector.DetectorApi.getNumberOfDetectors') + m.return_value = 12 + assert d.n_modules == 12 + +def test_get_period_time(d, mocker): + m = mocker.patch('_sls_detector.DetectorApi.getPeriod') + m.return_value = 130000000 + assert d.period == 0.13 + +def test_set_period_time(d, mocker): + m = mocker.patch('_sls_detector.DetectorApi.setPeriod') + d.period = 1.953 + m.assert_called_once_with(1953000000) + +def test_set_period_time_less_than_zero(d, mocker): + mocker.patch('_sls_detector.DetectorApi.setPeriod') + with pytest.raises(ValueError): + d.period = -7 + + +def test_get_online(d, mocker): + m = mocker.patch('_sls_detector.DetectorApi.getOnline') + d.online + m.assert_called_once_with() + +def test_set_online(d, mocker): + m = mocker.patch('_sls_detector.DetectorApi.setOnline') + d.online = True + m.assert_called_once_with(True) + +def test_last_client_ip_no_detector(d): + assert d.last_client_ip == '' + +def test_last_cliten_ip_call(d, mocker): + m = mocker.patch('_sls_detector.DetectorApi.getLastClientIP') + d.last_client_ip + m.assert_called_once_with() + +#-------------------------------------------------------------Rate correction +def test_get_rate_correction(d, mocker): + m = mocker.patch('_sls_detector.DetectorApi.getRateCorrection') + m.return_value = [132,129] + assert d.rate_correction == [132,129] + +def test_set_rate_correction(d, mocker): + m = mocker.patch('_sls_detector.DetectorApi.setRateCorrection') + mock_n = mocker.patch('_sls_detector.DetectorApi.getNumberOfDetectors') + mock_n.return_value = 3 + d.rate_correction = [123,90,50] + m.assert_called_once_with([123,90,50]) + +def test_set_rate_correction_raises_on_wrong_number_of_values(d, mocker): + mocker.patch('_sls_detector.DetectorApi.setRateCorrection') + mock_n = mocker.patch('_sls_detector.DetectorApi.getNumberOfDetectors') + mock_n.return_value = 4 + with pytest.raises(ValueError): + d.rate_correction = [123,90,50] + +#----------------------------------------------------------------Readout clock +def test_get_readout_clock_0(d, mocker): + m = mocker.patch('_sls_detector.DetectorApi.getReadoutClockSpeed') + m.return_value = 0 + assert d.readout_clock == 'Full Speed' + +def test_get_readout_clock_1(d, mocker): + m = mocker.patch('_sls_detector.DetectorApi.getReadoutClockSpeed') + m.return_value = 1 + assert d.readout_clock == 'Half Speed' + +def test_get_readout_clock_2(d, mocker): + m = mocker.patch('_sls_detector.DetectorApi.getReadoutClockSpeed') + m.return_value = 2 + assert d.readout_clock == 'Quarter Speed' + +def test_get_readout_clock_3(d, mocker): + m = mocker.patch('_sls_detector.DetectorApi.getReadoutClockSpeed') + m.return_value = 3 + assert d.readout_clock == 'Super Slow Speed' + +def test_set_readout_clock_0(d, mocker): + m = mocker.patch('_sls_detector.DetectorApi.setReadoutClockSpeed') + d.readout_clock = 'Full Speed' + m.assert_called_once_with(0) + +def test_set_readout_clock_1(d, mocker): + m = mocker.patch('_sls_detector.DetectorApi.setReadoutClockSpeed') + d.readout_clock = 'Half Speed' + m.assert_called_once_with(1) + +def test_set_readout_clock_2(d, mocker): + m = mocker.patch('_sls_detector.DetectorApi.setReadoutClockSpeed') + d.readout_clock = 'Quarter Speed' + m.assert_called_once_with(2) + +def test_set_readout_clock_3(d, mocker): + m = mocker.patch('_sls_detector.DetectorApi.setReadoutClockSpeed') + d.readout_clock = 'Super Slow Speed' + m.assert_called_once_with(3) + +#----------------------------------------------------------------rx_datastream +def test_get_rx_datastream(d, mocker): + m = mocker.patch('_sls_detector.DetectorApi.getRxDataStreamStatus') + m.return_value = False + assert d.rx_datastream == False + +def test_set_rx_datastream(d, mocker): + m = mocker.patch('_sls_detector.DetectorApi.setRxDataStreamStatus') + d.rx_datastream = True + m.assert_called_once_with(True) + +def test_get_rx_zmqip(d, mocker): + m = mocker.patch('_sls_detector.DetectorApi.getNetworkParameter') + d.rx_zmqip + m.assert_called_once_with('rx_zmqip') + +def test_get_rx_zmqport_call(d, mocker): + m = mocker.patch('_sls_detector.DetectorApi.getNetworkParameter') + d.rx_zmqport + m.assert_called_once_with('rx_zmqport') + +def test_get_rx_zmqport_decode(d, mocker): + m = mocker.patch('_sls_detector.DetectorApi.getNetworkParameter') + m.return_value = ['30001', '30003'] + assert d.rx_zmqport == [30001, 30003] + +def test_get_rx_zmqport_empty(d, mocker): + m = mocker.patch('_sls_detector.DetectorApi.getNetworkParameter') + m.return_value = '' + assert d.rx_zmqport == [] + + +#--------------------------------------------------------------------status +def test_status_call(d, mocker): + m = mocker.patch('_sls_detector.DetectorApi.getRunStatus') + d.status + m.assert_called_once_with() + +def test_start_detecor(d, mocker): + m = mocker.patch('_sls_detector.DetectorApi.startAcquisition') + d.start_detector() + m.assert_called_once_with() + +def test_stop_acq_call(d, mocker): + m = mocker.patch('_sls_detector.DetectorApi.stopAcquisition') + d.stop_detector() + m.assert_called_once_with() + + + +#------------------------------------------------------------------timing mode +def test_get_timing_mode(d, mocker): + m = mocker.patch('_sls_detector.DetectorApi.getTimingMode') + d.timing_mode + m.assert_called_once_with() + +def test_set_timing_mode(d, mocker): + m = mocker.patch('_sls_detector.DetectorApi.setTimingMode') + d.timing_mode = 'auto' + m.assert_called_once_with('auto') + +#----------------------------------------------------------------vthreshold +def test_get_vthreshold(d, mocker): + m = mocker.patch('_sls_detector.DetectorApi.getDac') + d.vthreshold + m.assert_called_once_with('vthreshold', -1) + +def test_set_vthreshold(d, mocker): + m = mocker.patch('_sls_detector.DetectorApi.setDac') + d.vthreshold = 1675 + m.assert_called_once_with('vthreshold', -1, 1675) + + + + diff --git a/python/unit-tests/test_detector_property.py b/python/unit-tests/test_detector_property.py new file mode 100644 index 000000000..c647c4efc --- /dev/null +++ b/python/unit-tests/test_detector_property.py @@ -0,0 +1,76 @@ +import pytest +from sls_detector.detector_property import DetectorProperty + +class Holder: + """ + This class does nothing except hold values + for testing of the DetectorProperty class + """ + def __init__(self, N): + self.values = [i for i in range(N)] + def get(self, i): + return self.values[i] + def set(self, i,v): + self.values[i] = v + def nmod(self): + return len(self.values) + +@pytest.fixture +def p(): + h = Holder(5) + return DetectorProperty(h.get, h.set, h.nmod, 'prop') + +def test_initialization(): + def getf(i): + return 5 + def setf(): + return + def nmod(): + return 3 + name = 'a property' + p = DetectorProperty(getf, setf, nmod, name) + assert p.get == getf + assert p.set == setf + assert p.get_nmod == nmod + assert p.__name__ == name + +def test_get_single_value(p): + assert p[2] == 2 + +def test_get_all_values(p): + assert p[:] == [0, 1, 2, 3, 4] + +def test_get_values_by_iterable(p): + vals = p[1,3] + assert vals == [1,3] + +def test_set_single_value(p): + p[2] = 7 + assert p[:] == [0,1,7,3,4] + +def test_set_all(p): + p[:] = 10 + assert p[:] == [10,10,10,10,10] + +def test_set_all_by_list(p): + p[:] = [7,8,9,10,11] + assert p[:] == [7,8,9,10,11] + +def test_set_all_bool(p): + p[:] = True + assert p[:] == [True]*5 + +def test_set_by_iter(p): + keys = [2,4] + vals = [18,23] + p[keys] = vals + assert p[:] == [0,1,18,3,23] + +def test_set_by_iter_single_val(p): + keys = [2,4] + val = 9 + p[keys] = val + assert p[:] == [0,1,9,3,9] + +def test_print_values(p): + assert repr(p) == 'prop: [0, 1, 2, 3, 4]' diff --git a/python/unit-tests/test_utils.py b/python/unit-tests/test_utils.py new file mode 100644 index 000000000..e759f6cb4 --- /dev/null +++ b/python/unit-tests/test_utils.py @@ -0,0 +1,50 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +Testing parameters and methods of the Detector class using mocks +""" + +import pytest +from sls_detector.utils import eiger_register_to_time +from sls_detector.utils import all_equal, element_if_equal + +def test_convert_zero(): + assert eiger_register_to_time(0) == 0 + +def test_convert_smallest_unit(): + assert pytest.approx(eiger_register_to_time(0b1000), 1e-9) == 1e-8 + +def test_convert_second_smallest_unit(): + assert pytest.approx(eiger_register_to_time(0b10000), 1e-9) == 2e-8 + +def test_convert_one_ms_using_exponent(): + assert pytest.approx(eiger_register_to_time(0b1101), 1e-9) == 1e-3 + +def test_convert_five_seconds(): + assert pytest.approx(eiger_register_to_time(0b1001110001000101), 1e-9) == 5.0 + +def test_all_equal_int(): + assert all_equal([5,5]) == True + +def test_all_equal_fails(): + assert all_equal([5,6]) == False + +def test_all_equal_tuple(): + assert all_equal(('a', 'a', 'a')) == True + +def test_all_equal_str(): + assert all_equal('aaa') == True + +def test_all_equal_str_fails(): + assert all_equal('aaab') == False + + + +def test_element_if_equal_int(): + assert element_if_equal([5,5]) == 5 + +def test_element_if_equal_str(): + assert element_if_equal('hhh') == 'h' + +def test_element_if_equal_int_fails(): + assert element_if_equal([5, 6, 7]) == [5, 6, 7] \ No newline at end of file