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